summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.ci/scripts/format/exec.sh3
-rw-r--r--.ci/scripts/format/script.sh2
-rwxr-xr-x[-rw-r--r--].ci/scripts/linux/docker.sh2
-rw-r--r--.ci/scripts/linux/exec.sh3
-rwxr-xr-x[-rw-r--r--].ci/scripts/windows/docker.sh22
-rw-r--r--.ci/scripts/windows/exec.sh3
-rw-r--r--.ci/templates/build-msvc.yml6
-rw-r--r--.ci/yuzu-patreon-step2.yml12
-rw-r--r--.github/ISSUE_TEMPLATE.md31
-rw-r--r--.github/ISSUE_TEMPLATE/bug-report-feature-request.md40
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml8
-rw-r--r--.gitmodules41
-rw-r--r--.lgtm.yml10
-rwxr-xr-x.travis/clang-format/script.sh2
-rwxr-xr-x.travis/linux-mingw/docker.sh10
-rwxr-xr-x.travis/linux/docker.sh2
-rwxr-xr-x.travis/macos/build.sh3
-rw-r--r--CMakeLists.txt365
-rw-r--r--CMakeModules/CopyYuzuFFmpegDeps.cmake10
-rw-r--r--CMakeModules/CopyYuzuQt5Deps.cmake1
-rw-r--r--CMakeModules/CopyYuzuUnicornDeps.cmake9
-rw-r--r--CMakeModules/GenerateSCMRev.cmake2
-rw-r--r--CMakeModules/MinGWCross.cmake4
-rw-r--r--README.md6
-rw-r--r--dist/icons/controller/applet_dual_joycon.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark_disabled.pngbin0 -> 3527 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_disabled.pngbin0 -> 3314 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight.pngbin0 -> 3549 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight_disabled.pngbin0 -> 3584 bytes
-rw-r--r--dist/icons/controller/applet_handheld.pngbin0 -> 1671 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark.pngbin0 -> 1637 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark_disabled.pngbin0 -> 2642 bytes
-rw-r--r--dist/icons/controller/applet_handheld_disabled.pngbin0 -> 2221 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight.pngbin0 -> 1644 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight_disabled.pngbin0 -> 2634 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller.pngbin0 -> 4382 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark.pngbin0 -> 4236 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark_disabled.pngbin0 -> 4477 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_disabled.pngbin0 -> 4173 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight.pngbin0 -> 4376 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight_disabled.pngbin0 -> 4459 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left.pngbin0 -> 2083 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark.pngbin0 -> 2067 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark_disabled.pngbin0 -> 2520 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_disabled.pngbin0 -> 2179 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight.pngbin0 -> 2065 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight_disabled.pngbin0 -> 2529 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark.pngbin0 -> 2146 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark_disabled.pngbin0 -> 2556 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_disabled.pngbin0 -> 2212 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight_disabled.pngbin0 -> 2611 bytes
-rw-r--r--dist/icons/controller/controller.qrc55
-rw-r--r--dist/icons/controller/dual_joycon.pngbin0 -> 36466 bytes
-rw-r--r--dist/icons/controller/dual_joycon_dark.pngbin0 -> 36261 bytes
-rw-r--r--dist/icons/controller/dual_joycon_midnight.pngbin0 -> 34667 bytes
-rw-r--r--dist/icons/controller/handheld.pngbin0 -> 14108 bytes
-rw-r--r--dist/icons/controller/handheld_dark.pngbin0 -> 13731 bytes
-rw-r--r--dist/icons/controller/handheld_midnight.pngbin0 -> 13366 bytes
-rw-r--r--dist/icons/controller/pro_controller.pngbin0 -> 36710 bytes
-rw-r--r--dist/icons/controller/pro_controller_dark.pngbin0 -> 34897 bytes
-rw-r--r--dist/icons/controller/pro_controller_midnight.pngbin0 -> 35893 bytes
-rw-r--r--dist/icons/controller/single_joycon_left.pngbin0 -> 25565 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_dark.pngbin0 -> 25682 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_midnight.pngbin0 -> 24405 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical.pngbin0 -> 24764 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical_dark.pngbin0 -> 24938 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical_midnight.pngbin0 -> 23681 bytes
-rw-r--r--dist/icons/controller/single_joycon_right.pngbin0 -> 28320 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_dark.pngbin0 -> 28157 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_midnight.pngbin0 -> 27006 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical.pngbin0 -> 27655 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical_dark.pngbin0 -> 27729 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical_midnight.pngbin0 -> 26354 bytes
-rw-r--r--dist/languages/.gitignore2
-rw-r--r--dist/languages/.tx/config8
-rw-r--r--dist/languages/README.md1
-rw-r--r--dist/languages/de.ts4749
-rw-r--r--dist/languages/es.ts4757
-rw-r--r--dist/languages/fr.ts4732
-rw-r--r--dist/languages/it.ts4724
-rw-r--r--dist/languages/ja_JP.ts4752
-rw-r--r--dist/languages/nl.ts4719
-rw-r--r--dist/languages/pl.ts4713
-rw-r--r--dist/languages/pt_BR.ts4757
-rw-r--r--dist/languages/pt_PT.ts4725
-rw-r--r--dist/languages/ru_RU.ts4720
-rw-r--r--dist/languages/zh_CN.ts4747
-rw-r--r--dist/license.md3
-rw-r--r--dist/qt_themes/colorful_dark/icons/16x16/refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/colorful_dark/icons/16x16/view-refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/colorful_dark/style.qrc1
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/16x16/lock.pngbin0 -> 401 bytes
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/16x16/refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/16x16/view-refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/index.theme8
-rw-r--r--dist/qt_themes/colorful_midnight_blue/style.qrc58
-rw-r--r--dist/qt_themes/default/default.qrc1
-rw-r--r--dist/qt_themes/default/icons/16x16/refresh.pngbin0 -> 349 bytes
-rw-r--r--dist/qt_themes/default/icons/16x16/view-refresh.pngbin0 -> 349 bytes
-rw-r--r--dist/qt_themes/default/style.qss250
-rw-r--r--dist/qt_themes/qdarkstyle/icons/16x16/refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/16x16/view-refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_checked.pngbin492 -> 1935 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.pngbin491 -> 1960 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.pngbin252 -> 1813 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.pngbin493 -> 492 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.pngbin492 -> 491 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.pngbin249 -> 252 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc3
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss334
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/LICENSE.rst405
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/lock.pngbin0 -> 304 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/view-refresh.pngbin0 -> 362 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/256x256/plus_folder.pngbin0 -> 3438 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/bad_folder.pngbin0 -> 1098 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/chip.pngbin0 -> 15120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/folder.pngbin0 -> 542 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/plus.pngbin0 -> 339 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/sd_card.pngbin0 -> 676 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme14
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/Hmovetoolbar.pngbin0 -> 220 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/Hsepartoolbar.pngbin0 -> 172 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/Vmovetoolbar.pngbin0 -> 228 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/Vsepartoolbar.pngbin0 -> 187 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down.pngbin0 -> 525 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down@2x.pngbin0 -> 977 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled.pngbin0 -> 547 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled@2x.pngbin0 -> 1040 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus.pngbin0 -> 530 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus@2x.pngbin0 -> 1025 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed.pngbin0 -> 518 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed@2x.pngbin0 -> 1007 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left.pngbin0 -> 546 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left@2x.pngbin0 -> 1072 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled.pngbin0 -> 569 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled@2x.pngbin0 -> 1126 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus.pngbin0 -> 565 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus@2x.pngbin0 -> 1143 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed.pngbin0 -> 541 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed@2x.pngbin0 -> 1120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right.pngbin0 -> 518 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right@2x.pngbin0 -> 1062 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled.pngbin0 -> 553 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled@2x.pngbin0 -> 1143 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus.pngbin0 -> 543 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus@2x.pngbin0 -> 1139 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed.pngbin0 -> 544 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed@2x.pngbin0 -> 1121 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up.pngbin0 -> 512 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up@2x.pngbin0 -> 969 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled.pngbin0 -> 538 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled@2x.pngbin0 -> 1046 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus.pngbin0 -> 530 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus@2x.pngbin0 -> 1017 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed.pngbin0 -> 518 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed@2x.pngbin0 -> 998 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon.pngbin0 -> 1256 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon@2x.pngbin0 -> 3286 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled.pngbin0 -> 1256 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled@2x.pngbin0 -> 3286 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus.pngbin0 -> 1256 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus@2x.pngbin0 -> 3286 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed.pngbin0 -> 1256 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed@2x.pngbin0 -> 3286 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed-on.pngbin0 -> 147 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed.pngbin0 -> 350 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed@2x.pngbin0 -> 704 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled.pngbin0 -> 373 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled@2x.pngbin0 -> 729 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus.pngbin0 -> 380 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus@2x.pngbin0 -> 717 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed.pngbin0 -> 372 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed@2x.pngbin0 -> 725 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end.pngbin0 -> 142 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end@2x.pngbin0 -> 220 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled.pngbin0 -> 146 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled@2x.pngbin0 -> 225 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus.pngbin0 -> 146 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus@2x.pngbin0 -> 226 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed.pngbin0 -> 146 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed@2x.pngbin0 -> 225 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line.pngbin0 -> 130 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line@2x.pngbin0 -> 242 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled@2x.pngbin0 -> 248 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus@2x.pngbin0 -> 249 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed@2x.pngbin0 -> 248 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more.pngbin0 -> 155 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more@2x.pngbin0 -> 257 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled.pngbin0 -> 162 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled@2x.pngbin0 -> 265 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus.pngbin0 -> 162 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus@2x.pngbin0 -> 266 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed.pngbin0 -> 162 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed@2x.pngbin0 -> 265 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open-on.pngbin0 -> 150 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open.pngbin0 -> 354 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open@2x.pngbin0 -> 657 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled.pngbin0 -> 375 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled@2x.pngbin0 -> 682 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus.pngbin0 -> 367 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus@2x.pngbin0 -> 665 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed.pngbin0 -> 369 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed@2x.pngbin0 -> 661 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked.pngbin0 -> 452 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked@2x.pngbin0 -> 825 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled.pngbin0 -> 467 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled@2x.pngbin0 -> 845 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus.pngbin0 -> 441 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus@2x.pngbin0 -> 823 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed.pngbin0 -> 418 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed@2x.pngbin0 -> 829 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate.pngbin0 -> 581 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate@2x.pngbin0 -> 1081 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled.pngbin0 -> 614 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled@2x.pngbin0 -> 1105 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus.pngbin0 -> 576 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus@2x.pngbin0 -> 1066 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed.pngbin0 -> 563 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed@2x.pngbin0 -> 1087 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked.pngbin0 -> 397 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked@2x.pngbin0 -> 828 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled.pngbin0 -> 386 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled@2x.pngbin0 -> 875 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus.pngbin0 -> 394 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus@2x.pngbin0 -> 866 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed.pngbin0 -> 403 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed@2x.pngbin0 -> 861 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/close-hover.pngbin0 -> 598 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/close-pressed.pngbin0 -> 598 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/close.pngbin0 -> 586 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow.pngbin0 -> 165 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow_disabled.pngbin0 -> 166 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow.pngbin0 -> 166 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow_disabled.pngbin0 -> 166 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal.pngbin0 -> 117 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal@2x.pngbin0 -> 135 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled.pngbin0 -> 121 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled@2x.pngbin0 -> 139 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus.pngbin0 -> 120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus@2x.pngbin0 -> 138 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed.pngbin0 -> 120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed@2x.pngbin0 -> 138 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical.pngbin0 -> 130 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical@2x.pngbin0 -> 242 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled@2x.pngbin0 -> 248 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus@2x.pngbin0 -> 249 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed.pngbin0 -> 134 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed@2x.pngbin0 -> 248 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked.pngbin0 -> 1224 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked@2x.pngbin0 -> 2714 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled.pngbin0 -> 1325 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled@2x.pngbin0 -> 2893 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus.pngbin0 -> 1293 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus@2x.pngbin0 -> 2736 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed.pngbin0 -> 1276 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed@2x.pngbin0 -> 2765 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked.pngbin0 -> 963 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked@2x.pngbin0 -> 2195 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled.pngbin0 -> 1040 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled@2x.pngbin0 -> 2294 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus.pngbin0 -> 1032 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus@2x.pngbin0 -> 2186 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed.pngbin0 -> 1022 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed@2x.pngbin0 -> 2197 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow.pngbin0 -> 160 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow_disabled.pngbin0 -> 160 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/sizegrip.pngbin0 -> 129 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-end.pngbin0 -> 224 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-more.pngbin0 -> 182 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-vline.pngbin0 -> 239 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal.pngbin0 -> 150 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal@2x.pngbin0 -> 304 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled.pngbin0 -> 155 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled@2x.pngbin0 -> 308 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus.pngbin0 -> 154 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus@2x.pngbin0 -> 311 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed.pngbin0 -> 154 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed@2x.pngbin0 -> 307 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical.pngbin0 -> 137 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical@2x.pngbin0 -> 201 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled.pngbin0 -> 140 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled@2x.pngbin0 -> 212 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus.pngbin0 -> 144 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus@2x.pngbin0 -> 211 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed.pngbin0 -> 143 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed@2x.pngbin0 -> 204 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal.pngbin0 -> 145 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal@2x.pngbin0 -> 286 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled.pngbin0 -> 151 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled@2x.pngbin0 -> 292 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus.pngbin0 -> 149 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus@2x.pngbin0 -> 294 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed.pngbin0 -> 149 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed@2x.pngbin0 -> 289 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical.pngbin0 -> 133 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical@2x.pngbin0 -> 191 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled.pngbin0 -> 135 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled@2x.pngbin0 -> 199 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus.pngbin0 -> 139 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus@2x.pngbin0 -> 196 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed.pngbin0 -> 138 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed@2x.pngbin0 -> 193 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent.pngbin0 -> 104 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent@2x.pngbin0 -> 117 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled.pngbin0 -> 104 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled@2x.pngbin0 -> 117 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus.pngbin0 -> 104 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus@2x.pngbin0 -> 117 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed.pngbin0 -> 104 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed@2x.pngbin0 -> 117 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/undock.pngbin0 -> 578 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow.pngbin0 -> 158 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow_disabled.pngbin0 -> 159 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close.pngbin0 -> 766 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close@2x.pngbin0 -> 1690 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled.pngbin0 -> 838 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled@2x.pngbin0 -> 1724 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus.pngbin0 -> 756 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus@2x.pngbin0 -> 1704 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed.pngbin0 -> 745 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed@2x.pngbin0 -> 1679 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip.pngbin0 -> 426 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip@2x.pngbin0 -> 735 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled.pngbin0 -> 447 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled@2x.pngbin0 -> 768 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus.pngbin0 -> 435 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus@2x.pngbin0 -> 738 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed.pngbin0 -> 444 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed@2x.pngbin0 -> 729 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize.pngbin0 -> 193 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize@2x.pngbin0 -> 316 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled.pngbin0 -> 206 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled@2x.pngbin0 -> 332 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus.pngbin0 -> 208 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus@2x.pngbin0 -> 339 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed.pngbin0 -> 202 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed@2x.pngbin0 -> 336 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock.pngbin0 -> 510 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock@2x.pngbin0 -> 875 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled.pngbin0 -> 541 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled@2x.pngbin0 -> 910 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus.pngbin0 -> 519 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus@2x.pngbin0 -> 877 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed.pngbin0 -> 523 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed@2x.pngbin0 -> 880 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qrc226
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qss2489
-rw-r--r--dist/yuzu.manifest80
-rw-r--r--externals/CMakeLists.txt71
m---------externals/Vulkan-Headers0
m---------externals/boost0
m---------externals/catch0
m---------externals/cubeb0
m---------externals/dynarmic0
-rw-r--r--externals/find-modules/FindCatch2.cmake49
-rw-r--r--externals/find-modules/FindFFmpeg.cmake100
-rw-r--r--externals/find-modules/FindLibUSB.cmake43
-rw-r--r--externals/find-modules/FindLibzip.cmake72
-rw-r--r--externals/find-modules/FindUnicorn.cmake (renamed from externals/cmake-modules/FindUnicorn.cmake)0
-rw-r--r--externals/find-modules/Findfmt.cmake69
-rw-r--r--externals/find-modules/Findlz4.cmake54
-rw-r--r--externals/find-modules/Findnlohmann_json.cmake49
-rw-r--r--externals/find-modules/Findopus.cmake42
-rw-r--r--externals/find-modules/Findzstd.cmake55
m---------externals/fmt0
-rw-r--r--externals/httplib/README.md2
-rw-r--r--externals/httplib/httplib.h4762
m---------externals/inih/inih0
-rw-r--r--externals/json/README.md9
-rw-r--r--externals/json/json.hpp17300
m---------externals/libressl0
-rw-r--r--externals/libusb/CMakeLists.txt150
-rw-r--r--externals/libusb/config.h.in90
m---------externals/libusb/libusb0
-rw-r--r--externals/lurlparser/CMakeLists.txt8
-rw-r--r--externals/lurlparser/LUrlParser.cpp265
-rw-r--r--externals/lurlparser/LUrlParser.h78
-rw-r--r--externals/lurlparser/README.md19
m---------externals/lz40
-rw-r--r--externals/microprofile/microprofile.h97
-rw-r--r--externals/microprofile/microprofileui.h320
-rw-r--r--externals/opus/CMakeLists.txt6
m---------externals/sirit0
m---------externals/unicorn0
m---------externals/xbyak0
-rw-r--r--externals/zlib/CMakeLists.txt81
m---------externals/zlib/zlib0
m---------externals/zstd0
-rw-r--r--src/CMakeLists.txt24
-rw-r--r--src/audio_core/CMakeLists.txt35
-rw-r--r--src/audio_core/algorithm/filter.cpp3
-rw-r--r--src/audio_core/algorithm/interpolate.cpp37
-rw-r--r--src/audio_core/algorithm/interpolate.h3
-rw-r--r--src/audio_core/audio_renderer.cpp455
-rw-r--r--src/audio_core/audio_renderer.h229
-rw-r--r--src/audio_core/behavior_info.cpp105
-rw-r--r--src/audio_core/behavior_info.h72
-rw-r--r--src/audio_core/codec.cpp5
-rw-r--r--src/audio_core/codec.h2
-rw-r--r--src/audio_core/command_generator.cpp977
-rw-r--r--src/audio_core/command_generator.h102
-rw-r--r--src/audio_core/common.h108
-rw-r--r--src/audio_core/cubeb_sink.cpp32
-rw-r--r--src/audio_core/effect_context.cpp299
-rw-r--r--src/audio_core/effect_context.h321
-rw-r--r--src/audio_core/info_updater.cpp516
-rw-r--r--src/audio_core/info_updater.h58
-rw-r--r--src/audio_core/memory_pool.cpp62
-rw-r--r--src/audio_core/memory_pool.h53
-rw-r--r--src/audio_core/mix_context.cpp296
-rw-r--r--src/audio_core/mix_context.h114
-rw-r--r--src/audio_core/sink_context.cpp31
-rw-r--r--src/audio_core/sink_context.h89
-rw-r--r--src/audio_core/splitter_context.cpp617
-rw-r--r--src/audio_core/splitter_context.h221
-rw-r--r--src/audio_core/stream.cpp24
-rw-r--r--src/audio_core/stream.h7
-rw-r--r--src/audio_core/voice_context.cpp529
-rw-r--r--src/audio_core/voice_context.h296
-rw-r--r--src/common/CMakeLists.txt51
-rw-r--r--src/common/algorithm.h3
-rw-r--r--src/common/alignment.h69
-rw-r--r--src/common/assert.h7
-rw-r--r--src/common/atomic_ops.cpp75
-rw-r--r--src/common/atomic_ops.h17
-rw-r--r--src/common/bit_cast.h22
-rw-r--r--src/common/bit_field.h21
-rw-r--r--src/common/bit_util.h34
-rw-r--r--src/common/cityhash.h23
-rw-r--r--src/common/color.h38
-rw-r--r--src/common/common_funcs.h42
-rw-r--r--src/common/common_paths.h1
-rw-r--r--src/common/concepts.h34
-rw-r--r--src/common/detached_tasks.cpp3
-rw-r--r--src/common/dynamic_library.cpp2
-rw-r--r--src/common/dynamic_library.h13
-rw-r--r--src/common/fiber.cpp233
-rw-r--r--src/common/fiber.h78
-rw-r--r--src/common/file_util.cpp133
-rw-r--r--src/common/file_util.h98
-rw-r--r--src/common/hash.h25
-rw-r--r--src/common/hex_util.cpp34
-rw-r--r--src/common/hex_util.h37
-rw-r--r--src/common/logging/backend.cpp39
-rw-r--r--src/common/logging/backend.h16
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/lz4_compression.cpp16
-rw-r--r--src/common/lz4_compression.h24
-rw-r--r--src/common/math_util.h22
-rw-r--r--src/common/memory_detect.cpp73
-rw-r--r--src/common/memory_detect.h22
-rw-r--r--src/common/misc.cpp15
-rw-r--r--src/common/multi_level_queue.h37
-rw-r--r--src/common/page_table.cpp36
-rw-r--r--src/common/page_table.h53
-rw-r--r--src/common/param_package.h12
-rw-r--r--src/common/quaternion.h45
-rw-r--r--src/common/ring_buffer.h4
-rw-r--r--src/common/scope_exit.h9
-rw-r--r--src/common/spin_lock.cpp54
-rw-r--r--src/common/spin_lock.h34
-rw-r--r--src/common/stream.cpp47
-rw-r--r--src/common/stream.h56
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h46
-rw-r--r--src/common/swap.h46
-rw-r--r--src/common/telemetry.cpp5
-rw-r--r--src/common/telemetry.h16
-rw-r--r--src/common/thread.cpp64
-rw-r--r--src/common/thread.h22
-rw-r--r--src/common/thread_queue_list.h10
-rw-r--r--src/common/threadsafe_queue.h12
-rw-r--r--src/common/time_zone.cpp49
-rw-r--r--src/common/time_zone.h18
-rw-r--r--src/common/timer.cpp12
-rw-r--r--src/common/timer.h16
-rw-r--r--src/common/uint128.cpp26
-rw-r--r--src/common/uint128.h7
-rw-r--r--src/common/uuid.h17
-rw-r--r--src/common/vector_math.h279
-rw-r--r--src/common/virtual_buffer.cpp43
-rw-r--r--src/common/virtual_buffer.h82
-rw-r--r--src/common/wall_clock.cpp91
-rw-r--r--src/common/wall_clock.h55
-rw-r--r--src/common/web_result.h25
-rw-r--r--src/common/x64/cpu_detect.cpp38
-rw-r--r--src/common/x64/cpu_detect.h13
-rw-r--r--src/common/x64/native_clock.cpp103
-rw-r--r--src/common/x64/native_clock.h48
-rw-r--r--src/common/x64/xbyak_abi.h229
-rw-r--r--src/common/x64/xbyak_util.h47
-rw-r--r--src/common/zstd_compression.cpp1
-rw-r--r--src/common/zstd_compression.h17
-rw-r--r--src/core/CMakeLists.txt97
-rw-r--r--src/core/arm/arm_interface.cpp71
-rw-r--r--src/core/arm/arm_interface.h48
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp25
-rw-r--r--src/core/arm/cpu_interrupt_handler.h40
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp139
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h14
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp170
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h30
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp87
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h126
-rw-r--r--src/core/arm/dynarmic/arm_exclusive_monitor.cpp76
-rw-r--r--src/core/arm/dynarmic/arm_exclusive_monitor.h48
-rw-r--r--src/core/arm/exclusive_monitor.cpp2
-rw-r--r--src/core/arm/exclusive_monitor.h8
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp274
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h55
-rw-r--r--src/core/constants.h1
-rw-r--r--src/core/core.cpp223
-rw-r--r--src/core/core.h207
-rw-r--r--src/core/core_manager.cpp67
-rw-r--r--src/core/core_manager.h63
-rw-r--r--src/core/core_timing.cpp265
-rw-r--r--src/core/core_timing.h132
-rw-r--r--src/core/core_timing_util.cpp45
-rw-r--r--src/core/core_timing_util.h19
-rw-r--r--src/core/cpu_manager.cpp367
-rw-r--r--src/core/cpu_manager.h82
-rw-r--r--src/core/crypto/aes_util.cpp23
-rw-r--r--src/core/crypto/aes_util.h9
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp9
-rw-r--r--src/core/crypto/ctr_encryption_layer.h9
-rw-r--r--src/core/crypto/key_manager.cpp469
-rw-r--r--src/core/crypto/key_manager.h27
-rw-r--r--src/core/crypto/partition_data_manager.cpp223
-rw-r--r--src/core/device_memory.cpp12
-rw-r--r--src/core/device_memory.h47
-rw-r--r--src/core/file_sys/bis_factory.cpp36
-rw-r--r--src/core/file_sys/bis_factory.h5
-rw-r--r--src/core/file_sys/card_image.cpp9
-rw-r--r--src/core/file_sys/card_image.h7
-rw-r--r--src/core/file_sys/content_archive.cpp25
-rw-r--r--src/core/file_sys/content_archive.h5
-rw-r--r--src/core/file_sys/control_metadata.cpp5
-rw-r--r--src/core/file_sys/control_metadata.h5
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp14
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h2
-rw-r--r--src/core/file_sys/ips_layer.cpp8
-rw-r--r--src/core/file_sys/kernel_executable.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.h9
-rw-r--r--src/core/file_sys/mode.h9
-rw-r--r--src/core/file_sys/nca_metadata.cpp3
-rw-r--r--src/core/file_sys/nca_metadata.h2
-rw-r--r--src/core/file_sys/nca_patch.cpp86
-rw-r--r--src/core/file_sys/nca_patch.h4
-rw-r--r--src/core/file_sys/partition_filesystem.cpp8
-rw-r--r--src/core/file_sys/partition_filesystem.h8
-rw-r--r--src/core/file_sys/patch_manager.cpp259
-rw-r--r--src/core/file_sys/patch_manager.h58
-rw-r--r--src/core/file_sys/program_metadata.cpp12
-rw-r--r--src/core/file_sys/program_metadata.h8
-rw-r--r--src/core/file_sys/registered_cache.cpp164
-rw-r--r--src/core/file_sys/registered_cache.h12
-rw-r--r--src/core/file_sys/romfs.h1
-rw-r--r--src/core/file_sys/romfs_factory.cpp56
-rw-r--r--src/core/file_sys/romfs_factory.h21
-rw-r--r--src/core/file_sys/savedata_factory.cpp38
-rw-r--r--src/core/file_sys/savedata_factory.h47
-rw-r--r--src/core/file_sys/sdmc_factory.cpp6
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp98
-rw-r--r--src/core/file_sys/submission_package.h7
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp22
-rw-r--r--src/core/file_sys/system_archive/ng_word.cpp42
-rw-r--r--src/core/file_sys/system_archive/shared_font.cpp2
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp14
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp51
-rw-r--r--src/core/file_sys/vfs.cpp63
-rw-r--r--src/core/file_sys/vfs_concat.cpp8
-rw-r--r--src/core/file_sys/vfs_concat.h6
-rw-r--r--src/core/file_sys/vfs_libzip.cpp6
-rw-r--r--src/core/file_sys/vfs_offset.cpp7
-rw-r--r--src/core/file_sys/vfs_real.cpp282
-rw-r--r--src/core/file_sys/vfs_real.h8
-rw-r--r--src/core/file_sys/vfs_static.h8
-rw-r--r--src/core/file_sys/vfs_vector.h13
-rw-r--r--src/core/file_sys/xts_archive.cpp26
-rw-r--r--src/core/file_sys/xts_archive.h10
-rw-r--r--src/core/frontend/applets/controller.cpp81
-rw-r--r--src/core/frontend/applets/controller.h56
-rw-r--r--src/core/frontend/emu_window.cpp12
-rw-r--r--src/core/frontend/emu_window.h4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp12
-rw-r--r--src/core/frontend/framebuffer_layout.h6
-rw-r--r--src/core/frontend/input.h36
-rw-r--r--src/core/gdbstub/gdbstub.cpp42
-rw-r--r--src/core/hardware_interrupt_manager.cpp15
-rw-r--r--src/core/hardware_properties.h4
-rw-r--r--src/core/hle/ipc_helpers.h32
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp211
-rw-r--r--src/core/hle/kernel/address_arbiter.h3
-rw-r--r--src/core/hle/kernel/client_port.cpp2
-rw-r--r--src/core/hle/kernel/client_session.cpp6
-rw-r--r--src/core/hle/kernel/client_session.h9
-rw-r--r--src/core/hle/kernel/errors.h2
-rw-r--r--src/core/hle/kernel/handle_table.cpp12
-rw-r--r--src/core/hle/kernel/handle_table.h7
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp114
-rw-r--r--src/core/hle/kernel/hle_ipc.h55
-rw-r--r--src/core/hle/kernel/kernel.cpp437
-rw-r--r--src/core/hle/kernel/kernel.h84
-rw-r--r--src/core/hle/kernel/memory/address_space_info.cpp117
-rw-r--r--src/core/hle/kernel/memory/address_space_info.h34
-rw-r--r--src/core/hle/kernel/memory/memory_block.h335
-rw-r--r--src/core/hle/kernel/memory/memory_block_manager.cpp223
-rw-r--r--src/core/hle/kernel/memory/memory_block_manager.h66
-rw-r--r--src/core/hle/kernel/memory/memory_layout.h71
-rw-r--r--src/core/hle/kernel/memory/memory_manager.cpp175
-rw-r--r--src/core/hle/kernel/memory/memory_manager.h96
-rw-r--r--src/core/hle/kernel/memory/memory_types.h18
-rw-r--r--src/core/hle/kernel/memory/page_heap.cpp119
-rw-r--r--src/core/hle/kernel/memory/page_heap.h370
-rw-r--r--src/core/hle/kernel/memory/page_linked_list.h92
-rw-r--r--src/core/hle/kernel/memory/page_table.cpp1174
-rw-r--r--src/core/hle/kernel/memory/page_table.h277
-rw-r--r--src/core/hle/kernel/memory/slab_heap.h163
-rw-r--r--src/core/hle/kernel/memory/system_control.cpp40
-rw-r--r--src/core/hle/kernel/memory/system_control.h13
-rw-r--r--src/core/hle/kernel/mutex.cpp119
-rw-r--r--src/core/hle/kernel/mutex.h4
-rw-r--r--src/core/hle/kernel/physical_core.cpp60
-rw-r--r--src/core/hle/kernel/physical_core.h44
-rw-r--r--src/core/hle/kernel/physical_memory.h2
-rw-r--r--src/core/hle/kernel/process.cpp226
-rw-r--r--src/core/hle/kernel/process.h49
-rw-r--r--src/core/hle/kernel/process_capability.cpp54
-rw-r--r--src/core/hle/kernel/process_capability.h24
-rw-r--r--src/core/hle/kernel/readable_event.cpp16
-rw-r--r--src/core/hle/kernel/resource_limit.cpp48
-rw-r--r--src/core/hle/kernel/resource_limit.h12
-rw-r--r--src/core/hle/kernel/scheduler.cpp570
-rw-r--r--src/core/hle/kernel/scheduler.h125
-rw-r--r--src/core/hle/kernel/server_session.cpp33
-rw-r--r--src/core/hle/kernel/server_session.h15
-rw-r--r--src/core/hle/kernel/shared_memory.cpp151
-rw-r--r--src/core/hle/kernel/shared_memory.h127
-rw-r--r--src/core/hle/kernel/svc.cpp970
-rw-r--r--src/core/hle/kernel/svc.h6
-rw-r--r--src/core/hle/kernel/svc_types.h68
-rw-r--r--src/core/hle/kernel/svc_wrap.h137
-rw-r--r--src/core/hle/kernel/synchronization.cpp136
-rw-r--r--src/core/hle/kernel/synchronization_object.cpp64
-rw-r--r--src/core/hle/kernel/synchronization_object.h18
-rw-r--r--src/core/hle/kernel/thread.cpp428
-rw-r--r--src/core/hle/kernel/thread.h284
-rw-r--r--src/core/hle/kernel/time_manager.cpp32
-rw-r--r--src/core/hle/kernel/time_manager.h4
-rw-r--r--src/core/hle/kernel/transfer_memory.cpp104
-rw-r--r--src/core/hle/kernel/transfer_memory.h55
-rw-r--r--src/core/hle/kernel/vm_manager.cpp1175
-rw-r--r--src/core/hle/kernel/vm_manager.h796
-rw-r--r--src/core/hle/result.h12
-rw-r--r--src/core/hle/service/acc/acc.cpp449
-rw-r--r--src/core/hle/service/acc/acc.h5
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp4
-rw-r--r--src/core/hle/service/acc/acc_su.cpp32
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp18
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp28
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp65
-rw-r--r--src/core/hle/service/am/am.cpp208
-rw-r--r--src/core/hle/service/am/am.h21
-rw-r--r--src/core/hle/service/am/applet_ae.cpp4
-rw-r--r--src/core/hle/service/am/applets/applets.cpp72
-rw-r--r--src/core/hle/service/am/applets/applets.h19
-rw-r--r--src/core/hle/service/am/applets/controller.cpp252
-rw-r--r--src/core/hle/service/am/applets/controller.h135
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp64
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h1
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp19
-rw-r--r--src/core/hle/service/am/spsm.cpp16
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp5
-rw-r--r--src/core/hle/service/apm/apm.cpp1
-rw-r--r--src/core/hle/service/apm/controller.cpp3
-rw-r--r--src/core/hle/service/audio/audctl.cpp2
-rw-r--r--src/core/hle/service/audio/audin_u.cpp70
-rw-r--r--src/core/hle/service/audio/audin_u.h29
-rw-r--r--src/core/hle/service/audio/audout_u.cpp6
-rw-r--r--src/core/hle/service/audio/audren_u.cpp156
-rw-r--r--src/core/hle/service/audio/hwopus.cpp2
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp2
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp55
-rw-r--r--src/core/hle/service/bcat/bcat.cpp2
-rw-r--r--src/core/hle/service/bcat/module.cpp7
-rw-r--r--src/core/hle/service/bpc/bpc.cpp20
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp168
-rw-r--r--src/core/hle/service/btm/btm.cpp148
-rw-r--r--src/core/hle/service/caps/caps.cpp2
-rw-r--r--src/core/hle/service/caps/caps.h76
-rw-r--r--src/core/hle/service/caps/caps_a.cpp2
-rw-r--r--src/core/hle/service/caps/caps_a.h2
-rw-r--r--src/core/hle/service/caps/caps_c.cpp18
-rw-r--r--src/core/hle/service/caps/caps_c.h5
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp2
-rw-r--r--src/core/hle/service/caps/caps_sc.h2
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp2
-rw-r--r--src/core/hle/service/caps/caps_ss.h2
-rw-r--r--src/core/hle/service/caps/caps_su.cpp19
-rw-r--r--src/core/hle/service/caps/caps_su.h5
-rw-r--r--src/core/hle/service/caps/caps_u.cpp56
-rw-r--r--src/core/hle/service/caps/caps_u.h4
-rw-r--r--src/core/hle/service/es/es.cpp53
-rw-r--r--src/core/hle/service/eupld/eupld.cpp1
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp103
-rw-r--r--src/core/hle/service/filesystem/filesystem.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp140
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h14
-rw-r--r--src/core/hle/service/friend/friend.cpp8
-rw-r--r--src/core/hle/service/glue/arp.cpp1
-rw-r--r--src/core/hle/service/glue/errors.h8
-rw-r--r--src/core/hle/service/grc/grc.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h4
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp55
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp18
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp845
-rw-r--r--src/core/hle/service/hid/controllers/npad.h223
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp16
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp2
-rw-r--r--src/core/hle/service/hid/hid.cpp904
-rw-r--r--src/core/hle/service/hid/hid.h59
-rw-r--r--src/core/hle/service/hid/irs.cpp8
-rw-r--r--src/core/hle/service/lbl/lbl.cpp1
-rw-r--r--src/core/hle/service/ldn/ldn.cpp1
-rw-r--r--src/core/hle/service/ldr/ldr.cpp426
-rw-r--r--src/core/hle/service/lm/lm.cpp9
-rw-r--r--src/core/hle/service/lm/manager.cpp3
-rw-r--r--src/core/hle/service/mig/mig.cpp6
-rw-r--r--src/core/hle/service/mii/manager.cpp484
-rw-r--r--src/core/hle/service/mii/manager.h331
-rw-r--r--src/core/hle/service/mii/mii.cpp316
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_manager.h273
-rw-r--r--src/core/hle/service/mii/raw_data.cpp2261
-rw-r--r--src/core/hle/service/mii/raw_data.h27
-rw-r--r--src/core/hle/service/mii/types.h67
-rw-r--r--src/core/hle/service/mm/mm_u.cpp32
-rw-r--r--src/core/hle/service/ncm/ncm.cpp21
-rw-r--r--src/core/hle/service/nfc/nfc.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp.cpp31
-rw-r--r--src/core/hle/service/nifm/nifm.cpp28
-rw-r--r--src/core/hle/service/nim/nim.cpp158
-rw-r--r--src/core/hle/service/npns/npns.cpp3
-rw-r--r--src/core/hle/service/ns/ns.cpp80
-rw-r--r--src/core/hle/service/ns/ns.h34
-rw-r--r--src/core/hle/service/ns/pl_u.cpp27
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h39
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp21
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp327
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h168
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp120
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h113
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp200
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h76
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp294
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h169
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp72
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h32
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp229
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h196
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp42
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h18
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp64
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h37
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp142
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h75
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp210
-rw-r--r--src/core/hle/service/nvdrv/interface.h10
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h86
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp119
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h39
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.cpp8
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h4
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.cpp39
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.h85
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp43
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h11
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp79
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h43
-rw-r--r--src/core/hle/service/olsc/olsc.cpp69
-rw-r--r--src/core/hle/service/olsc/olsc.h16
-rw-r--r--src/core/hle/service/pcie/pcie.cpp3
-rw-r--r--src/core/hle/service/pctl/module.cpp2
-rw-r--r--src/core/hle/service/pcv/pcv.cpp3
-rw-r--r--src/core/hle/service/pm/pm.cpp35
-rw-r--r--src/core/hle/service/prepo/prepo.cpp25
-rw-r--r--src/core/hle/service/psc/psc.cpp2
-rw-r--r--src/core/hle/service/ptm/psm.cpp22
-rw-r--r--src/core/hle/service/service.cpp15
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/set/set.cpp80
-rw-r--r--src/core/hle/service/set/set.h2
-rw-r--r--src/core/hle/service/set/set_cal.cpp2
-rw-r--r--src/core/hle/service/set/set_sys.cpp14
-rw-r--r--src/core/hle/service/sm/controller.cpp21
-rw-r--r--src/core/hle/service/sm/controller.h6
-rw-r--r--src/core/hle/service/sm/sm.cpp27
-rw-r--r--src/core/hle/service/sm/sm.h12
-rw-r--r--src/core/hle/service/sockets/blocking_worker.h161
-rw-r--r--src/core/hle/service/sockets/bsd.cpp811
-rw-r--r--src/core/hle/service/sockets/bsd.h150
-rw-r--r--src/core/hle/service/sockets/nsd.cpp6
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp25
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h2
-rw-r--r--src/core/hle/service/sockets/sockets.cpp6
-rw-r--r--src/core/hle/service/sockets/sockets.h85
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp165
-rw-r--r--src/core/hle/service/sockets/sockets_translate.h48
-rw-r--r--src/core/hle/service/spl/module.cpp2
-rw-r--r--src/core/hle/service/spl/spl.cpp39
-rw-r--r--src/core/hle/service/time/interface.cpp2
-rw-r--r--src/core/hle/service/time/standard_network_system_clock_core.h2
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.cpp5
-rw-r--r--src/core/hle/service/time/steady_clock_core.h1
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.h2
-rw-r--r--src/core/hle/service/time/system_clock_core.cpp2
-rw-r--r--src/core/hle/service/time/system_clock_core.h2
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.cpp5
-rw-r--r--src/core/hle/service/time/time.cpp105
-rw-r--r--src/core/hle/service/time/time.h10
-rw-r--r--src/core/hle/service/time/time_manager.cpp352
-rw-r--r--src/core/hle/service/time/time_manager.h85
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp8
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp27
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h4
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp39
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp4
-rw-r--r--src/core/hle/service/usb/usb.cpp31
-rw-r--r--src/core/hle/service/vi/vi.cpp127
-rw-r--r--src/core/hle/service/vi/vi_u.cpp1
-rw-r--r--src/core/hle/service/wlan/wlan.cpp102
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp55
-rw-r--r--src/core/loader/deconstructed_rom_directory.h6
-rw-r--r--src/core/loader/elf.cpp14
-rw-r--r--src/core/loader/elf.h6
-rw-r--r--src/core/loader/kip.cpp9
-rw-r--r--src/core/loader/kip.h6
-rw-r--r--src/core/loader/loader.cpp81
-rw-r--r--src/core/loader/loader.h16
-rw-r--r--src/core/loader/nax.cpp5
-rw-r--r--src/core/loader/nax.h8
-rw-r--r--src/core/loader/nca.cpp8
-rw-r--r--src/core/loader/nca.h6
-rw-r--r--src/core/loader/nro.cpp35
-rw-r--r--src/core/loader/nro.h8
-rw-r--r--src/core/loader/nso.cpp69
-rw-r--r--src/core/loader/nso.h15
-rw-r--r--src/core/loader/nsp.cpp26
-rw-r--r--src/core/loader/nsp.h11
-rw-r--r--src/core/loader/xci.cpp23
-rw-r--r--src/core/loader/xci.h11
-rw-r--r--src/core/memory.cpp315
-rw-r--r--src/core/memory.h86
-rw-r--r--src/core/memory/cheat_engine.cpp89
-rw-r--r--src/core/memory/cheat_engine.h12
-rw-r--r--src/core/memory/dmnt_cheat_types.h4
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp254
-rw-r--r--src/core/memory/dmnt_cheat_vm.h18
-rw-r--r--src/core/network/network.cpp654
-rw-r--r--src/core/network/network.h87
-rw-r--r--src/core/network/sockets.h94
-rw-r--r--src/core/perf_stats.cpp29
-rw-r--r--src/core/perf_stats.h11
-rw-r--r--src/core/reporter.cpp22
-rw-r--r--src/core/reporter.h1
-rw-r--r--src/core/settings.cpp205
-rw-r--r--src/core/settings.h563
-rw-r--r--src/core/telemetry_session.cpp73
-rw-r--r--src/core/telemetry_session.h23
-rw-r--r--src/core/tools/freezer.cpp51
-rw-r--r--src/core/tools/freezer.h16
-rw-r--r--src/input_common/CMakeLists.txt50
-rwxr-xr-xsrc/input_common/analog_from_button.cpp18
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp509
-rw-r--r--src/input_common/gcadapter/gc_adapter.h167
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp332
-rw-r--r--src/input_common/gcadapter/gc_poller.h78
-rw-r--r--src/input_common/keyboard.cpp7
-rw-r--r--src/input_common/main.cpp257
-rw-r--r--src/input_common/main.h147
-rw-r--r--src/input_common/motion_emu.cpp60
-rw-r--r--src/input_common/motion_from_button.cpp34
-rw-r--r--src/input_common/motion_from_button.h25
-rw-r--r--src/input_common/motion_input.cpp301
-rw-r--r--src/input_common/motion_input.h74
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/input_common/sdl/sdl_impl.cpp772
-rw-r--r--src/input_common/sdl/sdl_impl.h12
-rw-r--r--src/input_common/settings.cpp47
-rw-r--r--src/input_common/settings.h371
-rw-r--r--src/input_common/touch_from_button.cpp51
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp245
-rw-r--r--src/input_common/udp/client.h89
-rw-r--r--src/input_common/udp/protocol.h11
-rw-r--r--src/input_common/udp/udp.cpp175
-rw-r--r--src/input_common/udp/udp.h54
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/fibers.cpp367
-rw-r--r--src/tests/core/arm/arm_test_common.cpp8
-rw-r--r--src/tests/core/core_timing.cpp195
-rw-r--r--src/video_core/CMakeLists.txt104
-rw-r--r--src/video_core/buffer_cache/buffer_block.h27
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h570
-rw-r--r--src/video_core/buffer_cache/map_interval.cpp33
-rw-r--r--src/video_core/buffer_cache/map_interval.h113
-rw-r--r--src/video_core/cdma_pusher.cpp171
-rw-r--r--src/video_core/cdma_pusher.h138
-rw-r--r--src/video_core/command_classes/codecs/codec.cpp115
-rw-r--r--src/video_core/command_classes/codecs/codec.h66
-rw-r--r--src/video_core/command_classes/codecs/h264.cpp293
-rw-r--r--src/video_core/command_classes/codecs/h264.h118
-rw-r--r--src/video_core/command_classes/codecs/vp9.cpp1040
-rw-r--r--src/video_core/command_classes/codecs/vp9.h196
-rw-r--r--src/video_core/command_classes/codecs/vp9_types.h366
-rw-r--r--src/video_core/command_classes/host1x.cpp39
-rw-r--r--src/video_core/command_classes/host1x.h78
-rw-r--r--src/video_core/command_classes/nvdec.cpp52
-rw-r--r--src/video_core/command_classes/nvdec.h39
-rw-r--r--src/video_core/command_classes/nvdec_common.h48
-rw-r--r--src/video_core/command_classes/sync_manager.cpp60
-rw-r--r--src/video_core/command_classes/sync_manager.h64
-rw-r--r--src/video_core/command_classes/vic.cpp180
-rw-r--r--src/video_core/command_classes/vic.h110
-rw-r--r--src/video_core/compatible_formats.cpp155
-rw-r--r--src/video_core/compatible_formats.h34
-rw-r--r--src/video_core/dma_pusher.cpp114
-rw-r--r--src/video_core/dma_pusher.h73
-rw-r--r--src/video_core/engines/const_buffer_engine_interface.h1
-rw-r--r--src/video_core/engines/engine_interface.h22
-rw-r--r--src/video_core/engines/fermi_2d.cpp38
-rw-r--r--src/video_core/engines/fermi_2d.h22
-rw-r--r--src/video_core/engines/kepler_compute.cpp40
-rw-r--r--src/video_core/engines/kepler_compute.h27
-rw-r--r--src/video_core/engines/kepler_memory.cpp18
-rw-r--r--src/video_core/engines/kepler_memory.h9
-rw-r--r--src/video_core/engines/maxwell_3d.cpp347
-rw-r--r--src/video_core/engines/maxwell_3d.h91
-rw-r--r--src/video_core/engines/maxwell_dma.cpp255
-rw-r--r--src/video_core/engines/maxwell_dma.h347
-rw-r--r--src/video_core/engines/shader_bytecode.h229
-rw-r--r--src/video_core/engines/shader_header.h13
-rw-r--r--src/video_core/fence_manager.h164
-rw-r--r--src/video_core/gpu.cpp211
-rw-r--r--src/video_core/gpu.h249
-rw-r--r--src/video_core/gpu_asynch.cpp43
-rw-r--r--src/video_core/gpu_asynch.h9
-rw-r--r--src/video_core/gpu_synch.cpp30
-rw-r--r--src/video_core/gpu_synch.h9
-rw-r--r--src/video_core/gpu_thread.cpp56
-rw-r--r--src/video_core/gpu_thread.h26
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt36
-rw-r--r--src/video_core/host_shaders/StringShaderHeader.cmake13
-rw-r--r--src/video_core/host_shaders/opengl_present.frag10
-rw-r--r--src/video_core/host_shaders/opengl_present.vert24
-rw-r--r--src/video_core/host_shaders/source_shader.h.in9
-rw-r--r--src/video_core/macro/macro.cpp91
-rw-r--r--src/video_core/macro/macro.h142
-rw-r--r--src/video_core/macro/macro_hle.cpp109
-rw-r--r--src/video_core/macro/macro_hle.h44
-rw-r--r--src/video_core/macro/macro_interpreter.cpp288
-rw-r--r--src/video_core/macro/macro_interpreter.h102
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp620
-rw-r--r--src/video_core/macro/macro_jit_x64.h98
-rw-r--r--src/video_core/macro_interpreter.cpp352
-rw-r--r--src/video_core/macro_interpreter.h109
-rw-r--r--src/video_core/memory_manager.cpp611
-rw-r--r--src/video_core/memory_manager.h208
-rw-r--r--src/video_core/morton.cpp274
-rw-r--r--src/video_core/query_cache.h89
-rw-r--r--src/video_core/rasterizer_accelerated.cpp10
-rw-r--r--src/video_core/rasterizer_accelerated.h6
-rw-r--r--src/video_core/rasterizer_cache.cpp7
-rw-r--r--src/video_core/rasterizer_cache.h197
-rw-r--r--src/video_core/rasterizer_interface.h45
-rw-r--r--src/video_core/renderer_base.cpp6
-rw-r--r--src/video_core/renderer_base.h40
-rw-r--r--src/video_core/renderer_opengl/gl_arb_decompiler.cpp2126
-rw-r--r--src/video_core/renderer_opengl/gl_arb_decompiler.h29
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp87
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h59
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp128
-rw-r--r--src/video_core/renderer_opengl/gl_device.h42
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.cpp73
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h52
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h14
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp864
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h98
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h25
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp459
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h109
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp436
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h39
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp130
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp123
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h59
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h2
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.h28
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp64
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.h25
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp296
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h40
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h127
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp397
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h50
-rw-r--r--src/video_core/renderer_opengl/utils.cpp62
-rw-r--r--src/video_core/renderer_opengl/utils.h43
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp543
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h345
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp347
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h2
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp220
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.h87
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp118
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h28
-rw-r--r--src/video_core/renderer_vulkan/shaders/quad_indexed.comp50
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp760
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h26
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp162
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h56
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.cpp46
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.h34
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp436
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h29
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp140
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.cpp54
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.h17
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp595
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h54
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp101
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h75
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp512
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h31
-rw-r--r--src/video_core/renderer_vulkan/vk_image.cpp38
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp56
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h70
-rw-r--r--src/video_core/renderer_vulkan/vk_memory_manager.cpp26
-rw-r--r--src/video_core/renderer_vulkan/vk_memory_manager.h15
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp353
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h117
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp87
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h24
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp853
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h80
-rw-r--r--src/video_core/renderer_vulkan/vk_renderpass_cache.cpp180
-rw-r--r--src/video_core/renderer_vulkan/vk_renderpass_cache.h59
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.cpp312
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.h196
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.cpp63
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.h43
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp58
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp133
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h71
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp346
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h16
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.cpp17
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp81
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h17
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp76
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h68
-rw-r--r--src/video_core/renderer_vulkan/vk_stream_buffer.cpp111
-rw-r--r--src/video_core/renderer_vulkan/vk_stream_buffer.h16
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp116
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h9
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp364
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h58
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.cpp35
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h41
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp257
-rw-r--r--src/video_core/renderer_vulkan/wrapper.h196
-rw-r--r--src/video_core/shader/ast.h25
-rw-r--r--src/video_core/shader/async_shaders.cpp216
-rw-r--r--src/video_core/shader/async_shaders.h147
-rw-r--r--src/video_core/shader/control_flow.cpp79
-rw-r--r--src/video_core/shader/decode.cpp32
-rw-r--r--src/video_core/shader/decode/arithmetic.cpp3
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp54
-rw-r--r--src/video_core/shader/decode/arithmetic_integer.cpp41
-rw-r--r--src/video_core/shader/decode/arithmetic_integer_immediate.cpp35
-rw-r--r--src/video_core/shader/decode/half_set.cpp88
-rw-r--r--src/video_core/shader/decode/image.cpp96
-rw-r--r--src/video_core/shader/decode/memory.cpp14
-rw-r--r--src/video_core/shader/decode/other.cpp45
-rw-r--r--src/video_core/shader/decode/register_set_predicate.cpp52
-rw-r--r--src/video_core/shader/decode/shift.cpp1
-rw-r--r--src/video_core/shader/decode/texture.cpp290
-rw-r--r--src/video_core/shader/decode/video.cpp19
-rw-r--r--src/video_core/shader/decode/xmad.cpp27
-rw-r--r--src/video_core/shader/memory_util.cpp76
-rw-r--r--src/video_core/shader/memory_util.h43
-rw-r--r--src/video_core/shader/node.h218
-rw-r--r--src/video_core/shader/node_helper.h2
-rw-r--r--src/video_core/shader/registry.cpp70
-rw-r--r--src/video_core/shader/registry.h37
-rw-r--r--src/video_core/shader/shader_ir.cpp120
-rw-r--r--src/video_core/shader/shader_ir.h49
-rw-r--r--src/video_core/shader/track.cpp116
-rw-r--r--src/video_core/shader_cache.h240
-rw-r--r--src/video_core/shader_notify.cpp42
-rw-r--r--src/video_core/shader_notify.h29
-rw-r--r--src/video_core/surface.cpp255
-rw-r--r--src/video_core/surface.h765
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp153
-rw-r--r--src/video_core/texture_cache/surface_base.cpp18
-rw-r--r--src/video_core/texture_cache/surface_base.h35
-rw-r--r--src/video_core/texture_cache/surface_params.cpp155
-rw-r--r--src/video_core/texture_cache/surface_params.h7
-rw-r--r--src/video_core/texture_cache/surface_view.cpp4
-rw-r--r--src/video_core/texture_cache/surface_view.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h500
-rw-r--r--src/video_core/textures/convert.cpp6
-rw-r--r--src/video_core/textures/decoders.cpp258
-rw-r--r--src/video_core/textures/decoders.h56
-rw-r--r--src/video_core/textures/texture.cpp2
-rw-r--r--src/video_core/textures/texture.h49
-rw-r--r--src/video_core/video_core.cpp42
-rw-r--r--src/web_service/CMakeLists.txt8
-rw-r--r--src/web_service/telemetry_json.cpp8
-rw-r--r--src/web_service/telemetry_json.h30
-rw-r--r--src/web_service/verify_login.cpp4
-rw-r--r--src/web_service/web_backend.cpp85
-rw-r--r--src/web_service/web_backend.h20
-rw-r--r--src/web_service/web_result.h25
-rw-r--r--src/yuzu/CMakeLists.txt93
-rw-r--r--src/yuzu/aboutdialog.ui20
-rw-r--r--src/yuzu/applets/controller.cpp637
-rw-r--r--src/yuzu/applets/controller.h144
-rw-r--r--src/yuzu/applets/controller.ui2653
-rw-r--r--src/yuzu/applets/profile_select.cpp13
-rw-r--r--src/yuzu/applets/profile_select.h1
-rw-r--r--src/yuzu/bootmanager.cpp208
-rw-r--r--src/yuzu/bootmanager.h17
-rw-r--r--src/yuzu/compatdb.cpp3
-rw-r--r--src/yuzu/configuration/config.cpp1036
-rw-r--r--src/yuzu/configuration/config.h58
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp134
-rw-r--r--src/yuzu/configuration/configuration_shared.h51
-rw-r--r--src/yuzu/configuration/configure.ui101
-rw-r--r--src/yuzu/configuration/configure_audio.cpp86
-rw-r--r--src/yuzu/configuration/configure_audio.h8
-rw-r--r--src/yuzu/configuration/configure_audio.ui177
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp76
-rw-r--r--src/yuzu/configuration/configure_cpu.h34
-rw-r--r--src/yuzu/configuration/configure_cpu.ui144
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.cpp65
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.h31
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.ui174
-rw-r--r--src/yuzu/configuration/configure_debug.cpp8
-rw-r--r--src/yuzu/configuration/configure_debug.ui77
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp43
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h41
-rw-r--r--src/yuzu/configuration/configure_debug_controller.ui77
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp31
-rw-r--r--src/yuzu/configuration/configure_dialog.h13
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp66
-rw-r--r--src/yuzu/configuration/configure_filesystem.ui121
-rw-r--r--src/yuzu/configuration/configure_general.cpp83
-rw-r--r--src/yuzu/configuration/configure_general.h9
-rw-r--r--src/yuzu/configuration/configure_general.ui14
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp227
-rw-r--r--src/yuzu/configuration/configure_graphics.h10
-rw-r--r--src/yuzu/configuration/configure_graphics.ui289
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp118
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h11
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui157
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp80
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h6
-rw-r--r--src/yuzu/configuration/configure_hotkeys.ui39
-rw-r--r--src/yuzu/configuration/configure_input.cpp312
-rw-r--r--src/yuzu/configuration/configure_input.h39
-rw-r--r--src/yuzu/configuration/configure_input.ui1033
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp171
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h46
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui2688
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp1172
-rw-r--r--src/yuzu/configuration/configure_input_player.h146
-rw-r--r--src/yuzu/configuration/configure_input_player.ui4137
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.h40
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.ui71
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp152
-rw-r--r--src/yuzu/configuration/configure_input_simple.h43
-rw-r--r--src/yuzu/configuration/configure_input_simple.ui97
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp314
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h90
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui317
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp90
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.h15
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.ui272
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp144
-rw-r--r--src/yuzu/configuration/configure_per_game.h51
-rw-r--r--src/yuzu/configuration/configure_per_game.ui330
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp144
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.h53
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.ui38
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp193
-rw-r--r--src/yuzu/configuration/configure_per_general.h53
-rw-r--r--src/yuzu/configuration/configure_per_general.ui276
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp4
-rw-r--r--src/yuzu/configuration/configure_service.cpp6
-rw-r--r--src/yuzu/configuration/configure_system.cpp197
-rw-r--r--src/yuzu/configuration/configure_system.h10
-rw-r--r--src/yuzu/configuration/configure_system.ui703
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp623
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h92
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui221
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h62
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.ui22
-rw-r--r--src/yuzu/configuration/configure_ui.cpp57
-rw-r--r--src/yuzu/configuration/configure_ui.h7
-rw-r--r--src/yuzu/configuration/configure_ui.ui233
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp146
-rw-r--r--src/yuzu/configuration/configure_vibration.h43
-rw-r--r--src/yuzu/configuration/configure_vibration.ui546
-rw-r--r--src/yuzu/configuration/input_profiles.cpp131
-rw-r--r--src/yuzu/configuration/input_profiles.h32
-rw-r--r--src/yuzu/debugger/profiler.cpp6
-rw-r--r--src/yuzu/debugger/wait_tree.cpp103
-rw-r--r--src/yuzu/discord_impl.cpp2
-rw-r--r--src/yuzu/game_list.cpp131
-rw-r--r--src/yuzu/game_list.h42
-rw-r--r--src/yuzu/game_list_p.h48
-rw-r--r--src/yuzu/game_list_worker.cpp73
-rw-r--r--src/yuzu/install_dialog.cpp72
-rw-r--r--src/yuzu/install_dialog.h35
-rw-r--r--src/yuzu/loading_screen.cpp3
-rw-r--r--src/yuzu/main.cpp1297
-rw-r--r--src/yuzu/main.h76
-rw-r--r--src/yuzu/main.ui53
-rw-r--r--src/yuzu/uisettings.cpp3
-rw-r--r--src/yuzu/uisettings.h18
-rw-r--r--src/yuzu/yuzu.rc2
-rw-r--r--src/yuzu_cmd/CMakeLists.txt2
-rw-r--r--src/yuzu_cmd/config.cpp160
-rw-r--r--src/yuzu_cmd/default_ini.h68
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp24
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h15
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp17
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h8
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp11
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h12
-rw-r--r--src/yuzu_cmd/yuzu.cpp30
-rw-r--r--src/yuzu_cmd/yuzu.rc2
-rw-r--r--src/yuzu_tester/CMakeLists.txt2
-rw-r--r--src/yuzu_tester/config.cpp95
-rw-r--r--src/yuzu_tester/default_ini.h38
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp5
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h6
-rw-r--r--src/yuzu_tester/service/yuzutest.cpp2
-rw-r--r--src/yuzu_tester/yuzu.cpp13
-rw-r--r--src/yuzu_tester/yuzu.rc2
1273 files changed, 137161 insertions, 47609 deletions
diff --git a/.ci/scripts/format/exec.sh b/.ci/scripts/format/exec.sh
index 5d6393b38..e9e9d2e17 100644
--- a/.ci/scripts/format/exec.sh
+++ b/.ci/scripts/format/exec.sh
@@ -1,4 +1,7 @@
#!/bin/bash -ex
chmod a+x ./.ci/scripts/format/docker.sh
+# the UID for the container yuzu user is 1027
+sudo chown -R 1027 ./
docker run -v $(pwd):/yuzu yuzuemu/build-environments:linux-clang-format /bin/bash -ex /yuzu/.ci/scripts/format/docker.sh
+sudo chown -R $UID ./
diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index 5ab828d5e..969ab637c 100644
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -7,7 +7,7 @@ if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dis
fi
# Default clang-format points to default 3.5 version one
-CLANG_FORMAT=clang-format-6.0
+CLANG_FORMAT=clang-format-10
$CLANG_FORMAT --version
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index f11878128..277775ef6 100644..100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -5,7 +5,7 @@ cd /yuzu
ccache -s
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_VULKAN=No
+cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON
ninja
diff --git a/.ci/scripts/linux/exec.sh b/.ci/scripts/linux/exec.sh
index 9fafa9208..a7deddeb3 100644
--- a/.ci/scripts/linux/exec.sh
+++ b/.ci/scripts/linux/exec.sh
@@ -2,4 +2,7 @@
mkdir -p "ccache" || true
chmod a+x ./.ci/scripts/linux/docker.sh
+# the UID for the container yuzu user is 1027
+sudo chown -R 1027 ./
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh $1
+sudo chown -R $UID ./
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index beb554b65..adfd636fa 100644..100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -4,22 +4,10 @@ cd /yuzu
ccache -s
-# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
-mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
-chmod +x /bin/uname
-
-# Dirty hack to trick unicorn makefile into believing we have cmd
-echo '' >> /bin/cmd
-chmod +x /bin/cmd
-
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_VULKAN=No
+cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON
ninja
-# Clean up the dirty hacks
-rm /bin/uname && mv /bin/uname1 /bin/uname
-rm /bin/cmd
-
ccache -s
echo "Tests skipped"
@@ -29,7 +17,13 @@ echo 'Prepare binaries...'
cd ..
mkdir package
-QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
+if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then
+ QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
+else
+ #fallback to qt
+ QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/'
+fi
+
find build/ -name "yuzu*.exe" -exec cp {} 'package' \;
# copy Qt plugins
diff --git a/.ci/scripts/windows/exec.sh b/.ci/scripts/windows/exec.sh
index 4155ed5fc..f904544bd 100644
--- a/.ci/scripts/windows/exec.sh
+++ b/.ci/scripts/windows/exec.sh
@@ -2,4 +2,7 @@
mkdir -p "ccache" || true
chmod a+x ./.ci/scripts/windows/docker.sh
+# the UID for the container yuzu user is 1027
+sudo chown -R 1027 ./
docker run -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.ci/scripts/windows/docker.sh $1
+sudo chown -R $UID ./
diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml
index adcecf0ec..0b4bbd341 100644
--- a/.ci/templates/build-msvc.yml
+++ b/.ci/templates/build-msvc.yml
@@ -4,7 +4,11 @@ parameters:
version: ''
steps:
-- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
+- script: choco install vulkan-sdk
+ displayName: 'Install vulkan-sdk'
+- script: python -m pip install --upgrade pip conan
+ displayName: 'Install conan'
+- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'
diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml
index 26e287257..3f338e2a0 100644
--- a/.ci/yuzu-patreon-step2.yml
+++ b/.ci/yuzu-patreon-step2.yml
@@ -5,21 +5,11 @@ variables:
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
stages:
-- stage: format
- displayName: 'format'
- jobs:
- - job: format
- displayName: 'clang'
- continueOnError: true
- pool:
- vmImage: ubuntu-latest
- steps:
- - template: ./templates/format-check.yml
- stage: build
- dependsOn: format
displayName: 'build'
jobs:
- job: build
+ timeoutInMinutes: 120
displayName: 'windows-msvc'
pool:
vmImage: windows-2019
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 70e1bba67..000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
-
-Please read the FAQ:
-https://yuzu-emu.org/wiki/faq/
-
-THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
-https://community.citra-emu.org/
-
-If the FAQ does not answer your question, please go to:
-https://community.citra-emu.org/
-
-When submitting an issue, please check the following:
-
-- You have read the above.
-- You have provided the version (commit hash) of yuzu you are using.
-- You have provided sufficient detail for the issue to be reproduced.
-- You have provided system specs (if relevant).
-- Please also provide:
- - For any issues, a log file
- - For crashes, a backtrace.
- - For graphical issues, comparison screenshots with real hardware.
- - For emulation inaccuracies, a test-case (if able).
-
--->
-
-
-
-
-
-
diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md
new file mode 100644
index 000000000..5706243bb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md
@@ -0,0 +1,40 @@
+---
+name: Bug Report / Feature Request
+about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+<!---
+Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
+
+Please read the FAQ:
+https://yuzu-emu.org/wiki/faq/
+
+THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
+https://community.citra-emu.org/
+
+If the FAQ does not answer your question, please go to:
+https://community.citra-emu.org/
+
+When submitting an issue, please check the following:
+
+- You have read the above.
+- You have provided the version (commit hash) of yuzu you are using.
+- You have provided sufficient detail for the issue to be reproduced.
+- You have provided system specs (if relevant).
+- Please also provide:
+ - For any issues, a log file
+ - For crashes, a backtrace.
+ - For graphical issues, comparison screenshots with real hardware.
+ - For emulation inaccuracies, a test-case (if able).
+
+-->
+
+
+
+
+
+
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..52faafad3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: yuzu Discord
+ url: https://discord.com/invite/u77vRWY
+ about: If you are experiencing an issue with yuzu, and you need tech support, or if you have a general question, try asking in the official yuzu Discord linked here. Piracy is not allowed.
+ - name: Community forums
+ url: https://community.citra-emu.org
+ about: This is an alternative place for tech support, however helpers there are not as active.
diff --git a/.gitmodules b/.gitmodules
index 63bf2cda0..41022615b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,54 +1,39 @@
[submodule "inih"]
path = externals/inih/inih
- url = https://github.com/svn2github/inih
-[submodule "boost"]
- path = externals/boost
- url = https://github.com/yuzu-emu/ext-boost.git
-[submodule "catch"]
- path = externals/catch
- url = https://github.com/philsquared/Catch.git
+ url = https://github.com/benhoyt/inih.git
[submodule "cubeb"]
path = externals/cubeb
url = https://github.com/kinetiknz/cubeb.git
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git
-[submodule "fmt"]
- path = externals/fmt
- url = https://github.com/fmtlib/fmt.git
-[submodule "lz4"]
- path = externals/lz4
- url = https://github.com/lz4/lz4.git
-[submodule "unicorn"]
- path = externals/unicorn
- url = https://github.com/yuzu-emu/unicorn
-[submodule "mbedtls"]
- path = externals/mbedtls
- url = https://github.com/DarkLordZach/mbedtls
-[submodule "opus"]
- path = externals/opus/opus
- url = https://github.com/xiph/opus.git
[submodule "soundtouch"]
path = externals/soundtouch
url = https://github.com/citra-emu/ext-soundtouch.git
[submodule "libressl"]
path = externals/libressl
url = https://github.com/citra-emu/ext-libressl-portable.git
+[submodule "libusb"]
+ path = externals/libusb/libusb
+ url = https://github.com/libusb/libusb.git
[submodule "discord-rpc"]
path = externals/discord-rpc
url = https://github.com/discordapp/discord-rpc.git
[submodule "Vulkan-Headers"]
path = externals/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git
-[submodule "externals/zstd"]
- path = externals/zstd
- url = https://github.com/facebook/zstd
[submodule "sirit"]
path = externals/sirit
url = https://github.com/ReinUsesLisp/sirit
+[submodule "mbedtls"]
+ path = externals/mbedtls
+ url = https://github.com/DarkLordZach/mbedtls
[submodule "libzip"]
path = externals/libzip/libzip
url = https://github.com/nih-at/libzip.git
-[submodule "zlib"]
- path = externals/zlib/zlib
- url = https://github.com/madler/zlib.git
+[submodule "xbyak"]
+ path = externals/xbyak
+ url = https://github.com/herumi/xbyak.git
+[submodule "opus"]
+ path = externals/opus/opus
+ url = https://github.com/xiph/opus.git
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100644
index 000000000..5751d0e4c
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,10 @@
+path_classifiers:
+ library: "externals"
+extraction:
+ cpp:
+ prepare:
+ packages:
+ - "libsdl2-dev"
+ - "qtmultimedia5-dev"
+ - "libtbb-dev"
+ - "libjack-jackd2-dev"
diff --git a/.travis/clang-format/script.sh b/.travis/clang-format/script.sh
index 3eff6322f..56a785fe0 100755
--- a/.travis/clang-format/script.sh
+++ b/.travis/clang-format/script.sh
@@ -7,7 +7,7 @@ if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis*
fi
# Default clang-format points to default 3.5 version one
-CLANG_FORMAT=clang-format-6.0
+CLANG_FORMAT=clang-format-10.0
$CLANG_FORMAT --version
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
diff --git a/.travis/linux-mingw/docker.sh b/.travis/linux-mingw/docker.sh
index 28033acfb..80d7dfe9b 100755
--- a/.travis/linux-mingw/docker.sh
+++ b/.travis/linux-mingw/docker.sh
@@ -4,16 +4,8 @@ cd /yuzu
# override Travis CI unreasonable ccache size
echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf"
-# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
-mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
-chmod +x /bin/uname
-
-# Dirty hack to trick unicorn makefile into believing we have cmd
-echo '' >> /bin/cmd
-chmod +x /bin/cmd
-
mkdir build && cd build
-cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
+cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
ninja
# Clean up the dirty hacks
diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh
index 3a9970384..166fb6d4c 100755
--- a/.travis/linux/docker.sh
+++ b/.travis/linux/docker.sh
@@ -3,7 +3,7 @@
cd /yuzu
mkdir build && cd build
-cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
+cmake .. -G Ninja -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
ninja
ccache -s
diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh
index 0abd1a93a..db1c7cae7 100755
--- a/.travis/macos/build.sh
+++ b/.travis/macos/build.sh
@@ -4,13 +4,12 @@ set -o pipefail
export MACOSX_DEPLOYMENT_TARGET=10.14
export Qt5_DIR=$(brew --prefix)/opt/qt5
-export UNICORNDIR=$(pwd)/externals/unicorn
export PATH="/usr/local/opt/ccache/libexec:$PATH"
# TODO: Build using ninja instead of make
mkdir build && cd build
cmake --version
-cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
+cmake .. -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
make -j4
ccache -s
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 467d769a2..273260378 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,8 @@
-cmake_minimum_required(VERSION 3.7)
+cmake_minimum_required(VERSION 3.15)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/find-modules")
include(DownloadExternals)
include(CMakeDependentOption)
@@ -10,15 +11,13 @@ project(yuzu)
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
-CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF)
option(ENABLE_QT "Enable the Qt frontend" ON)
+option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
-option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
-
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON)
@@ -29,6 +28,13 @@ option(ENABLE_VULKAN "Enables Vulkan backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
+# Default to a Release build
+get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+ message(STATUS "Defaulting to a Release build")
+endif()
+
if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@@ -53,7 +59,6 @@ endfunction()
if(EXISTS ${PROJECT_SOURCE_DIR}/.gitmodules)
check_submodules_present()
endif()
-
configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
COPYONLY)
@@ -112,141 +117,98 @@ message(STATUS "Target architecture: ${ARCHITECTURE}")
# Configure C++ standard
# ===========================
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-# System imported libraries
-# ======================
-
-find_package(Boost 1.66.0 QUIET)
-if (NOT Boost_FOUND)
- message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
+# boost asio's concept usage doesn't play nicely with some compilers yet.
+add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
+if (MSVC)
+ add_compile_options(/std:c++latest)
- set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
- set(Boost_NO_SYSTEM_PATHS OFF)
- find_package(Boost QUIET REQUIRED)
+ # cubeb and boost still make use of deprecated result_of.
+ add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
+else()
+ set(CMAKE_CXX_STANDARD 20)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
# Output binaries to bin/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
-# Prefer the -pthread flag on Linux.
-set(THREADS_PREFER_PTHREAD_FLAG ON)
-find_package(Threads REQUIRED)
-
-if (ENABLE_SDL2)
- if (YUZU_USE_BUNDLED_SDL2)
- # Detect toolchain and platform
- if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
- set(SDL2_VER "SDL2-2.0.8")
- else()
- message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
- endif()
-
- if (DEFINED SDL2_VER)
- download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX)
- endif()
-
- set(SDL2_FOUND YES)
- set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
- set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
- set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
-
- add_library(SDL2 INTERFACE)
- target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}")
- target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}")
+# System imported libraries
+# If not found, download any missing through Conan
+# =======================================================================
+set(CONAN_CMAKE_SILENT_OUTPUT TRUE)
+set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
+if (YUZU_CONAN_INSTALLED)
+ if (IS_MULTI_CONFIG)
+ include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
else()
- find_package(SDL2 REQUIRED)
-
- # Some installations don't set SDL2_LIBRARIES
- if("${SDL2_LIBRARIES}" STREQUAL "")
- message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
- set(SDL2_LIBRARIES "SDL2::SDL2")
- endif()
-
- include_directories(${SDL2_INCLUDE_DIRS})
- add_library(SDL2 INTERFACE)
- target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
+ include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
endif()
-else()
- set(SDL2_FOUND NO)
+ list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
+ list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
+ conan_basic_setup()
+ message(STATUS "Adding conan installed libraries to the search path")
endif()
-# If unicorn isn't found, msvc -> download bundled unicorn; everyone else -> build external
-if (YUZU_USE_BUNDLED_UNICORN)
- if (MSVC)
- message(STATUS "unicorn not found, falling back to bundled")
- # Detect toolchain and platform
- if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
- set(UNICORN_VER "unicorn-yuzu")
- else()
- message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.")
- endif()
-
- if (DEFINED UNICORN_VER)
- download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
- endif()
-
- if (DEFINED UNICORN_VER)
- download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
+macro(yuzu_find_packages)
+ set(options FORCE_REQUIRED)
+ cmake_parse_arguments(FN "${options}" "" "" ${ARGN})
+
+ # Cmake has a *serious* lack of 2D array or associative array...
+ # Capitalization matters here. We need the naming to match the generated paths from Conan
+ set(REQUIRED_LIBS
+ # Cmake Pkg Prefix Version Conan Pkg
+ "Boost 1.73 boost/1.73.0"
+ "Catch2 2.13 catch2/2.13.0"
+ "fmt 7.1 fmt/7.1.2"
+ # can't use until https://github.com/bincrafters/community/issues/1173
+ #"libzip 1.5 libzip/1.5.2@bincrafters/stable"
+ "lz4 1.8 lz4/1.9.2"
+ "nlohmann_json 3.8 nlohmann_json/3.8.0"
+ "ZLIB 1.2 zlib/1.2.11"
+ "zstd 1.4 zstd/1.4.5"
+ )
+
+ foreach(PACKAGE ${REQUIRED_LIBS})
+ string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE})
+ list(GET PACKAGE_SPLIT 0 PACKAGE_PREFIX)
+ list(GET PACKAGE_SPLIT 1 PACKAGE_VERSION)
+ list(GET PACKAGE_SPLIT 2 PACKAGE_CONAN)
+ # This function is called twice, once to check if the packages exist on the system already
+ # and a second time to check if conan installed them properly. The second check passes in FORCE_REQUIRED
+ if (NOT ${PACKAGE_PREFIX}_FOUND)
+ if (FN_FORCE_REQUIRED)
+ find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION} REQUIRED)
+ else()
+ find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION})
+ endif()
endif()
-
- set(UNICORN_FOUND YES)
- set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
- set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/lib/x64/unicorn_dynload.lib" CACHE PATH "Path to Unicorn library" FORCE)
- set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/lib/x64/" CACHE PATH "Path to unicorn.dll" FORCE)
- else()
- message(STATUS "unicorn not found, falling back to externals")
- if (MINGW)
- set(UNICORN_LIB_NAME "unicorn.a")
+ if (NOT ${PACKAGE_PREFIX}_FOUND)
+ list(APPEND CONAN_REQUIRED_LIBS ${PACKAGE_CONAN})
else()
- set(UNICORN_LIB_NAME "libunicorn.a")
+ # Set a legacy findPackage.cmake style PACKAGE_LIBRARIES variable for subprojects that rely on this
+ set(${PACKAGE_PREFIX}_LIBRARIES "${PACKAGE_PREFIX}::${PACKAGE_PREFIX}")
endif()
+ endforeach()
+ unset(FN_FORCE_REQUIRED)
+endmacro()
- set(UNICORN_FOUND YES)
- set(UNICORN_PREFIX ${PROJECT_SOURCE_DIR}/externals/unicorn)
- set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/${UNICORN_LIB_NAME}" CACHE PATH "Path to Unicorn library" FORCE)
- set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
- set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/" CACHE PATH "Path to unicorn dynamic library" FORCE)
-
- find_package(PythonInterp 2.7 REQUIRED)
-
- if (MINGW)
- add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
- COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh cross-win64
- WORKING_DIRECTORY ${UNICORN_PREFIX}
- )
- else()
- add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
- COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh macos-universal-no
- WORKING_DIRECTORY ${UNICORN_PREFIX}
- )
- endif()
+# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
+yuzu_find_packages()
- # ALL makes this custom target build every time
- # but it won't actually build if LIBUNICORN_LIBRARY is up to date
- add_custom_target(unicorn-build ALL
- DEPENDS ${LIBUNICORN_LIBRARY}
- )
- unset(UNICORN_LIB_NAME)
+# Qt5 requires that we find components, so it doesn't fit our pretty little find package function
+if(ENABLE_QT)
+ # We want to load the generated conan qt config so that we get the QT_ROOT var so that we can use the official
+ # Qt5Config inside the root folder instead of the conan generated one.
+ if(EXISTS ${CMAKE_BINARY_DIR}/qtConfig.cmake)
+ include(${CMAKE_BINARY_DIR}/qtConfig.cmake)
+ list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
+ list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
endif()
-else()
- find_package(Unicorn REQUIRED)
-endif()
-
-if (UNICORN_FOUND)
- add_library(unicorn INTERFACE)
- add_dependencies(unicorn unicorn-build)
- target_link_libraries(unicorn INTERFACE "${LIBUNICORN_LIBRARY}")
- target_include_directories(unicorn INTERFACE "${LIBUNICORN_INCLUDE_DIR}")
-else()
- message(FATAL_ERROR "Could not find or build unicorn which is required.")
-endif()
-
-if (ENABLE_QT)
- if (YUZU_USE_BUNDLED_QT)
+ # Workaround for an issue where conan tries to build Qt from scratch instead of download prebuilt binaries
+ set(QT_PREFIX_HINT)
+ if(YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
- set(QT_VER qt-5.12.0-msvc2017_64)
+ set(QT_VER qt-5.12.8-msvc2017_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
@@ -256,19 +218,158 @@ if (ENABLE_QT)
endif()
set(QT_PREFIX_HINT HINTS "${QT_PREFIX}")
+ endif()
+ find_package(Qt5 5.9 COMPONENTS Widgets ${QT_PREFIX_HINT})
+ if (YUZU_USE_QT_WEB_ENGINE)
+ find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
+ endif()
+
+ if (ENABLE_QT_TRANSLATION)
+ find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT})
+ endif()
+ if (NOT Qt5_FOUND)
+ list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable")
+ endif()
+endif()
+# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the yuzu_find_package
+if(ENABLE_SDL2)
+ if(EXISTS ${CMAKE_BINARY_DIR}/sdl2Config.cmake)
+ include(${CMAKE_BINARY_DIR}/sdl2Config.cmake)
+ list(APPEND CMAKE_MODULE_PATH "${CONAN_SDL2_ROOT_RELEASE}")
+ list(APPEND CMAKE_PREFIX_PATH "${CONAN_SDL2_ROOT_RELEASE}")
+ endif()
+ find_package(SDL2)
+ if (NOT SDL2_FOUND)
+ # otherwise add this to the list of libraries to install
+ list(APPEND CONAN_REQUIRED_LIBS "sdl2/2.0.12@bincrafters/stable")
+ endif()
+endif()
+
+# Install any missing dependencies with conan install
+if (CONAN_REQUIRED_LIBS)
+ message(STATUS "Packages ${CONAN_REQUIRED_LIBS} not found!")
+ # Use Conan to fetch the libraries that aren't found
+ # Download conan.cmake automatically, you can also just copy the conan.cmake file
+ if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
+ message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
+ file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake"
+ "${CMAKE_BINARY_DIR}/conan.cmake")
+ endif()
+ include(${CMAKE_BINARY_DIR}/conan.cmake)
+
+ set(CONAN_LIB_OPTIONS
+ libzip:with_openssl=False
+ libzip:enable_windows_crypto=False
+ )
+
+ conan_check(VERSION 1.24.0 REQUIRED)
+ # Add the bincrafters remote
+ conan_add_remote(NAME bincrafters
+ URL https://api.bintray.com/conan/bincrafters/public-conan)
+
+ # Manually add iconv to fix a dep conflict between qt and sdl2
+ # We don't need to add it through find_package or anything since the other two can find it just fine
+ if ("${CONAN_REQUIRED_LIBS}" MATCHES "qt" AND "${CONAN_REQUIRED_LIBS}" MATCHES "sdl")
+ list(APPEND CONAN_REQUIRED_LIBS "libiconv/1.16")
+ endif()
+ if (IS_MULTI_CONFIG)
+ conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
+ OPTIONS ${CONAN_LIB_OPTIONS}
+ BUILD missing
+ CONFIGURATION_TYPES "Release;Debug"
+ GENERATORS cmake_multi cmake_find_package_multi)
+ include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
else()
- # Passing an empty HINTS seems to cause default system paths to get ignored in CMake 2.8 so
- # make sure to not pass anything if we don't have one.
- set(QT_PREFIX_HINT)
+ conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
+ OPTIONS ${CONAN_LIB_OPTIONS}
+ BUILD missing
+ GENERATORS cmake cmake_find_package_multi)
+ include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+ endif()
+ list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
+ list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
+ conan_basic_setup()
+
+ set(YUZU_CONAN_INSTALLED TRUE CACHE BOOL "If true, the following builds will add conan to the lib search path" FORCE)
+
+ # Now that we've installed what we are missing, try to locate them again,
+ # this time with required, so we bail if its not found.
+ yuzu_find_packages(FORCE_REQUIRED)
+
+ # Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
+ if(ENABLE_QT)
+ list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
+ list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
+ find_package(Qt5 5.9 REQUIRED COMPONENTS Widgets)
+ if (YUZU_USE_QT_WEB_ENGINE)
+ find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets)
+ endif()
+ endif()
+ if(ENABLE_SDL2)
+ list(APPEND CMAKE_MODULE_PATH "${CONAN_SDL2_ROOT_RELEASE}")
+ list(APPEND CMAKE_PREFIX_PATH "${CONAN_SDL2_ROOT_RELEASE}")
+ find_package(SDL2 REQUIRED)
endif()
- find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
+endif()
- if (YUZU_USE_QT_WEB_ENGINE)
- find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets ${QT_PREFIX_HINT})
- endif ()
+# Reexport some targets that are named differently when using the upstream CmakeConfig vs the generated Conan config
+# In order to ALIAS targets to a new name, they first need to be IMPORTED_GLOBAL
+# Dynarmic checks for target `boost` and so we want to make sure it can find it through our system instead of using their external
+if (TARGET Boost::Boost)
+ set_target_properties(Boost::Boost PROPERTIES IMPORTED_GLOBAL TRUE)
+ add_library(Boost::boost ALIAS Boost::Boost)
+ add_library(boost ALIAS Boost::Boost)
+elseif (TARGET Boost::boost)
+ set_target_properties(Boost::boost PROPERTIES IMPORTED_GLOBAL TRUE)
+ add_library(boost ALIAS Boost::boost)
+endif()
+
+if (TARGET sdl2::sdl2)
+ # imported from the conan generated sdl2Config.cmake
+ set_target_properties(sdl2::sdl2 PROPERTIES IMPORTED_GLOBAL TRUE)
+ add_library(SDL2 ALIAS sdl2::sdl2)
+elseif(SDL2_FOUND)
+ # found through the system package manager
+ # Some installations don't set SDL2_LIBRARIES
+ if("${SDL2_LIBRARIES}" STREQUAL "")
+ message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
+ set(SDL2_LIBRARIES "SDL2::SDL2")
+ endif()
+
+ include_directories(SYSTEM ${SDL2_INCLUDE_DIRS})
+ add_library(SDL2 INTERFACE)
+ target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
+endif()
+
+# Ensure libusb is properly configured (based on dolphin libusb include)
+if(NOT APPLE)
+ include(FindPkgConfig)
+ find_package(LibUSB)
+endif()
+if (NOT LIBUSB_FOUND)
+ add_subdirectory(externals/libusb)
+ set(LIBUSB_INCLUDE_DIR "")
+ set(LIBUSB_LIBRARIES usb)
+endif()
+
+# Use system installed ffmpeg.
+if (NOT MSVC)
+ find_package(FFmpeg REQUIRED)
+else()
+ set(FFMPEG_EXT_NAME "ffmpeg-4.2.1")
+ set(FFMPEG_PATH "${CMAKE_BINARY_DIR}/externals/${FFMPEG_EXT_NAME}")
+ download_bundled_external("ffmpeg/" ${FFMPEG_EXT_NAME} "")
+ set(FFMPEG_FOUND YES)
+ set(FFMPEG_INCLUDE_DIR "${FFMPEG_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE)
+ set(FFMPEG_LIBRARY_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg library" FORCE)
+ set(FFMPEG_DLL_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE)
endif()
+# Prefer the -pthread flag on Linux.
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
+
# Platform-specific library requirements
# ======================================
@@ -292,14 +393,14 @@ endif()
# against all the src files. This should be used before making a pull request.
# =======================================================================
-set(CLANG_FORMAT_POSTFIX "-6.0")
+set(CLANG_FORMAT_POSTFIX "-10")
find_program(CLANG_FORMAT
NAMES clang-format${CLANG_FORMAT_POSTFIX}
clang-format
PATHS ${PROJECT_BINARY_DIR}/externals)
# if find_program doesn't find it, try to download from externals
if (NOT CLANG_FORMAT)
- if (WIN32)
+ if (WIN32 AND NOT CMAKE_CROSSCOMPILING)
message(STATUS "Clang format not found! Downloading...")
set(CLANG_FORMAT "${PROJECT_BINARY_DIR}/externals/clang-format${CLANG_FORMAT_POSTFIX}.exe")
file(DOWNLOAD
diff --git a/CMakeModules/CopyYuzuFFmpegDeps.cmake b/CMakeModules/CopyYuzuFFmpegDeps.cmake
new file mode 100644
index 000000000..cca1eeeab
--- /dev/null
+++ b/CMakeModules/CopyYuzuFFmpegDeps.cmake
@@ -0,0 +1,10 @@
+function(copy_yuzu_FFmpeg_deps target_dir)
+ include(WindowsCopyFiles)
+ set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
+ windows_copy_files(${target_dir} ${FFMPEG_DLL_DIR} ${DLL_DEST}
+ avcodec-58.dll
+ avutil-56.dll
+ swresample-3.dll
+ swscale-5.dll
+ )
+endfunction(copy_yuzu_FFmpeg_deps)
diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake
index 2598b9b60..59343b1ca 100644
--- a/CMakeModules/CopyYuzuQt5Deps.cmake
+++ b/CMakeModules/CopyYuzuQt5Deps.cmake
@@ -15,7 +15,6 @@ function(copy_yuzu_Qt5_deps target_dir)
icuuc*.dll
Qt5Core$<$<CONFIG:Debug>:d>.*
Qt5Gui$<$<CONFIG:Debug>:d>.*
- Qt5OpenGL$<$<CONFIG:Debug>:d>.*
Qt5Widgets$<$<CONFIG:Debug>:d>.*
)
diff --git a/CMakeModules/CopyYuzuUnicornDeps.cmake b/CMakeModules/CopyYuzuUnicornDeps.cmake
deleted file mode 100644
index 7af0ef023..000000000
--- a/CMakeModules/CopyYuzuUnicornDeps.cmake
+++ /dev/null
@@ -1,9 +0,0 @@
-function(copy_yuzu_unicorn_deps target_dir)
- include(WindowsCopyFiles)
- set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
- windows_copy_files(${target_dir} ${UNICORN_DLL_DIR} ${DLL_DEST}
- libgcc_s_seh-1.dll
- libwinpthread-1.dll
- unicorn.dll
- )
-endfunction(copy_yuzu_unicorn_deps)
diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake
index 83e4e9df2..311ba1c2e 100644
--- a/CMakeModules/GenerateSCMRev.cmake
+++ b/CMakeModules/GenerateSCMRev.cmake
@@ -51,6 +51,8 @@ endif()
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
set(HASH_FILES
+ "${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.cpp"
+ "${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.h"
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
diff --git a/CMakeModules/MinGWCross.cmake b/CMakeModules/MinGWCross.cmake
index 29ecd1ac4..b268e72d8 100644
--- a/CMakeModules/MinGWCross.cmake
+++ b/CMakeModules/MinGWCross.cmake
@@ -10,8 +10,8 @@ set(SDL2_PATH ${MINGW_PREFIX})
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
# Specify the cross compiler
-set(CMAKE_C_COMPILER ${MINGW_TOOL_PREFIX}gcc-posix)
-set(CMAKE_CXX_COMPILER ${MINGW_TOOL_PREFIX}g++-posix)
+set(CMAKE_C_COMPILER ${MINGW_TOOL_PREFIX}gcc)
+set(CMAKE_CXX_COMPILER ${MINGW_TOOL_PREFIX}g++)
set(CMAKE_RC_COMPILER ${MINGW_TOOL_PREFIX}windres)
# Mingw tools
diff --git a/README.md b/README.md
index 1e9f67e08..981c8ef24 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ yuzu emulator
=============
[![Travis CI Build Status](https://travis-ci.com/yuzu-emu/yuzu.svg?branch=master)](https://travis-ci.com/yuzu-emu/yuzu)
[![Azure Mainline CI Build Status](https://dev.azure.com/yuzu-emu/yuzu/_apis/build/status/yuzu%20mainline?branchName=master)](https://dev.azure.com/yuzu-emu/yuzu/)
-[![Discord](https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white)](https://discord.gg/XQV6dn9)
+[![Discord](https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white)](https://discord.com/invite/u77vRWY)
yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/).
@@ -16,7 +16,7 @@ yuzu is licensed under the GPLv2 (or any later version). Refer to the license.tx
Check out our [website](https://yuzu-emu.org/)!
-For development discussion, please join us on [Discord](https://discord.gg/XQV6dn9).
+For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY).
### Development
@@ -24,6 +24,8 @@ Most of the development happens on GitHub. It's also where [our central reposito
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/yuzu-emu/yuzu/wiki/Contributing) and [Developer Information](https://github.com/yuzu-emu/yuzu/wiki/Developer-Information). You should also contact any of the developers on Discord in order to know about the current state of the emulator.
+If you want to contribute to the user interface translation, please check out the [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu). We centralize translation work there, and periodically upstream translations.
+
### Building
* __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows)
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
new file mode 100644
index 000000000..1c4e960c0
--- /dev/null
+++ b/dist/icons/controller/controller.qrc
@@ -0,0 +1,55 @@
+<RCC>
+ <qresource prefix="controller">
+ <file alias="dual_joycon">dual_joycon.png</file>
+ <file alias="dual_joycon_dark">dual_joycon_dark.png</file>
+ <file alias="dual_joycon_midnight">dual_joycon_midnight.png</file>
+ <file alias="handheld">handheld.png</file>
+ <file alias="handheld_dark">handheld_dark.png</file>
+ <file alias="handheld_midnight">handheld_midnight.png</file>
+ <file alias="pro_controller">pro_controller.png</file>
+ <file alias="pro_controller_dark">pro_controller_dark.png</file>
+ <file alias="pro_controller_midnight">pro_controller_midnight.png</file>
+ <file alias="single_joycon_left">single_joycon_left.png</file>
+ <file alias="single_joycon_left_dark">single_joycon_left_dark.png</file>
+ <file alias="single_joycon_left_midnight">single_joycon_left_midnight.png</file>
+ <file alias="single_joycon_right">single_joycon_right.png</file>
+ <file alias="single_joycon_right_dark">single_joycon_right_dark.png</file>
+ <file alias="single_joycon_right_midnight">single_joycon_right_midnight.png</file>
+ <file alias="single_joycon_left_vertical">single_joycon_left_vertical.png</file>
+ <file alias="single_joycon_left_vertical_dark">single_joycon_left_vertical_dark.png</file>
+ <file alias="single_joycon_left_vertical_midnight">single_joycon_left_vertical_midnight.png</file>
+ <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file>
+ <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file>
+ <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file>
+ <file alias="applet_dual_joycon">applet_dual_joycon.png</file>
+ <file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file>
+ <file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file>
+ <file alias="applet_handheld">applet_handheld.png</file>
+ <file alias="applet_handheld_dark">applet_handheld_dark.png</file>
+ <file alias="applet_handheld_midnight">applet_handheld_midnight.png</file>
+ <file alias="applet_pro_controller">applet_pro_controller.png</file>
+ <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file>
+ <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file>
+ <file alias="applet_joycon_left">applet_single_joycon_left.png</file>
+ <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file>
+ <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file>
+ <file alias="applet_joycon_right">applet_single_joycon_right.png</file>
+ <file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file>
+ <file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file>
+ <file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file>
+ <file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file>
+ <file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file>
+ <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file>
+ <file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file>
+ <file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file>
+ <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file>
+ <file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file>
+ <file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file>
+ <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file>
+ <file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file>
+ <file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file>
+ <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file>
+ <file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file>
+ <file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file>
+ </qresource>
+</RCC>
diff --git a/dist/icons/controller/dual_joycon.png b/dist/icons/controller/dual_joycon.png
new file mode 100644
index 000000000..4230f5f7b
--- /dev/null
+++ b/dist/icons/controller/dual_joycon.png
Binary files differ
diff --git a/dist/icons/controller/dual_joycon_dark.png b/dist/icons/controller/dual_joycon_dark.png
new file mode 100644
index 000000000..4445db489
--- /dev/null
+++ b/dist/icons/controller/dual_joycon_dark.png
Binary files differ
diff --git a/dist/icons/controller/dual_joycon_midnight.png b/dist/icons/controller/dual_joycon_midnight.png
new file mode 100644
index 000000000..aac8e5321
--- /dev/null
+++ b/dist/icons/controller/dual_joycon_midnight.png
Binary files differ
diff --git a/dist/icons/controller/handheld.png b/dist/icons/controller/handheld.png
new file mode 100644
index 000000000..d009b4a47
--- /dev/null
+++ b/dist/icons/controller/handheld.png
Binary files differ
diff --git a/dist/icons/controller/handheld_dark.png b/dist/icons/controller/handheld_dark.png
new file mode 100644
index 000000000..c80ca9259
--- /dev/null
+++ b/dist/icons/controller/handheld_dark.png
Binary files differ
diff --git a/dist/icons/controller/handheld_midnight.png b/dist/icons/controller/handheld_midnight.png
new file mode 100644
index 000000000..19de4629b
--- /dev/null
+++ b/dist/icons/controller/handheld_midnight.png
Binary files differ
diff --git a/dist/icons/controller/pro_controller.png b/dist/icons/controller/pro_controller.png
new file mode 100644
index 000000000..07d65e94a
--- /dev/null
+++ b/dist/icons/controller/pro_controller.png
Binary files differ
diff --git a/dist/icons/controller/pro_controller_dark.png b/dist/icons/controller/pro_controller_dark.png
new file mode 100644
index 000000000..73efe18f4
--- /dev/null
+++ b/dist/icons/controller/pro_controller_dark.png
Binary files differ
diff --git a/dist/icons/controller/pro_controller_midnight.png b/dist/icons/controller/pro_controller_midnight.png
new file mode 100644
index 000000000..8d7e63f0d
--- /dev/null
+++ b/dist/icons/controller/pro_controller_midnight.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left.png b/dist/icons/controller/single_joycon_left.png
new file mode 100644
index 000000000..547153034
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_dark.png b/dist/icons/controller/single_joycon_left_dark.png
new file mode 100644
index 000000000..b6ee073cb
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left_dark.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_midnight.png b/dist/icons/controller/single_joycon_left_midnight.png
new file mode 100644
index 000000000..34a485c81
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left_midnight.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical.png b/dist/icons/controller/single_joycon_left_vertical.png
new file mode 100644
index 000000000..1e6282ad8
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left_vertical.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical_dark.png b/dist/icons/controller/single_joycon_left_vertical_dark.png
new file mode 100644
index 000000000..a615d995d
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left_vertical_dark.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical_midnight.png b/dist/icons/controller/single_joycon_left_vertical_midnight.png
new file mode 100644
index 000000000..4cc578216
--- /dev/null
+++ b/dist/icons/controller/single_joycon_left_vertical_midnight.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right.png b/dist/icons/controller/single_joycon_right.png
new file mode 100644
index 000000000..8d29173f6
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_dark.png b/dist/icons/controller/single_joycon_right_dark.png
new file mode 100644
index 000000000..ead2c44e0
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right_dark.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_midnight.png b/dist/icons/controller/single_joycon_right_midnight.png
new file mode 100644
index 000000000..89afe022d
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right_midnight.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical.png b/dist/icons/controller/single_joycon_right_vertical.png
new file mode 100644
index 000000000..4d7d06547
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right_vertical.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical_dark.png b/dist/icons/controller/single_joycon_right_vertical_dark.png
new file mode 100644
index 000000000..9a6eb3013
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right_vertical_dark.png
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical_midnight.png b/dist/icons/controller/single_joycon_right_vertical_midnight.png
new file mode 100644
index 000000000..685249b68
--- /dev/null
+++ b/dist/icons/controller/single_joycon_right_vertical_midnight.png
Binary files differ
diff --git a/dist/languages/.gitignore b/dist/languages/.gitignore
new file mode 100644
index 000000000..27e5a0158
--- /dev/null
+++ b/dist/languages/.gitignore
@@ -0,0 +1,2 @@
+# Ignore the source language file
+en.ts
diff --git a/dist/languages/.tx/config b/dist/languages/.tx/config
new file mode 100644
index 000000000..0d9b512ea
--- /dev/null
+++ b/dist/languages/.tx/config
@@ -0,0 +1,8 @@
+[main]
+host = https://www.transifex.com
+
+[yuzu.emulator]
+file_filter = <lang>.ts
+source_file = en.ts
+source_lang = en
+type = QT
diff --git a/dist/languages/README.md b/dist/languages/README.md
new file mode 100644
index 000000000..61981ab1d
--- /dev/null
+++ b/dist/languages/README.md
@@ -0,0 +1 @@
+This directory stores translation patches (TS files) for yuzu Qt frontend. This directory is linked with [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu), so you can update the translation by executing `tx pull -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically. Do not directly open PRs on github to modify the translation.
diff --git a/dist/languages/de.ts b/dist/languages/de.ts
new file mode 100644
index 000000000..b93c9f51c
--- /dev/null
+++ b/dist/languages/de.ts
@@ -0,0 +1,4749 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="de" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Über yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu ist ein experimenteller, quelloffener Emulator für Nintendo Switch, lizensiert unter GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Diese Software sollte nicht dazu verwendet werden, Spiele zu spielen, die du nicht legal erhalten hast.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Quellcode&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Mitwirkende&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Lizenz&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; ist ein Warenzeichen von Nintendo. yuzu ist in keiner Weise mit Nintendo verbunden.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Verbindung mit dem Server wird hergestellt...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Tippe auf die obere linke Ecke &lt;br&gt;deines Touchpads.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Tippe auf die untere rechte Ecke &lt;br&gt;deines Touchpads.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Konfiguration abgeschlossen!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Kompatibilität melden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Kompatibilität des Spiels melden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Solltest du einen Bericht zur &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu-Kompatibilitätsliste&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; beitragen wollen, werden die folgenden Informationen gesammelt und auf der yuzu-Webseite dargestellt:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware-Informationen (CPU / GPU / Betriebssystem)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Welche yuzu-Version du benutzt&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Den verbundenen yuzu-Account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfekt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert einwandfrei und ohne Audio- oder Grafikfehler.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Gut</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert mit kleineren Grafik- oder Audiofehlern und ist von Anfang bis Ende spielbar. Eventuell sind einige Workarounds erforderlich.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert mit größeren Grafik- oder Audiofehlern, lässt sich aber mit Workarounds bis zum Ende durchspielen. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Schlecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Spiel funktioniert zwar, aber nur mit starken Grafik- oder Audiofehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menü</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel ist wegen schwerwiegendsten Grafik- oder Audiofehlern unspielbar. Das Spiel lässt sich lediglich starten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Startet nicht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel stürzt beim Versuch zu starten ab.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Unabhängig von Geschwindigkeit oder Performance, wie gut läuft das Spiel von Start bis Ende in dieser Version von yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Vielen Dank für deinen Beitrag!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Absenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Kommunikationsfehler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Beim Senden des Berichtes ist ein Fehler aufgetreten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Weiter</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Ausgabe-Engine:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Dieser Nachbearbeitungseffekt passt die Audiogeschwindigkeit an die Emulationsgeschwindigkeit an und verhindert Audioaussetzer. Dies erhöht jedoch die Audioverzögerung.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Audiodehnung aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Audiogerät:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Globale Lautstärke verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Lautstärke:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Lautstärke:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Allgemeines</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Genauigkeit der Emulation:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Akkurat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Unsicher</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Debug-Modus aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Wir empfehlen, dies auf &quot;Akkurat&quot; zu stellen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Unsichere CPU-Optimierungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Diese Optionen reduzieren die Genauigkeit, können jedoch die Geschwindigkeit erhöhen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Unfuse FMA (erhöht Leistung auf CPUs ohne FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Diese Option steigert die Geschwindigkeit, indem die Genauigkeit von sog. &quot;fused-multiply-add&quot; Anweisungen auf CPUs ohne native FMA-Unterstützung reduziert wird.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Schnelleres FRSQRTE und FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Diese Option steigert die Geschwindigkeit von einigen &quot;floating point&quot;-Anweisungen, indem weniger akkurate Schätzungen der Werte verwendet werden.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Debug-Modus für CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>Der Debug-Modus ist nur für Entwickler gedacht. Bist du dir sicher?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>CPU-Optimierungen ändern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Dies ist nur zur Fehlerbehebung gedacht .&lt;/b&gt;
+ &lt;br&gt;
+ Falls du dir nicht sicher bist, was hier passiert, solltest du keine dieser Optionen ändern.
+ &lt;br&gt;
+ Diese Optionen treten nur dann in Kraft, wenn auch der CPU Debug-Modus aktiviert ist.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Enable inline page tables</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Enable block linking</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Return stack buffer aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Enable fast dispatcher</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Enable context elimination</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Enable constant propagation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Enable miscellaneous optimizations</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Enable misalignment check reduction</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDB-Stub aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globaler Log-Filter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Log-Konsole öffnen (nur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Log-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>String-Argumente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Wenn aktiviert, wird die Grafik-API in einem langsamen Debug-Modus gestartet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Grafik-Debugging aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Diese Option deaktiviert den Macro-JIT-Compiler. Dies wird die Geschwindigkeit verringern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Macro-JIT deaktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ausführliche Service-Fehlerberichte aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Dies wird automatisch beim Schließen von yuzu zurückgesetzt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk(Quest)-Modus</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Debug-Controller einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu-Konfiguration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Benutzeroberfläche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Spieleliste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Dateisystem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Steuerung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Hotkeys</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GraphicsAdvanced</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Speicherverzeichnisse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD-Karte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamecard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Pfad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Eingelegt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Aktuelles Spiel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Patchmanager</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dekomprimierte NSOs dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>ExeFS dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod-Ladeverzeichnis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Root dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cache-Ordner</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadaten der Spieleliste cachen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Metadaten-Cache zurücksetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Emulierten NAND-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Emulierten SD-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Gamecard-Pfad auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Dump-Verzeichnis auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Mod-Ladeverzeichnis auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Cache-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Der Metadaten-Cache ist bereits leer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>Der Vorgang wurde erfolgreich abgeschlossen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Der Metadaten-Cache konnte nicht gelöscht werden. Er könnte in Gebrauch oder nicht vorhanden sein.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Geschwindigkeit auf % festlegen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Multicore-CPU-Emulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Schließen des Emulators bestätigen, falls ein Spiel läuft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Beim Spielstart nach Nutzer fragen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Emulation im Hintergrund pausieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Mauszeiger verstecken</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Gerät:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Grafik-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Nutze Festplatten-Shader-Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Asynchrone GPU-Emulation verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Seitenverhältnis:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Standard (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>4:3 erzwingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>21:9 erzwingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Auf Fenster anpassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Globale Hintergrundfarbe verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Hintergrundfarbe:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Hintergrundfarbe:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGL GPU</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Erweiterte Grafik-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Genauigkeit der Emulation:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSync verhindert Screen-Tearing, aber manche Grafikkarten haben eine schlechtere Leistung, wenn es aktiviert ist. Wenn du keinen Unterschied merkst, lasse es aktiviert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>VSync nutzen (Nur OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Aktivieren dieser Einstellung reduziert Stottern durch Shader. Aktiviert OpenGL Assembly-Shader auf unterstützten Nvidia-Karten (NV_gpu_program5 wird benötigt). Dieses Feature ist experimentell.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Nutze Assembly-Shader (experimentell, nur Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Nutze asynchrone Shader-Kompilierung. Dies kann Stottern durch Shader reduzieren. Dieses Feature ist experimentell.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Nutze asynchrone Shader-Kompilierung (experimentell)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Nutze schnelle GPU-Zeit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Anisotrope Filterung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Hotkey-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Doppelklicke auf eine Tastensequenz, um sie zu ändern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Alle löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Aktion</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Kontext</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Tastensequenz bereits belegt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>Die eingegebene Sequenz ist bereits vergeben an: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>Die Standard-Sequenz ist bereits vergeben an: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigureInput</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Spieler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Spieler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Spieler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Spieler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Spieler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Spieler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Spieler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Spieler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Konsolenmodus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Im Dock</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Bewegung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Verbunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Eingabe einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joyconfarben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Spieler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Links</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>L-Taste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Rechts</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>R-Taste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Spieler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Spieler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Spieler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Spieler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Spieler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Spieler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Spieler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Weiteres</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Tastatur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Maus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Bewegung / Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debug Controller</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Eingabe einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Controller verbinden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Zwei Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Linker Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Rechter Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Eingabegerät</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Alle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Tastatur/Maus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Neu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Linker Analogstick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Hoch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Links</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Rechts</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Runter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Gedrückt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modifikator</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Radius</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Deadzone: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Modifikator-Radius: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Steuerkreuz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Minus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Tasten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Rechter Analogstick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Deadzone: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Modifikator-Radius: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[wartet]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Bewegung / Touch einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Bewegung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Quelle für Bewegung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Empfindlichkeit:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Quelle für Touch:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Kalibrierung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Einrichtung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Tastenbelegung nutzen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP Konfiguration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Du kannst alle Cemuhook-kompatiblen UDP-Eingabequellen für Bewegung und Touch verwenden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Server:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Mehr erfahren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Testen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Maus (Rechtsklick)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Emulator-Fenster</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Mehr erfahren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Test erfolgreich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Daten wurden erfolgreich vom Server empfangen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>Test fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Konnte keine Daten vom Server empfangen.&lt;br&gt;Prüfe bitte, dass der Server korrekt eingerichtet wurde und dass Adresse und Port korrekt sind.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDP-Test oder Kalibration wird gerade durchgeführt.&lt;br&gt;Bitte warte einen Moment.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Maus einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Maustasten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Vorwärts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Rückwärts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Links:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Mitte:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Rechts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[nicht belegt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Standardwert wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[Taste drücken]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Dialog</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Info</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Name</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>Titel ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Dateiname</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Format</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Version</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Größe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Entwickler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Allgemeines</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Erw. Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Globale Konfiguration verwenden (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Patchname</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Version</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Nutzerverwaltung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Aktueller Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nutzername</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Bild wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Hinzufügen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Die Nutzerverwaltung ist nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Nutzername eingeben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Gib einen Benutzernamen für den neuen Benutzer ein:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Gib einen neuen Nutzernamen ein:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Löschen bestätigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Du bist dabei, den Nutzer &quot;%1&quot; zu löschen. Bist du dir sicher?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Profilbild wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG Bilddateien (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Fehler beim Löschen des Bildes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Fehler beim Überschreiben des vorherigen Bildes bei: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Fehler beim Löschen der Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Konnte die bestehende Datei &quot;%1&quot; nicht löschen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Fehler beim Erstellen des Ordners für die Profilbilder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Konnte Ordner &quot;%1&quot; nicht erstellen, um Profilbilder zu speichern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Fehler beim Kopieren des Profilbildes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Das Bild konnte nicht von &quot;%1&quot; nach &quot;%2&quot; kopiert werden</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT ist Nintendos Methode, Daten an Spiele zu senden, um die Community zu involvieren und zusätzliche Inhalte freizuschalten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Erfahre mehr über BCAT, Boxcat, und aktuelle Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Der Boxcat Service ist offline oder du bist nicht mit dem Internet verbunden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Bei der Verarbeitung der Boxcat-Eventdaten ist ein Fehler aufgetreten. Kontaktiere die yuzu-Entwickler.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>Die Version von yuzu, die du verwendest, ist entweder zu neu oder zu alt für den Server. Versuche auf die neueste offizielle Version von yuzu zu updaten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Es gibt zurzeit keine Events auf Boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Momentane Boxcat-Events</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu lädt den neuesten Boxcat-Status...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Systemeinstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Region:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Kuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Ägypten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Island</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libyen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Türkei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Korea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Zeitzone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Anmerkung: Diese Einstellung kann überschrieben werden, falls deine Region auf &quot;auto-select&quot; eingestellt ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japanisch (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Englisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Französisch (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Deutsch (German)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italienisch (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spanisch (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreanisch (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Niederländisch (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugiesisch (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russisch (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Britisches Englisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Kanadisches Französisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Lateinamerikanisches Spanisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Vereinfachtes Chinesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Traditionelles Chinesisch (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Benutzerdefinierte Echtzeituhr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Sprache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG Seed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Konsolen ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Soundausgabe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Neu generieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Die Systemeinstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Dieser Vorgang wird deine momentane &quot;virtuelle Switch&quot; mit einer Neuen ersetzen. Deine momentane &quot;virtuelle Switch&quot; wird nicht wiederherstellbar sein. Dies könnte einige unerwartete Effekte in manchen Spielen mit sich bringen. Zudem könnte der Prozess fehlschlagen, wenn zu alte Daten verwendet werden. Möchtest du den Vorgang fortsetzen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Warnung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Konsolen ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Touchscreen-Belegung einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Belegung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Neu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Klicke das untere Feld um einen Punkt hinzuzufügen und drücke dann eine Taste, die diesen Punkt auslösen soll.
+Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf die Tabelle, um die Belegung des Punktes zu ändern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Punkt löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Knopf</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Neues Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Neuen Namen für das Profil eingeben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Profil löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Profil &quot;%1&quot; löschen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Profil umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Neuer Name:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[Knopf drücken]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Touchscreen einrichten:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Achtung: Die Einstellungen auf dieser Seite haben Einfluss auf yuzus Touchscreen-Emulation. Sie zu ändern könnte zu unerwünschtem Verhalten, wie zu einem Ausfall des Touchscreens, führen. Du solltest sie nur verändern, falls du weißt, was du tust.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Berührungsparameter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Berührungsdurchmesser Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Finger</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Berührungsdurchmesser X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Drehwinkel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Anmerkung: Das Ändern der Sprache wird deine Konfiguration speichern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Sprache der Benutzeroberfläche:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Theme:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Spieleliste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Add-On Spalte anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Icongröße:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Zeile 1 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Zeile 2 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Screenshots</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Frage nach, wo Screenshots gespeichert werden sollen (Nur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Screenshotpfad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Screenshotpfad auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Englisch</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Mit dem Bereitstellen deines Benutzernamens und Tokens erlaubst du yuzu, zusätzliche Nutzungsdaten zu sammeln. Diese könnten auch Informationen beinhalten, die dich identifizieren könnten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Überprüfen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nutzername: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Was ist mein Token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Teile anonyme Nutzungsdaten mit dem yuzu-Team</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Mehr erfahren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Telemetrie-ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Neu generieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord-Präsenz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Zeig dein momentanes Spiel in deinem Discord-Status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Mehr erfahren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registrieren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Was ist mein Token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Telemetrie-ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Nicht spezifiziert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token nicht verifiziert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Token wurde nicht verfiziert. Die Änderungen an deinem Token wurden nicht gespeichert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifizieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verifizierung fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verifizierung fehlgeschlagen. Prüfe ob dein Nutzername und Token richtig eingegeben wurden und ob deine Internetverbindung korrekt funktioniert.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonyme Daten werden gesammelt,&lt;/a&gt; um yuzu zu verbessern.&lt;br/&gt;&lt;br/&gt;Möchstest du deine Nutzungsdaten mit uns teilen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Textprüfung fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Lade Web-Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Web-Applet verlassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Verlassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Um das Web-Applet zu verlassen, verwende die im Spiel enthaltenen Steuerelemente, wähle die die Option &apos;Web-Applet beenden&apos; in der Menüleiste oder drücke die &apos;Enter&apos;-Taste.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web-Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Diese Version von yuzu wurde ohne QtWebEngine-Unterstützung kompiliert, was bedeutet, dass yuzu das angeforderte Spielhandbuch oder die Webseite nicht richtig anzeigen kann.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>Wie viele Shader im Moment kompiliert werden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Derzeitige Emulations-Geschwindigkeit. Werte höher oder niedriger als 100% zeigen, dass die Emulation scheller oder langsamer läuft als auf einer Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Wie viele Bilder pro Sekunde angezeigt werden variiert von Spiel zu Spiel und von Szene zu Szene. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Zeit, die gebraucht wurde, um einen Switch-Frame zu emulieren, ohne Framelimit oder V-Sync. Für eine Emulation bei voller Geschwindigkeit sollte dieser Wert bei höchstens 16.67ms liegen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MEHRKERN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Zuletzt geladene Dateien leeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Warnung veraltetes Spielformat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Du nutzt eine entpackte ROM-Ordnerstruktur für dieses Spiel, welches ein veraltetes Format ist und von anderen Formaten wie NCA, NAX, XCI oder NSP überholt wurde. Entpackte ROM-Ordner unterstützen keine Icons, Metadaten oder Updates.&lt;br&gt;&lt;br&gt;&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Unser Wiki&lt;/a&gt; enthält eine Erklärung der verschiedenen Formate, die yuzu unterstützt. Diese Nachricht wird nicht noch einmal angezeigt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>ROM konnte nicht geladen werden!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>ROM-Format wird nicht unterstützt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Beim Initialisieren des Video-Kerns ist ein Fehler aufgetreten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>Beim Laden des Video-Kerns trat ein Fehler auf. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Wie kann ich eine Log-Datei hochladen?&lt;/a&gt;. Stelle sicher, dass die aktuellsten Grafiktreiber für deine Grafikkarte installiert sind.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>Fehler beim Laden des ROMs!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ein unbekannter Fehler ist aufgetreten. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Speicherdaten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Konnte Verzeichnis %1 nicht öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Verzeichnis existiert nicht!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Fehler beim Öffnen des transferierbaren Shader-Caches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Es existiert kein Shader-Cache für diesen Titel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Inhalte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Eintrag entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Installiertes Spiel %1 entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Erfolgreich entfernt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Das Spiel wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Fehler beim Entfernen von %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>Das Spiel ist nicht im NAND installiert und kann somit nicht entfernt werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Das Update wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Es ist kein Update für diesen Titel installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Es sind keine DLC für diesen Titel installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1 DLC entfernt. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>Transferierbaren Shader-Cache entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Spiel-Einstellungen entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Datei entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Fehler beim Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>Der transferierbare Shader-Cache wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Konnte den transferierbaren Shader-Cache nicht entfernen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Fehler beim Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Es existieren keine Spiel-Einstellungen für dieses Spiel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Die Spiel-Einstellungen wurden entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Die Spiel-Einstellungen konnten nicht entfernt werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS-Extraktion fehlgeschlagen!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Das RomFS konnte wegen eines Fehlers oder Abbruchs nicht kopiert werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Komplett</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Nur Ordnerstruktur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>RomFS Extraktions-Modus auswählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Bitte wähle, wie das RomFS gespeichert werden soll.&lt;br&gt;&quot;Full&quot; wird alle Dateien des Spiels extrahieren, während &lt;br&gt;&quot;Skeleton&quot; nur die Ordnerstruktur erstellt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFS wird extrahiert...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS wurde extrahiert!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Der Vorgang wurde erfolgreich abgeschlossen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Fehler beim Öffnen von %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Verzeichnis auswählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Spiel-Einstellungen konnten nicht geladen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch-Programme (%1);;Alle Dateien (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Datei laden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Öffne das extrahierte ROM-Verzeichnis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Ungültiges Verzeichnis ausgewählt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Das Verzeichnis, das du ausgewählt hast, enthält keine &apos;main&apos;-Datei.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Installierbares Switch-Programm (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge Image (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Dateien installieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Datei &quot;%1&quot; wird installiert...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>NAND-Installation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Systemanwendung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Systemarchiv</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Systemanwendungsupdate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Firmware-Paket (Typ A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Firmware-Paket (Typ B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Spiel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Spiel-Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Spiel-DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Wähle den NCA-Installationstyp aus...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Bitte wähle, als was diese NCA installiert werden soll:
+(In den meisten Fällen sollte die Standardeinstellung &apos;Spiel&apos; ausreichen.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installation fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Der Titel-Typ, den du für diese NCA ausgewählt hast, ist ungültig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Datei nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Datei &quot;%1&quot; nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Fortsetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Fehleranzeige</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Fehlender yuzu-Account</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Um einen Kompatibilitätsbericht abzuschicken, musst du einen yuzu-Account mit yuzu verbinden.&lt;br&gt;&lt;br/&gt;Um einen yuzu-Account zu verbinden, prüfe die Einstellungen unter Emulation &amp;gt; Konfiguration &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Fehler beim Öffnen der URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>URL &quot;%1&quot; kann nicht geöffnet werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo-Datei (%1);; Alle Dateien (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Amiibo laden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Fehler beim Öffnen der Amiibo Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Die Amiibo Datei &quot;%1&quot; konnte nicht zum Lesen geöffnet werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Fehler beim Lesen der Amiibo-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Amiibo-Daten können nicht vollständig gelesen werden. Es wurde erwartet, dass %1 Bytes gelesen werden, es konnten aber nur %2 Bytes gelesen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Fehler beim Laden der Amiibo-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Amiibo-Daten konnten nicht geladen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot aufnehmen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG Bild (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Geschwindigkeit: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Geschwindigkeit: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Spiel: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Das Spiel, dass du versuchst zu spielen, benötigt bestimmte Dateien von deiner Switch-Konsole.&lt;br/&gt;&lt;br/&gt;Um Informationen darüber zu erhalten, wie du diese Dateien von deiner Switch extrahieren kannst, prüfe bitte die folgenden Wiki-Seiten: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;System-Archive und Shared Fonts von einer Switch-Konsole extrahieren&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu konnte ein Switch Systemarchiv nicht finden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu konnte ein Switch Systemarchiv nicht finden: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Systemarchiv nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Systemarchiv fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu konnte die Switch Shared Fonts nicht finden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Shared Fonts nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Shared Font fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Schwerwiegender Fehler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Ein schwerwiegender Fehler ist aufgetreten, bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Wie kann ich eine Log-Datei hochladen&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Fataler Fehler aufgetreten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Schlüsselableitung bestätigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Du bist im Begriff, alle Schlüssel neu abzuleiten. Falls du nicht weißt, was das heißt oder was du hier tust, könnte dieser Prozess möglicherweise destruktiv sein. Bitte stelle sicher, dass du wirklich fortfahren willst und optional Sicherungen deiner Daten machst.
+
+Dieser Prozess wird die generierten Schlüsseldateien löschen und die Schlüsselableitung neu starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Fuses fehlen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - BOOT0 fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - BCPKG2-1-Normal-Main fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - PRODINFO fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Derivationskomponenten fehlen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Einige Komponenten, die yuzu benötigt, um Schlüssel zu generieren, wurden nicht gefunden. &lt;br&gt;Bitte folge &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;dem yuzu Schnellstart-Guide&lt;/a&gt; um alle deine Schlüssel und Spiele zu übertragen.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Schlüssel werden abgeleitet...
+Dies könnte, je nach Leistung deines Systems, bis zu einer Minute dauern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Schlüsselableitung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>RomFS wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Wähle, welches RomFS du speichern möchtest.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Bist du sicher, dass du yuzu beenden willst?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Bist du sicher, dass du die Emulation stoppen willst? Jeder nicht gespeicherte Fortschritt geht verloren.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Die derzeit laufende Anwendung hat yuzu aufgefordert, sich nicht zu beenden.
+
+Möchtest du dies umgehen und sie trotzdem beenden?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL nicht verfügbar!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu wurde nicht mit OpenGL-Unterstützung kompiliert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan nicht verfügbar!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu wurde nicht mit Vulkan-Unterstützung kompiliert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>Fehler beim Initialisieren von OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Deine Grafikkarte unterstützt kein OpenGL 4.3, oder du hast nicht den neusten Treiber installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Fehler beim Initialisieren von OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Deine GPU unterstützt anscheinend nicht eine oder mehrere von yuzu benötigte OpenGL-Erweiterungen. Bitte stelle sicher, dass du den neusten Grafiktreiber für deine GPU installiert hast.&lt;br&gt;&lt;br&gt;Nicht unterstützte Erweiterungen:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Name</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Kompatibilität</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Add-ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Dateityp</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Größe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Spielstand-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Mod-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Transferierbaren Shader-Cache öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Installiertes Update entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Alle installierten DLCs entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Shader-Cache entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Spiel-Einstellungen entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Alle installierten Inhalte entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>RomFS speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Title-ID in die Zwischenablage kopieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>GameDB-Eintrag öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Eigenschaften</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Unterordner scannen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Spieleverzeichnis entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Nach Oben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Nach Unten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Verzeichnis öffnen</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfekt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Das Spiel funktioniert fehlerfrei, ohne Audio- oder Grafikfehler, und sämtliche getestete Funktionalität funktioniert wie vorhergesehen ohne Workarounds.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Gut</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Das Spiel funktioniert mit kleineren Audio- oder Grafikfehlern und lässt sich bis zum Ende durchspielen.
+Eventuell sind einige Workarounds notwendig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Das Spiel funktioniert mit größern Audio- oder Grafikfehlern,
+lässt sich aber mit Workarounds bis zum Ende durchspielen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Schlecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Spiel funktioniert zwar, aber nur mit starken Audio- oder Grafikfehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menü</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Das Spiel ist wegen schwerwiegenden Audio- oder Grafikfehlern unspielbar. Das Spiel lässt sich lediglich starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Startet nicht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Das Spiel stürzt beim Versuch zu starten ab.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Nicht getestet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Spiel wurde noch nicht getestet.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Doppelklicke, um einen neuen Ordner zur Spieleliste hinzuzufügen.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Wörter zum Filtern eingeben</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Bitte bestätige, dass du diese Dateien installieren willst.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Wenn du ein Update oder DLC installierst, wirst du die vorher installierten überschreiben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Installieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Dateien im NAND installieren</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Shader 387 / 1628 wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Shader %v / %m wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Geschätzte Zeit: 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Lädt...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Shader %1 / %2 wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Starten...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Geschätzte Zeit: %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Letzte Dateien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Anzeige</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Werkzeuge</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Hilfe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Dateien im NAND installieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Datei laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Verzeichnis laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;chließen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Schlüssel neu initialisieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Über yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Einzelfenster-Modus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Einstellungen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Dock-Widget-Header anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Filterleiste anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Statusleiste anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Fenstergröße zurücksetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Vollbild</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Neustart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Amiibo laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Kompatibilität berichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Mods-Seite öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Schnellstart-Anleitung öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>yuzu-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot aufnehmen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Spiel-Einstellungen ändern...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Installierte SD-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Installierte NAND-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Systemtitel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Neues Spieleverzeichnis hinzufügen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Strg</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[nicht gesetzt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Achse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Taste %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[unbekannt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Klick 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Klick 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Klick 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Klick 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Klick 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC Achse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC Knopf %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[unbenutzt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Achse %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC Achse %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ein Fehler ist aufgetreten.
+Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software.
+
+Fehlercode: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ein Fehler ist in %1 bei %2 aufgetreten.
+Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software.
+
+Fehlercode: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ein Fehler ist aufgetreten.
+Fehlercode: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Wähle einen Benutzer aus:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Profilauswahl</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Text eingeben:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software-Tastatur</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Hotkey eingeben</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stack aufrufen</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>Warten auf Mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>has waiters: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>owner handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>Warten auf alle Objekte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>Warten auf eines der folgenden Objekte</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>von keinem Thread pausiert</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>läuft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>bereit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausiert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>warten auf HLE Rückgabe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>schläft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>Warten auf IPC-Antwort</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>Warten auf Objekte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>Warten auf Mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>wartet auf condition variable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>Warten auf den Adressarbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>ruhend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>tot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>Kern %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Unbekannter Prozessor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>Prozessor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>ideal core = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>Affinitätsmaske = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>Thread-ID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>Priorität = %1(aktuell) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>Letzte laufende Ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>nicht auf Mutex warten</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>gewartet von Thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wait Tree</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/es.ts b/dist/languages/es.ts
new file mode 100644
index 000000000..437817561
--- /dev/null
+++ b/dist/languages/es.ts
@@ -0,0 +1,4757 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="es" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Acerca de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu es un emulador experimental de código abierto de Nintendo Switch licenciado bajo GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Este software no debe ser utilizado para jugar juegos que no hayas obtenido legalmente.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Sitio web&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código fuente&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidor&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licencia&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; es una marca registrada de Nintendo. yuzu no esta afiliado con Nintendo de ninguna manera.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Comunicando con el servidor...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Toque la esquina superior izquierda&lt;br&gt;de su trackpad.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Ahora toque la esquina inferior derecha &lt;br&gt;de su trackpad.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>¡Configuración completa!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Informar de compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Informar de compatibilidad del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;SI decides presentar una prueba a la &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Lista de Compatibilidad de yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, La siguiente información será obtenida y mostrada en el sitio web:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informacion de Hardware (CPU / GPU / Sistema Operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Qué versión de yuzu estas utilizando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;La cuenta de yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfecto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona a la perfección sin ningún problema gráfico o de audio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Excelente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona con fallos gráficos o de audio menores y es jugable desde el principio hasta el final. Puede requerir de soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Bien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona con importantes fallos gráficos o de audio, pero es jugable desde el principio hasta el final con soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona, pero tiene errores gráficos o de audio. Imposible progresar en ciertas áreas debido a errores incluso con soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menú</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla de inicio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>No inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego se bloquea al intentar iniciar.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independientemente de la velocidad o del rendimiento, ¿cómo definiría su experiencia con el juego de principio a fin en esta versión de yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Gracias por su contribución.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Enviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Error de comunicación.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Ha ocurrido un error mientras se mandaba el caso de prueba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Siguiente</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motor de Salida:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efecto de post-procesado ajusta la velocidad del audio para igualarla a la velocidad de emulación y así prevenir parones en el audio. No obstante, esto aumenta la latencia del audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activar extensión del audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de Audio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usar el volumen global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Ajustar volumen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volumen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Precisión: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Preciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Impreciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Activar el Modo de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Recomendamos ajustar la precisión a &quot;Preciso&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Ajustes del Modo Impreciso de Optimización de la CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Estos ajustes reducen la precisión para mejorar el rendimiento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Unfuse FMA (mejorar el rendimiendo en las CPU sin FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opción mejora el rendimiento al reducir la precisión de las instrucciónes fused-multiply-add en las CPU sin soporte nativo de FMA.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Más rápido FRSQRTE y FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opción mejora el rendimiento de algunas funciones aproximadas de punto flotante al utilizar aproximaciones nativas menos precisas.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Ajustando la CPU al Modo de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>El Modo de Depuración de la CPU sólo está destinado al uso de los desarrolladores. ¿Estás seguro de que quieres activar esto?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Cambiar las optimizaciones de la CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Sólo para la depuración.&lt;/b&gt;
+ &lt;br&gt;
+ Si no está seguro de lo que hacen estas opciónes, manténgalos todos activados.
+ &lt;br&gt;
+ Estos ajustes sólo toman efecto cuando la precisión de la CPU esta en &quot;Modo de Depuración&quot;.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Activar las tablas de páginas inline.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Esta optimización acelera los accesos a la memoria del programa del invitado.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Activando las inlines accede a PageTable::pointers en código emitido.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Desactivando esto obliga a que todos los accesos a la memoria pasen por las funciones Memory::Read/Memory::Write.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Activar el enlace de bloques</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta optimización evita las búsquedas del despachador permitiendo que los bloques básicos emitidos salten directamente a otros bloques básicos si el PC de destino es estático.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Activar el buffer de la pila de retorno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta optimización evita las búsquedas de los despachadores al rastrear las direcciones potenciales de retorno de las instrucciones de BL. Esto se aproxima a lo que sucede con un buffer de la pila de retorno en una CPU real.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Activar el despachador rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Habilitar un sistema de despacho de dos niveles. Un despachador más rápido escrito en ensamblado tiene un pequeño caché MRU de destinos de salto se utiliza primero. Si eso falla, el despacho vuelve al despacho más lento de C++.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Activar la eliminación del contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita una optimización de IR que reduce los accesos innecesarios a la estructura del contexto de la CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Activar la propagación constante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita optimizaciones de IR que implican una propagación constante.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Activar optimizaciones misceláneas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita optimizaciónes misceláneas IR.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Activar la reducción del control de desalineación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Cuando está activada, una desalineación sólo se activa cuando un acceso cruza el límite de una página.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Cuando está desactivado, se desencadena una desalineación en todos los accesos desalineados.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activar GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Puerto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro de Registro Global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar Consola del Registro (Sólo en Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir Ubicación del Registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Cadena de Argumentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Cuando está marcado, la API de gráficos entra en un modo de depuración más lento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Activar la Depuración de Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Cuando está marcado, se desactiva el compilador de macro Just In Time. Activando esto hace que los juegos se ejecuten más lento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Desactivar Macro JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Volcar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Habilitar Servicios de Reporte Detallados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Estas opciones se restablecerán automáticamente cuando yuzu se cierre.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo Quiosco (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Configurar el Control de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuración de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>IU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de Archivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GráficosAvanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Servicios</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Directorios de Almacenamiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Tarjeta SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartucho de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Ruta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Insertado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Juego Actual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Administrador de Parches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Volcar NSOs Descomprimidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Volcar Partición ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Carpeta raíz de carga de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Carpeta raíz de Volcado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Cargando Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Directorio de Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadatos de Lista de Juegos en Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Reiniciar Caché de Metadatos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Seleccione el directorio de NAND Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Seleccione el directorio de SD Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Seleccione la ruta del Cartucho...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Seleccione Directorio para Volcar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Seleccione el directorio de carga de Mod...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Seleccione el Directorio para Caché...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>El caché de metadatos ya está vacío.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>La operación se completó con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>El caché de metadatos no se pudo eliminar. Puede que se encuentre en uso actualmente o ya haya sido eliminado.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limitar el Porcentaje de Velocidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulación de CPU multinúcleo </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmar salida mientras se ejecuta la emulación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Mostrar usuario actual al abrir el juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar emulación cuando la ventana esté en segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Ocultar el cursor del ratón cuando no está activo.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Ajustes de la API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Ajustes de los Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar caché de shaders en disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulación asíncrona de GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Relación de Aspecto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Valor Predeterminado (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Forzar a 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Forzar a 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Estirar a la Ventana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usar el color de fondo global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Establecer el color de fondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Color de Fondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Dispositivo Gráfico OpenGL:</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Ajustes de los Gráficos Avanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Nivel de Precisión: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSync evita que la pantalla se desgarre, pero algunas tarjetas gráficas tienen un rendimiento menor con VSync activado. Mantenlo activado si no notas una diferencia de rendimiento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>Usar VSync (sólo en OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Activando esto reduce el tartamudeo causado por la compilación de shaders. Activa shaders de ensamblaje OpenGL en los dispositivos de Nvidia soportados (se requiere NV_gpu_program5). Esta función es experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Usar shaders de ensamblaje (experimental, sólo Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Activa la compilación de shaders en modo asíncrono, cuál podría reducir el tartamudeo de los shaders. Esta función es experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Usar la construcción de shaders asíncronos (experimental)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Usar Tiempo Rápido en la GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Filtrado Anisotrópico:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Configuración de Teclas de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Haz doble-clic para cambiar la asignación de teclas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Eliminar Todo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Acción</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Tecla de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Secuencia de Teclas en Conflicto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>La secuencia de teclas introducida ya ha sido asignada a: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>La secuencia de teclas predeterminada ya ha sido asignada a: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigurarEntrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jugador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jugador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jugador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jugador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jugador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jugador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jugador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jugador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Modo de la Consola</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Estacionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Movimiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controladores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Conectado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Colores de Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Jugador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Lado L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Botón L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Lado R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Botón R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Jugador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Jugador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Jugador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Jugador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Jugador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Jugador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Jugador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Otro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Teclado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Pantalla Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Movimiento / Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Control de Depuración</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Conectar el Controlador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Doble Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Joycon Izquierdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Joycon Derecho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Dispositivo de Entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Cualquier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Teclado/Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Guardar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Crear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Palanca Izquierda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Arriba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Izquierda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Derecha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Abajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Presionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Rango</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Zona Muerta: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Rango del Modificador: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Cruceta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Menos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Captura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Más</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Inicio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botones Frontales</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Palanca Derecha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Zona Muerta: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Rango del Modificador: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[esperando]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Configurar: Movimiento / Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Movimiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Proveedor de Movimiento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilidad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Proveedor de Táctil:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibración:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Usar el mapeo de botones:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Configuración CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Puede utilizar cualquier fuente de entrada UDP compatible con Cemuhook para proporcionar una entrada de movimiento y de tacto.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Servidor:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Puerto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Más Información</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Probar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Ratón (Clic Derecho)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Ventana del Emulador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Más Información&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Probando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Configurando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Prueba Existosa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Se recibió con éxito los datos del servidor.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>Prueba fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>No pudo recibir datos válidos del servidor.&lt;br&gt;Por favor, verifique que el servidor esté configurado correctamente y que la dirección y el puerto sean correctos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>La prueba de UDP o la configuración de la calibración está en curso.&lt;br&gt;Por favor, espere a que termine el proceso.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar el Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botones del Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Adelante:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Atrás:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Izquierda:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Medio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Derecha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[no establecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pulsa un botón]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Diálogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Información</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nombre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>ID del Título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nombre del Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Versión</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Tamaño</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Desarrollador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Complementos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Gráficos Avanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Usar la configuración global (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Nombre del Parche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Versión</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Administrador de Perfiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Usuario Actual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nombre de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Seleccionar Imagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Añadir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renombrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>El sistema de perfiles solo se encuentra disponible cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Introduzca el Nombre de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Usuarios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Introduzca un nombre para el nuevo usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Introduzca un nuevo nombre de usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation> Confirmar Eliminanación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Estás a punto de eliminar al usuario &quot;%1&quot; ¿Estás seguro?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Seleccione una Imagen de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagenes JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Error al eliminar la imagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocurrió un error al intentar sobrescribir la imagen anterior en: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Error eliminando archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>No se pudo eliminar el archivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Error al crear el directorio de imagen del usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>No se puede crear el directorio % 1 para almacenar imágenes de usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Error al copiar la imagen del usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>No se puede copiar la imagen de %1 a %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT es la forma con la que Nintendo envía datos de sus juegos para así interconectar su comunidad y desbloquear contenido adicional.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend de BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;Saber más sobre BCAT, Boxcat, y sus eventos actuales.&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>El servicio boxcat se encuentra fuera de linea o usted no está conectado a internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Hubo un error al intentar procesar los datos del evento de boxcat. Por favor, contacte con los desarrolladores de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La versión de yuzu que está utilizando actualmente es demasiado nueva o demasiado antigua para este servidor. Intente actualizar a la última versión oficial de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>No hay ningún evento de boxcat en estos momentos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Eventos de Boxcat Actuales</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu está verificando el estado actual de boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Ajustes del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Región:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egipto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polonia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Turquía</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>EEUU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Corea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Zona Horaria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: esto puede ser ignorado cuando la opción de región está en &quot;autoseleccionar&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonés (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglés (english)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francés (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemán (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Español</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chino</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandés (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugués (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Ruso (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglés Británico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francés Canadiense</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Español latinoamericano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chino Simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chino Tradicional (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Personalizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Idioma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semilla de GNA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Envolvente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID de la Consola:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de salida del sonido:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM aaaa h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Los ajustes del sistema solo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Esto reemplazará tu Switch virtual con una nueva. Tu Switch virtual actual no será recuperable. Esto podría causar efectos inesperados en determinados juegos. Si usas un archivo de configuración obsoleto, esto podría fallar. ¿Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Advertencia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID de Consola: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Configurar las Asignaciones de la Pantalla Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Asignaciones:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Crear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Renombrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Haz clic en el área inferior para añadir un punto, y luego presiona un botón para unir.
+Arrastre los puntos para cambiar de posición, o haga doble clic en las celdas de la tabla para editar los valores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Borrar Punto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Botón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Crear Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Introduzca un nombre para el nuevo perfíl:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Borrar Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Borrar Perfíl %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Renombrar Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Nuevo nombre:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[presionar tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar pantalla táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Advertencia: Los ajustes en esta página afectarán al funcionamiento de la pantalla táctil emulada de yuzu. Cambiarlas podría resultar en un comportamiento inapropiado, como que la pantalla táctil deje de funcionar parcialmente. Se recomienda usar esta pestaña sólo si sabes lo que estás haciendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parámetros Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diámetro Táctil Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diámetro Táctil X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ángulo Rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Recuperar ajustes predeterminados</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Cambiar el idioma guardará tu configuración actual.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Lenguage de la Interfaz:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar Columna de Accesorios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamaño de los Iconos:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texto de la Fila 1:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texto de la Fila 2:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Capturas de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Pregunte Dónde Guardar las Capturas de Pantalla (sólo para Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Trayectoria de las Capturas de Pantalla:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Selecciona la Trayectoria de las Capturas de Pantalla:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglés</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Servicio Web de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Al proporcionar su nombre de usuario y token, das tu consentimiento a que yuzu recopile datos de uso adicionales, que pueden incluir información de identificación del usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrarse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nombre de usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>¿Cuál es mi token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetría</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartir datos de uso anónimos con el equipo de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saber más</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de Telemetría:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presencia de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar el juego actual en tu estado de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saber más&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Regístrate&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;¿Cuál es mi token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de Telemetría: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Sin especificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>No se pudo verificar el token</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>El token pudo ser verificado. El cambio realizado a su token no se ha guardado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verificando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificación fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificación fallida. Compruebe que ha ingresado el token correctamente, y que esté funcionando su conexión a internet.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Se recogen datos anónimos&lt;/a&gt; para ayudar a mejorar yuzu. &lt;br/&gt;&lt;br/&gt;¿Quieres compartir tus datos de uso con nosotros?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetría </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Comprobación de Texto Fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Cargando Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Cerrar Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Salir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para salir de la aplicación web, use los controles provistos por el juego y seleccione la opción &quot;Cerrar Web Applet&quot; en la barra del menú, o presione la tecla &quot;Enter&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versión de yuzu fue creada sin soporte para QtWebEngine, lo que significa que yuzu no puede mostrar correctamente el manual del juego o la página solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>La cantidad de shaders que se están construyendo actualmente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>La velocidad de emulación actual. Los valores superiores o inferiores al 100% indican que la emulación se está ejecutando más rápido o más lento que en una Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Cuántos fotogramas por segundo está mostrando el juego actualmente. Esto variará de un juego a otro y de una escena a otra.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tiempo que lleva emular un fotograma de la Switch, sin tener en cuenta la limitación de fotogramas o sincronización vertical. Para una emulación óptima, este valor debería ser como máximo de 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>ESTACIONADO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASINC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTINÚCLEO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpiar Archivos Recientes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Advertencia Formato de Juego Obsoleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Está utilizando el formato de directorio de ROM deconstruido para este juego, que es un formato desactualizado que ha sido reemplazado por otros, como NCA, NAX, XCI o NSP. Los directorios de ROM deconstruidos carecen de íconos, metadatos y soporte de actualización.&lt;br&gt;&lt;br&gt;Para obtener una explicación de los diversos formatos de Switch que soporta yuzu,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;echa un vistazo a nuestra wiki&lt;/a&gt;. Este mensaje no se volverá a mostrar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>¡Error al cargar la ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>El formato de la ROM no es compatible.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Se produjo un error al inicializar el núcleo de video.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu ha encontrado un error al ejecutar el núcleo de video, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Cómo cargar el archivo de registro.&lt;/a&gt; Asegúrese de tener los últimos controladores de gráficos para su GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>¡Error al cargar la ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Error desconocido. Por favor, consulte el registro para ver más detalles.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Datos de Juegos Guardados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Datos de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Error al abrir la carpeta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>¡La carpeta no existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Error al Abrir el Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>No existe un Caché de shaders para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Contenidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Actualización</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Eliminar el Título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Eliminar el Juego Instalado %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Se Eliminó con Éxito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Se eliminó con éxito el juego de base instalado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Error en la eliminación de %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>El juego base no está instalado en el NAND y no se puede eliminar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Se eliminó con éxito la actualización instalada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>No hay ninguna actualización instalada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>No hay ningún DLC instalado para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>Se eliminó con éxito %1 DLC instalado(s).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>¿Desea eliminar el Caché Transferible de Shaders?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>¿Desea Eliminar la Configuración Personalizada del Juego?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Eliminar Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Error al Eliminar el Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>El Caché Transferible de Shaders fue eliminado con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>No se ha podido eliminar el caché de shaders transferibles.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Error al Eliminar la Configuración Personalizada del Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>No existe una configuración personalizada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Se eliminó con éxito la configuración personalizada del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>No se ha podido eliminar la configuración personalizada del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>¡La extracción de RomFS falló!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Se produjo un error al copiar los archivos RomFS o el usuario canceló la operación.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Completo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Elegir modo para volcar el RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Seleccione la forma en que desea volcar el RomFS. &lt;br&gt;Copiará todos los archivos en el nuevo directorio &lt;br&gt; mientras que el esqueleto solo creará la estructura del directorio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extrayendo RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>¡La extracción RomFS tuvo éxito!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>La operación se completó con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Error al intentar abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Seleccionar Directorio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>No se pudieron cargar las propiedades del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Ejecutable de Switch (%1);;Todos los archivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Cargar archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir el directorio de la ROM extraída</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Directorio no válido seleccionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>El directorio que ha seleccionado no contiene ningún archivo &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Archivo de Switch Instalable (*.nca *.nsp *.xci);;Archivo de Contenidos Nintendo (*.nca);;Paquete de Envío Nintendo (*.nsp);;Imagen de Cartucho NX (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Instalar Archivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando el archivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>Instalar Resultados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicación del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archivo del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Actualización de la aplicación del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paquete de Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paquete de Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Actualización del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC del Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titulo Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Seleccione el tipo de instalación NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Seleccione el tipo de título en el que desea instalar este NCA como:
+(En la mayoría de los casos, el &apos;Juego&apos; predeterminado está bien).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Fallo en la instalación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>El tipo de título que seleccionó para el NCA no es válido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Archivo no encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Archivo &quot;%1&quot; no encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Mostrar Error</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Falta la cuenta de Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar un caso de prueba de compatibilidad de juegos, debe vincular su cuenta de yuzu.&lt;br&gt;&lt;br/&gt; Para vincular su cuenta yuzu, vaya a Emulación &amp; gt; Configuración &amp; gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Error al abrir la URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>No se puede abrir la URL &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Archivo Amiibo (%1);; Todos los archivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Cargar amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Error al abrir el archivo de datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>No se puede abrir el archivo de Amiibo &quot;%1&quot; para leer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Error al leer el archivo de datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>No se pueden leer completamente los datos de Amiibo. Se esperaban leer %1 bytes, pero solo se pudo leer %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Error al cargar los datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>No se pueden cargar los datos de Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagen PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidad: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidad: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Juego: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Fotogramas: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>El juego que estás intentando cargar requiere de archivos adicionales de su Switch antes de poder jugar. &lt;br/&gt;&lt;br/&gt;Para obtener más información sobre cómo obtener estos archivos, ve a la siguiente página de la wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Volcar archivos del sistema y las fuentes compartidas desde una Consola Switch. &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;¿Quieres volver a la lista de juegos? Continuar con la emulación puede provocar fallos, datos de guardado dañados u otros errores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu no pudo localizar el archivo de sistema de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu no pudo localizar un archivo de sistema de la Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archivo del Sistema No Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Falta Archivo del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu no pudo encontrar las Fuentes Compartidas de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fuentes compartidas no encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Faltan las Fuentes Compartidas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Error Fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu ha encontrado un error fatal, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;¿Cómo cargar el archivo de registro?&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt; La emulación continua puede provocar fallos, datos de guardado dañados u otros errores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Error Fatal Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirma la Clave de Rederivación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Estás a punto de forzar todas tus claves.
+Si no sabes qué es esto,
+es una acción potencialmente destructiva.
+Por favor, asegúrese de que esto
+es lo que desea hacer si es necesario.
+
+ Esto eliminará los archivos de las claves generados automáticamente y volverá a ejecutar el módulo de derivación de claves.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Falta fuses</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation>- Falta BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - Falta BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Falta PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Faltan Componentes de Derivación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Faltan componentes que pueden impedir que la derivación de la clave se complete. &lt;br&gt;Por favor siga &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;la guía de inicio rápido de yuzu&lt;/a&gt; para conseguir todas tus llaves y juegos.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando claves...
+Esto puede llevar unos minutos dependiendo
+del rendimiento de su sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Obtención de claves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecciona el destinatario para volcar el RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Por favor, seleccione los RomFS que desea volcar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>¿Estás seguro de que quieres cerrar yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>¿Estás seguro de que quieres detener la emulación? Cualquier progreso no guardado se perderá.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>La aplicación que se está ejecutando actualmente ha solicitado que yuzu no sea cerrado.
+
+¿Desea cerrarlo de todas formas?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL no está disponible!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu no ha sido compilado con soporte de OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan no está disponible!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu no ha sido compilado con soporte de Vulkan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>¡Error al inicializar OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Tu GPU no soporta OpenGL 4.3 o no tienes instalado el último controlador de la tarjeta gráfica.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>¡Error al inicializar OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Es posible que su GPU no soporta una o más extensiones OpenGL requeridas. Por favor, asegúrese de tener el último controlador de la tarjeta gráfica&lt;br&gt;&lt;br&gt;Extensiones no soportadas:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nombre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Complementos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamaño</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir ubicación de los archivos de guardado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir ubicación de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Eliminar la Actualización Instalada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Eliminar todos los DLC instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Eliminar el Caché de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Eliminar Configuración Personalizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Eliminar Todos los Contenidos Instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Volcar RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar ID de título al portapapeles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navegar a la entrada de BD del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Escanear subdirectorios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Eliminar Directorio de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Mover Hacia Arriba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Mover Hacia Abajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir Ubicación del Directorio</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfecto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>El juego funciona a la perfección sin fallos de audio o gráficos, todas las funciones probadas funcionan según lo previsto
+sin ninguna solución necesaria.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Excelente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>El juego funciona con fallos gráficos o de audio menores y se puede jugar de principio a fin. Puede requerir de
+soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Bien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>El juego funciona con importantes fallos gráficos o de audio, pero el juego se puede jugar de principio a fin con
+soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>El juego funciona, pero con importantes fallos gráficos o de audio. Es imposible avanzar en zonas específicas
+incluso con soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menú</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla
+de inicio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>No inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>El juego se bloquea al intentar iniciar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Sin probar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>El juego todavía no ha sido probado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Haga doble clic para agregar un nuevo directorio a la lista de juegos.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtrar:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Introduce patrón para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Por favor, confirme que estos son los archivos que desea instalar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Instalando una Actualización o DLC reemplazará la que está instalada anteriormente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Instalar archivos al NAND...</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Cargando Shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Cargando Shaders %v de %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tiempo Estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Cargando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Cargando Shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Iniciando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tiempo estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Archivos recientes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Ver</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Herramientas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ayuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Instalar archivos al NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Cargar archivo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Cargar carpeta...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;alir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Detener</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reiniciar claves...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Acerca de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo Ventana Única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Mostrar los Encabezados del Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostrar Barra de Filtro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostrar Barra de Estado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Reiniciar el Tamaño de la Ventana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Pantalla completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Cargar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Informar de compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Abrir la Página de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Abrir la Guía de Inicio Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>Preguntas Más Frecuentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir la carpeta yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Configurar el Juego Actual...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroPerfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos instalados en la SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos instalados en la NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Agregar un Nuevo Directorio de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[no establecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Rotación %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eje %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botón %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[desconocido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clic 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clic 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clic 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clic 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clic 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Eje GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Botón GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[no usado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eje %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Eje GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ha ocurrido un error.
+Inténtelo nuevamente o contacte con el desarrollador del software.
+
+Código del error: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ha ocurrido un error en el %1 a las %2.
+Inténtelo nuevamente o contacte con el desarrollador del software.
+
+Código del error: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ha ocurrido un error.
+Códio del error: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Seleccione un usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Usuarios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selector de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Introducir texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software del Teclado</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Ingrese combinación de teclas de acceso rápido</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pila de Llamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando por mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>tiene receptores: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>manejo del propietario: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando todos los objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando uno de los siguientes objetos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>esperado por ningún hilo</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>corriendo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>listo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>en pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando para el retorno HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>esperando para respuesta IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>esperando variable condicional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando al árbitro de dirección</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>inactivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>muerto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Procesador desconocido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>procesador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidad = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id de hilo = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridad = %1(presente) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>Últimos ticks consecutivos = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>no esperando por mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>esperado por hilo</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Árbol de Espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts
new file mode 100644
index 000000000..83e678f93
--- /dev/null
+++ b/dist/languages/fr.ts
@@ -0,0 +1,4732 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="fr" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>À propos de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu est un émulateur expérimental open-source pour la Nintendo Switch licencié sous GPLv2.0. &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n&apos;avez pas obtenu légalement.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;
+&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site Web&lt;/span&gt;&lt;/a&gt;|&lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Code Source&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributeurs&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licence&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; est une marque déposée de Nintendo. yuzu n&apos;est en aucun cas affilié à Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Communications avec le serveur...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annuler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Touchez le coin supérieur&lt;br&gt;gauche de votre pavé tactile.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Touchez le coin supérieur gauche&lt;br&gt; de votre pavé tactile.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configuration terminée!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Signaler la compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Signaler la compatibilité d&apos;un jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Si vous choisissez à soumettre un test d&apos;essai à la liste de compatibilité yuzu&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Les informations suivantes seront collectées et publiées sur le site :
+&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informations Système (Processeur / Carte Graphique / Système d&apos;exploitation)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;La version de yuzu que vous employez&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Le compte yuzu sous lequel vous êtes connecté&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Parfait</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne parfaitement sans problèmes audio-visuels.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Super</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne avec des problèmes audio-visuels mineurs et est jouable du début jusqu&apos;à la fin. Peut nécessiter des patchs temporaires. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Correct</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne avec des problèmes audio ou graphiques majeurs, mais est jouable du début à la fin avec des modifications.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mauvais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne, mais avec des problèmes graphiques ou audio majeurs. Impossible de progresser à certains endroits spécifiques à cause de ces problèmes même en utilisant des modifications. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu est complètement injouable à cause de problèmes graphique ou audio majeur. Impossible de progresser plus loin que le menu de départ.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Ne se lance pas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu crash au lancement. .&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sans prendre en compte la vitesse ou les performances, À quel point ce jeu est-il bien émulée du début à la fin sur cette version de yuzu ?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Merci de votre Suggestion !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Soumission en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erreur de communication</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Une erreur est survenue en envoyant l&apos;erreur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Suivant</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Moteur de Sortie :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Il s&apos;agit d&apos;un effet de post-traitement qui ajuste la vitesse de l&apos;audio sur celle de l&apos;émulation afin de prévenir les lags du son. Ceci augmente toutefois la latence du son.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activer l&apos;étirement du son</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Appareil audio :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Utiliser le volume global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Régler le volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Précision:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Précis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Peu sûr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Activer le mode de débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Nous recommandons de régler la précision sur &quot;Précis&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Paramètres d&apos;optimisation du CPU non sûrs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Ces réglages réduisent la précision pour la vitesse.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Non-utilisation du FMA (améliorer les performances des CPU sans FMA) </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+Cette option améliore la vitesse en réduisant la précision des instructions fusionnées-multiple-ajoutées sur les CPU sans support FMA natif.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>FRSQRTE et FRECPE plus rapides</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+Cette option améliore la vitesse de certaines fonctions approximatives en virgule flottante en utilisant des approximations natives moins précises.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Les paramètres du CPU ne sont disponibles que lorsque le jeu n&apos;est pas en marche.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Mise en mode de débogage du CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>Le mode de débogage du CPU est uniquement destiné à l&apos;usage des développeurs. Êtes-vous sûr de vouloir l&apos;activer?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Activer les optimisations du CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;
+&lt;b&gt;Pour le débogage uniquement.&lt;/b&gt;
+&lt;br&gt;
+Si vous n&apos;êtes pas sûr de ce qu&apos;elles font, laissez-les toutes activées.
+&lt;br&gt;
+Ces réglages ne prennent effet que lorsque la précision du CPU est en &quot;mode débogage&quot;.
+&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activer le &quot;stub&quot; de GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>S&apos;enregistrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtre de log global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Montrer le journal de logs (Uniquement sur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Ouvrir l&apos;emplacement du journal de logs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Chaîne d&apos;arguments</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Activer les services de rapport verbeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Ceci sera automatiquement mis à zéro quand yuzu se fermera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avancé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Mode Kiosk (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuration de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Liste des jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profils</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Système de fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Contrôles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Raccourcis clavier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Vidéo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Son</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Répertoire de stockage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Carte SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartouche de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Chemin</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inséré</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jeu en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestionnaire de correctif</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Extraire les fichiers NSOs décompressés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Extraire l&apos;ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Racine de chargement de mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Extraire la racine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Mise en cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Répertoire du cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Mettre en cache la métadonnée de la liste des jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Mettre à zéro le cache des métadonnées</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Sélectionner le répertoire NAND émulé...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Sélectionner le répertoire SD émulé...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Sélectionner le chemin de la cartouche de jeu...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Sélectionner le répertoire d&apos;extraction...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Sélectionner le répertoire de chargement de mod...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Sélectionner le répertoire du cache...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Le cache des métadonnées est déjà vide.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;opération s&apos;est terminée avec succès.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Le cache des métadonnées n&apos;a pas pu être supprimé. Il pourrait être utilisé ou non-existant.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Général </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limiter la vitesse en pourcentages</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmer la sortie pendent l&apos;émulation en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Demander l&apos;utilisateur au lancement d&apos;un jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Mettre en pause l’émulation lorsque mis en arrière plan </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Utiliser les shader cache de disque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Utiliser l&apos;émulation GPU asynchrone</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Couleur de L’arrière plan :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Paramètres de raccourcis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Double-cliquez sur une liaison pour la changer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Action</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Raccourci clavier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Séquence de touches conflictuelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Joueur 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Joueur 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Joueur 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Joueur 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Joueur 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Joueur 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Joueur 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Joueur 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avancé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurer les touches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Stick Gauche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Boutons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Stick Droit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configuration de la souris</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Boutons de la souris</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Avant :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Arrière :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Gauche :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Milieu :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Droite :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Effacer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[pas défini]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Réinitialiser </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[appuyez sur une touche]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestionnaire de profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utilisateur actuel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nom d&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Mettre une image</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Ajouter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renommer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Supprimer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>La gestion de profil est accessible uniquement lorsque aucun jeu n&apos;est en cours.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Entrez un nom d&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utilisateurs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Entrez un nom d&apos;utilisateur pour le nouvel utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Entrez un nouveau nom d&apos;utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmez la suppression</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Vous êtes sur le point de supprimer l&apos;utilisateur avec le nom &quot;%1&quot;. Êtes vous sûr?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Sélectionner l&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Images JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Erreur dans la suppression de l&apos;image</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Une erreur est survenue en essayant de changer l&apos;image précédente à : %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erreur dans la suppression du fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Impossible de supprimer le fichier existant : %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erreur dans la création du répertoire d&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Impossible de créer le répertoire %1 pour stocker les images de l&apos;utilisateur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erreur dans la copie de l&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Impossible de copier l&apos;image de %1 à %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>Le BCAT et le chemin que Nintendo utilise pour envoyer des informations au jeux pour encourager sa communauté à débloquer du contenu additionel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Back-end</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apprenez en plus sur le BCAT, le Boxcat, et les événements courants&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Le service boxcat est hors-ligne ou vous n&apos;êtes pas connectés à internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Une erreur est survenu lors du traitement des données boxcat. Contactez les développeurs de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La version de yuzu que vous utilisez est trop vieille ou trop nouvelle pour le serveur. Essayez la dernière mise à jour officiel de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Il n&apos;y a pour le moment aucun événements sur boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu récupere le statut de boxcat le plus récent...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Paramètres Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Note : ceci peut être remplacé quand le paramètre de région est réglé sur automatique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonais (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Anglais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Français (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Allemand (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italien (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espagnol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinois</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coréen (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Néerlandais (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugais (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russe (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taïwanais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Anglais Britannique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Français Canadien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espagnol d&apos;Amérique Latine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinois Simplifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinois Traditionnel (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Customisé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Langue</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Seed RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stéréo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID de la Console :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Mode de sortie Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regénérer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Les paramètres systèmes ne sont accessibles que lorsque le jeu n&apos;est pas en cours.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Ceci remplacera la Switch virtuelle actuelle par une nouvelle. La Switch actuelle ne sera plus récupérable. cela peut entrainer des effets non désirés pendant le jeu. Ceci peut échouer si une configuration de sauvegarde périmée est utilisée. Continuer ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Avertissement</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID de la Console : 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurer l&apos;Écran Tactile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Avertissement : les paramètres de cette page affecte la fonctionnalité intrinsèque de l&apos;écran tactile émulée de yuzu. Toute modification pourrait aboutir à un comportement non désiré, comme par exemple : l&apos;écran tactile qui ne fonctionne plus, de manières partielle ou totale. N&apos;utilisez cette page que si ne vous ne savez ce que vos faites.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Paramètres Tactiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diamètres Tactiles Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Doigt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diamètres Tactiles X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Angle de Rotation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurer</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Note : Changer la langue appliquera votre configuration.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Language de l&apos;interface :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Thème :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Liste de jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Afficher la colonne Des Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Grosseur de l&apos;icône</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texte rangée 1 :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texte rangée 2 :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Anglais</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Service Web yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>En fournissant votre surnom et token, vous acceptez de permettre à yuzu de collecter des données d&apos;utilisation supplémentaires, qui peuvent contenir des informations d&apos;identification de l&apos;utilisateur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Vérifier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Se connecter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Pseudonyme :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qu&apos;est ce que mon token ? </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Télémétrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Partager les données d&apos;utilisation anonymes avec l&apos;équipe yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>En savoir plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Télémétrie :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regénérer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Statut Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Afficher le jeu en cours dans le Statut Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;En savoir plus&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Se connecter&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Quel est mon token ?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Télémétrie : 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Non-spécifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token non vérifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Le token n&apos;a pas été vérifié. Le changement à votre token n&apos;a pas été enregistré.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Vérification...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Échec de la vérification</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Échec de la vérification. Vérifiez si vous avez correctement entrez votre token, et que votre connection internet fonctionne.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Des données anonymes sont collectées&lt;/a&gt; pour aider à améliorer yuzu. &lt;br/&gt;&lt;br/&gt;Voulez-vous partager vos données d&apos;utilisations avec nous ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Télémétrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Échec de la vérification du texte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Chargement du Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Quitter le Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Quitter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Pour quitter l&apos;application web, utilisez les contrôles réserver par le jeu pour sélectionner exit, sélectionner l&apos;option &apos;Quitter le Web Applet&apos; dans la barre menu, ou appuyez sur la touche &apos;Entrer&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Cette version de yuzu à été construit sans le support de QtWebEngine, ce qui veut dire que yuzu ne peut pas correctement afficher le manuel du jeu ou la page web demandée.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Valeur actuelle de la vitesse de l&apos;émulation. Des valeurs plus hautes ou plus basses que 100% indique que l&apos;émulation fonctionne plus vite ou plus lentement qu&apos;une véritable Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Combien d&apos;image par seconde le jeu est en train d&apos;afficher. Ceci vas varier de jeu en jeu et de scènes en scènes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Temps pris pour émuler une image par seconde de la switch, sans compter le limiteur d&apos;image par seconde ou la synchronisation verticale. Pour une émulation à pleine vitesse, ceci devrait être au maximum à 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Effacer les Fichiers Récents</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Avertissement : Le Format de jeu est dépassé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Vous utilisez un format de ROM déconstruite pour ce jeu, qui est donc un format dépassé qui à été remplacer par d&apos;autre. Par exemple les formats NCA, NAX, XCI, ou NSP. Les destinations de ROM déconstruites manque des icônes, des métadonnée et du support de mise à jour.&lt;br&gt;&lt;br&gt;Pour une explication des divers formats Switch que yuzu supporte, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Regardez dans le wiki&lt;/a&gt;. Ce message ne sera pas montré une autre fois.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erreur lors du chargement de la ROM !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Le format de la ROM n&apos;est pas supporté.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Une erreur s&apos;est produite lors de l&apos;initialisation du noyau dédié à la vidéo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d&apos;informations sur l&apos;accès aux logs, veuillez consulter la page suivante : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt; Comment télécharger le fichier des logs &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Une erreur inconnue est survenue. Veuillez consulter le journal des logs pour plus de détails.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Démarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Enregistrer les données</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Donnés du Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erreur dans l&apos;ouverture du dossier %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Le dossier n&apos;existe pas !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erreur lors de l&apos;ouverture des Shader Cache Transferable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Un shader cache pour ce titre n&apos;existe pas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>L&apos;extraction de la RomFS a échoué !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Une erreur s&apos;est produite lors de la copie des fichiers RomFS ou l&apos;utilisateur a annulé l&apos;opération.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Plein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Squelette</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Sélectionnez le mode d&apos;extraction de la RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Veuillez sélectionner la manière dont vous souhaitez que le fichier RomFS soit extrait.&lt;br&gt;Full copiera tous les fichiers dans le nouveau répertoire, tandis que&lt;br&gt;skeleton créera uniquement la structure de répertoires.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraction de la RomFS ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annuler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extraction de la RomFS réussi !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;opération s&apos;est déroulée avec succès.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erreur lors de l&apos;ouverture %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Sélectionner un répertoire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriétés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Les propriétés du jeu n&apos;ont pas pu être chargées.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Exécutable Switch (%1);;Tous les fichiers (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Charger un fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Ouvrir le dossier des ROM extraites</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Destination sélectionnée invalide</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Le répertoire que vous avez sélectionné ne contient pas de fichier &quot;main&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Installation du fichier &quot;%1&quot; ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Application Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archive Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Mise à jour de l&apos;application système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paquet micrologiciel (Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paquet micrologiciel (Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Mise à jour de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titre Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Sélectionner le type d&apos;installation du NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Veuillez sélectionner le type de titre auquel vous voulez installer ce NCA :
+(Dans la plupart des cas, le titre par défaut : &apos;Jeu&apos; est correct.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Échec de l&apos;installation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Le type de titre que vous avez sélectionné pour le NCA n&apos;est pas valide.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Fichier non trouvé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Fichier &quot;%1&quot; non trouvé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Erreur d&apos;affichage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Compte yuzu manquant</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Pour soumettre un test de compatibilité pour un jeu, vous devez lier votre compte yuzu.&lt;br&gt;&lt;br/&gt;Pour lier votre compte yuzu, aller à Emulation &amp;gt; Configuration&amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Fichier Amiibo (%1);; Tous les fichiers (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Charger un Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erreur lors de l&apos;ouverture du fichier de données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Impossible d&apos;ouvrir le fichier Amiibo &quot;%1&quot; à lire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erreur lors de la lecture du fichier de données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Impossible de lire entièrement les données Amiibo. On s&apos;attend à lire %1 octets, mais il n&apos;a pu lire que %2 octets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erreur lors du chargement des données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Impossible de charger les données Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Capture d&apos;écran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Image PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Vitesse : %1% / %2% </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Vitesse : %1% </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jeu : %1 FPS </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame : %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Le jeu que vous essayez de charger a besoin de fichiers additionnels que vous devez extraire depuis votre Switch avant de jouer.&lt;br/&gt;&lt;br/&gt;Pour plus d&apos;information sur l&apos;extraction de ces fichiers, veuillez consulter la page du wiki suivante : &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Extraction des archives système et des Shared Fonts depuis la Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu n&apos;a pas été capable de localiser un système d&apos;archive Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu n&apos;a pas été capable de localiser un système d&apos;archive Switch. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archive système introuvable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Archive Système Manquante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>Yuzu n&apos;a pas été capable de localiser les polices partagées de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Les polices partagées non pas été trouvées</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Polices Partagée Manquante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erreur fatale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d&apos;informations sur l&apos;accès aux logs, veuillez consulter la page suivante : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt; Comment télécharger le fichier des logs &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Erreur Fatale rencontrée</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirmer la réinstallation de la clé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Vous êtes sur le point de réinstaller toutes vos clés.
+Si vous ne savez pas ce que cela veut dire et ce que vous faites,
+cela peut être une action potentiellement destructrice.
+S&apos;il vous plaît assurez-vous que c&apos;est bien ce que vous voulez
+et éventuellement faites des sauvegardes.
+
+Cela supprimera vos fichiers de clé générés automatiquement et ré exécutera le module d&apos;installation de clé.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Installation des clés...
+Cela peut prendre jusqu&apos;à une minute en fonction
+des performances de votre système.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Installation des clés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Sélectionner la cible d&apos;extraction du RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Veuillez sélectionner quel RomFS vous voulez extraire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Êtes vous sûr de vouloir fermer yuzu ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Êtes-vous sûr d&apos;arrêter l&apos;émulation ? Tout progrès non enregistré sera perdu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>L&apos;application en cours a demandé à yuzu de ne pas quitter.
+
+Voulez-vous ignorer ceci and quitter quand même ?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nom</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Extensions</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Type de fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Taille</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Ouvrir l&apos;emplacement des données de sauvegarde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Ouvrir l&apos;emplacement des données des mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Ouvrir le cache de shaders transférable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Extraire la RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copier l&apos;ID du titre dans le Presse-papiers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Accédez à l&apos;entrée GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriétés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scanner les sous-dossiers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Supprimer le répertoire du jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Ouvrir l&apos;emplacement du répertoire</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Parfait</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Le jeu fonctionne parfaitement, de manière fluide sans aucun bug audio ou graphique, toutes les fonctionnalités testées fonctionnent comme prévu sans
+aucune modification nécessaire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Bon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Le jeu fonctionne correctement avec des bugs audio ou graphiques mineurs et est jouable du début à la fin. Nécessite peut être des
+modifications</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Le jeu fonctionne avec des bugs audio ou graphiques majeurs, mais il est jouable du début à la fin avec des modifications.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mauvais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Le jeu fonctionne mais avec des bugs audio et graphiques majeurs. Impossible de progresser dans certaines zones à causes des bugs
+même avec des modifications.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Le jeu est complètement injouable à cause de bugs audio et graphiques. Impossible de progresser plus loin que l&apos;écran de démarrage.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Ne démarre pas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Le jeu crash au lancement.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Non testé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Le jeu n&apos;a pas encore été testé.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Double-cliquez pour ajouter un nouveau dossier à la liste de jeux</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtre :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Entrez un motif à filtrer</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Chargement des shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Chargement des shaders %v sur %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Temps Estimé 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Chargement...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Chargement des shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Lancement...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Temps Estimé %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Fichiers récents</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Émulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Vue</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Outils</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Aide</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Charger un fichier ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Charger un dossier ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>Q&amp;uitter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Démarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Arrêter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Réinitialiser les clés ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>À propos de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Mode fenêtre unique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurer ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Afficher les &quot;Dock Widget Headers&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Afficher la barre de filtre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Afficher la barre d&apos;état</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Plein écran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Redémarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Charger un Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Signaler la compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Ouvrir le dossier de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Prendre une capture d&apos;ecran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Titres installés sur la SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Titres installés sur la NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Titres Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Ajouter un nouveau répertoire de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Maj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[non défini]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Chapeau %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Axe %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Bouton %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[inconnu]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[inutilisé]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Axe %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Une erreur s&apos;est produite.
+Veuillez essayer à nouveau ou contactez le développeur du logiciel.
+
+Code d&apos;Erreur: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Une erreur s&apos;est produite le %1 à %2.
+Veuillez essayer à nouveau ou contactez le développeur du logiciel.
+
+Code d&apos;Erreur: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Une erreur s&apos;est produite.
+Code d&apos;Erreur: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Choisir un utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utilisateurs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Sélecteur de profil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Entrer le texte :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Clavier virtuel</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Entrez une hotkey</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pile d&apos;exécution</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>en attente du &quot;mutex&quot; 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>En attente : %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>propriétaire de la manche : 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>en attente de tous les objets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>en attente d&apos;un des objets suivants</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>prêt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>en pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>en attente du retour de HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>en veille</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>en attente de réponse IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>En attente d&apos;objets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>En attente du &quot;mutex&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>en attente de la variable conditionnelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>En attente de l&apos;adresse arbitre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormant</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>Mort</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>idéal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>cœur %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processeur inconnu %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>Processeur = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>Cœur idéal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>masque d&apos;affinité = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id du fil = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>priorité = %1(courant) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>dernier tick en cours = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>en attente du &quot;mutex&quot;</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>attendu par un fil</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Arbre d&apos;attente</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/it.ts b/dist/languages/it.ts
new file mode 100644
index 000000000..09c832fd3
--- /dev/null
+++ b/dist/languages/it.ts
@@ -0,0 +1,4724 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="it" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Riguardo yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu è un emulatore open source sperimentale per Nintendo Switch sotto licenza GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Questo software non dovrebbe essere utilizzato per giocare a giochi che non hai ottenuto legalmente&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Sito&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Codice Sorgente&lt;/span&gt;&lt;/a&gt; I &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributori&lt;/span&gt;&lt;/a&gt; I &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licenza&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; è un marchio registrato di Nintendo. yuzu non è affiliato a Nintendo in alcun modo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>In comunicazione con il server...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annulla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configurazione completata!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Segnala Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Segnala la Compatibilità di un Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Se dovessi scegliere di inviare un rapporto alla &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Lista Compatibilità di yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Le seguenti informazioni saranno raccolte e visualizzate sul sito: &lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informazioni sull&apos;Hardware (CPU / GPU / Sistema Operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Quale versione di yuzu stai utilizzando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;L&apos;account di yuzu connesso&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfetto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona impeccabilmente senza errori di audio o grafici.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Buono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona con qualche errore grafico minore o glitch nell&apos;audio ed è giocabile dall&apos;inizio alla fine. Potrebbe richiedere qualche metodo alternativo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona e ha errori grafici gravi o glitch nell&apos;audio ma è possibile giocare dall&apos;inizio alla fine con dei metodi alternativi.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Scadente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco presenta alcuni errori audio o video. E&apos; impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Questo gioco è completamente ingiocabile a causa di gravi errori audio o video. E&apos; impossibile proseguire oltre la schermata iniziale.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Non si Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco si blocca quando viene avviato.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indipendentemente da velocità o prestazioni, come ti è sembrato giocare questo gioco dall&apos;inizio alla fine su questa versione di yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Grazie per la tua segnalazione!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Inviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Errore di comunicazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>E&apos; stato riscontrato un errore mandando il Testcase</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Prossimo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motore dell&apos;Output:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>L&apos;effetto post-processing regola la velocità dell&apos;audio per fare in modo che sia uguale alla velocità del gioco e prevenire problemi di audio. Questo tuttavia aumenta la latenza dell&apos;audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Abilita allungamento dell&apos;audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo Audio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usa volume globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Imposta volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Non sicuro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Abilita Modalità Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Impostazioni Ottimizzazione CPU non sicura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Abilita GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro Log Globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostra Console Log (Solo per Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Apri Cartella Log</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Stringa degli Argomenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Grafica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Abilita Servizi di Segnalazione Dettagliata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Verrà ripristinato automaticamente dopo la chiusura di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modalità Kiosk (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configurazione di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Interfaccia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Filesystem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Comandi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Servizi</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Cartelle di Archiviazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Scheda SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartuccia di gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Percorso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Gioco In Uso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestione Patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Estrai NSO Decompressi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Estrai ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Cartella Caricamento Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Cartella di Estrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cartella Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Crea Cache di Metadati Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Elimina Cache dei Metadati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Seleziona Cartella NAND Emulata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Seleziona Cartella Scheda SD Emulata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Seleziona Percorso Cartuccia di Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Seleziona Cartella di Estrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Seleziona Cartella per il Caricamento delle Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Seleziona Cartella della Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>La cache dei metadati è già vuota.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;operazione è stata completata con successo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Impossibile eliminare cache dei metadati. Potrebbe essere in uso o inesistente.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Percentuale Limite Velocità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Richiedi conferma di uscire mentre l&apos;emulazione è in corso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Richiedi utente all&apos;avvio di un gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausa emulazione quando in background</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Impostazioni API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Impostazioni grafiche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usa cache shader su disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usa emulazione GPU asincrona</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Default (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usa il colore di sfondo globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Imposta colore dello sfondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Colore Sfondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Impostazioni Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clicca due volte su un collegamento per cambiarlo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Azione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contesto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Conflitto nella Sequenza di Tasti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Giocatore 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Giocatore 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Giocatore 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Giocatore 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Giocatore 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Giocatore 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Giocatore 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Giocatore 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Connesso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Predefiniti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Giocatore 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Giocatore 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Giocatore 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Giocatore 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Giocatore 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Giocatore 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Giocatore 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Giocatore 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Altro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Tastiera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debug Controller</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configura Input</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Salva</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Nuovo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Elimina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Stick Sinistro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Su</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Sinistra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Destra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Giù</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Premuto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificatore</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Meno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Cattura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Più</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Pulsanti Frontali</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Stick Destro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[in attesa]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilità:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibrazione:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configura Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Pulsanti Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Avanti:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Indietro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Sinistro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Centrale:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Destro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Cancella</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[non impostato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Ripristina Predefinito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[premi un pulsante]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestione Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utente in Uso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Imposta Immagine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Aggiungi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Rinomina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Rimuovi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>La gestione dei profili è disponibile solamente quando nessun gioco è in esecuzione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>&amp;1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Inserisci Nome Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Inserisci un nome utente per il nuovo utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Inserisci un nuovo nome utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Conferma Eliminazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Stai per cancellare l&apos;utente chiamato &quot;%1&quot;. Sei sicuro?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Seleziona Immagine Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Immagini JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Impossibile eliminare l&apos;immagine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Impossibile sovrascrivere l&apos;immagine precedente in: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Impossibile eliminare il file</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Impossibile eliminare il file già esistente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Errore nella creazione della cartella delle immagini dell&apos;utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Impossibile creare la cartella %1 per archiviare le immagini dell&apos;utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Impossibile copiare l&apos;immagine utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Impossibile copiare l&apos;immagine da %1 a %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>Nintendo usa BCAT per inviare dati ai giochi per coinvolgere la comunità e sbloccare contenuti aggiuntivi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Scopri di più riguardo BCAT, Boxcat, ed Eventi in Corso&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Il servizio boxcat è offline o potresti non essere connesso a internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>E&apos; stato riscontrato un errore nell&apos;elaborazione dei dati degli eventi di boxcat. Contatta gli sviluppatori di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La versione di yuzu che stai usando è troppo vecchia o troppo nuova per questo server. Prova ad aggiornare all&apos;ultima release ufficiale di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Non ci sono eventi in corso su boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>yuzu sta recuperando lo stato attuale di boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Impostazioni di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>Cina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Corea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: questo può essere ignorato quando le impostazioni della regione sono su auto-select</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Giapponese (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglese (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francese (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Tedesco (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spagnolo (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Cinese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Olandese (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portoghese (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglese Britannico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francese Canadese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Spagnolo Latino Americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Cinese Semplificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Cinese Tradizionale (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Personalizzato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Lingua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Seed RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID Console:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modalità di output del sonoro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Rigenera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Le impostazioni di sistema sono disponibili solamente quando nessun gioco è in esecuzione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Questo rimpiazzerà la tua Switch virtuale con una nuova. La tua Switch virtuale non sarà recuperabile. Questo potrebbe avere effetti indesiderati nei giochi. Questo potrebbe fallire, se usi un salvataggio non aggiornato. Desideri continuare?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Attenzione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID Console: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Nuovo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Elimina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Rinomina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Elimina Punto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Pulsante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Nuovo Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Inserisci il nome per il nuovo profilo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Elimina Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Elimina profilo %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Rinomina Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Nuovo nome:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[premi pulsante]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configura Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Attenzione: Le impostazioni in questa pagina influenzano il funzionamento interno del touchscreen emulato di yuzu. Cambiarle potrebbe risultare in effetti indesiderati, ad esempio il touchscreen potrebbe non funzionare parzialmente o totalmente. Utilizza questa pagina solo se sei consapevole di ciò che stai facendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parametri Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diametro Touch Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diametro Touch X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Angolo di Rotazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Ripristina Predefiniti</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Cambiare lingua applicherà i cambiamenti fatti alla configurazione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Lingua Interfaccia:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostra Colonna Add-On</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Dimensione Icona:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Testo Riga 1:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Testo Riga 2:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Screenshots</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglese</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Servizio Web di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Fornendo i tuoi nome utente e token, permetti a yuzu di raccogliere dati di utilizzo, che potrebbero includere informazioni di identificazione utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verifica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qual è il mio token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Condividi dati sull&apos;utilizzo anonimamente con il team di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Per saperne di più</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Rigenera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostra il Gioco Attuale nel tuo Stato di Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Per saperne di più&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registrati&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Qual è il mio token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Non specificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token non verificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Nome utente e token non verificati. I cambiamenti al tuo nome utente e/o token non sono stati salvati.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifica in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verifica fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verifica fallita. Controlla di aver inserito correttamente il tuo username e token, e che la tua connessione ad internet sia funzionante.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Vengono raccolti dati anonimi&lt;/a&gt; per aiutarci a migliorare yuzu. &lt;br/&gt;&lt;br/&gt;Desideri condividere i tuoi dati di utilizzo con noi?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Verificazione Testo Fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Caricamento Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Esci dal Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Esci</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Per uscire dall&apos;applicazione web, usa i pulsanti provvisti dal gioco per uscire, seleziona l&apos;opzione &apos;Esci dal Web Applet&apos; nel menu in alto, o premi il tasto &apos;Invio&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Questa versione di yuzu è stata compilata senza supporto a QtWebEngine, quindi yuzu non potrà aprire il manuale di gioco o delle pagine web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocità corrente dell&apos;emulazione. Valori più alti o più bassi di 100% indicano che l&apos;emulazione sta funzionando più velocemente o lentamente rispetto a una Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quanti frame al secondo il gioco mostra attualmente. Questo varia da gioco a gioco e da situazione a situazione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo utilizzato per emulare un frame della Switch, non contando i limiti ai frame o il v-sync.
+Per un&apos;emulazione alla massima velocità, il valore dev&apos;essere al massimo 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Elimina File Recenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Avviso Formato di Gioco Obsoleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Stai usando una cartella con dentro una ROM decostruita come formato per avviare questo gioco, è un formato obsoleto ed è stato sostituito da altri come NCA, NAX, XCI o NSP. Le ROM decostruite non hanno icone, metadata e non supportano gli aggiornamenti. &lt;br&gt;&lt;br&gt;Per una spiegazione sui vari formati di Switch che yuzu supporta, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;controlla la nostra wiki&lt;/a&gt;. Questo messaggio non verrà più mostrato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Errore nel caricamento della ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Il formato della ROM non è supportato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>E&apos; stato riscontrato un errore nell&apos;inizializzazione del core video.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu ha riscontrato un errore nell&apos;esecuzione del video core, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Come Caricare Il File Log&lt;/a&gt;. Assicurati di avere gli ultimi driver della tua GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>E&apos; stato riscontrato un errore sconosciuto. Visualizza il log per maggiori dettagli.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Dati di Salvataggio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Dati delle Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Errore nell&apos;Apertura della Cartella %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>La cartella non esiste!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Errore nell&apos;Apertura della Cache Shader Trasferibile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Una cache di shader per questo titolo non esiste.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Contenuti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Aggiorna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Estrazione RomFS Fallita!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>C&apos;è stato un errore nella copia dei file del RomFS o l&apos;operazione è stata annullata dall&apos;utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Scheletro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Seleziona Modalità Estrazione RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Seleziona come vorresti estrarre il RomFS. &lt;br&gt;Completo copierà tutti i file in una nuova cartella mentre&lt;br&gt;scheletro creerà solamente le cartelle e le sottocartelle.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Estrazione RomFS in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annulla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Estrazione RomFS Riuscita!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;operazione è stata completata con successo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Errore nell&apos;Apertura di %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Seleziona Cartella</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Proprietà</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Le proprietà del gioco non sono potute essere caricate.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Eseguibile Switch (%1);;Tutti i File (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carica File</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Apri Cartella ROM Estratta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Cartella Selezionata Non Valida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>La cartella che hai selezionato non contiene un file &quot;main&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Installazione del file &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Applicazione di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archivio di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Aggiornamento Applicazione di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacchetto Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacchetto Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Aggiornamento di Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titolo Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Seleziona il Tipo di Installazione NCA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Seleziona il tipo del file NCA da installare:
+(Nella maggior parte dei casi, il predefinito &apos;Gioco&apos; va bene.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installazione Fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Il tipo che hai selezionato per l&apos;NCA non è valido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>File non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>File &quot;%1&quot; non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Messaggio di Errore</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Account di yuzu non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Per segnalare la compatibilità di un gioco, devi collegare il tuo account yuzu. &lt;br&gt;&lt;br/&gt;Per collegare il tuo account yuzu, vai su Emulazione &amp;gt;
+Configurazione &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>File Amiibo (%1);; Tutti I File (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carica Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Errore nell&apos;apertura del file dati Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Impossibile aprire e leggere il file Amiibo &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Errore nella lettura dei dati del file Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Impossibile leggere tutti i dati dell&apos;Amiibo. E&apos; stato possibile leggere solamente %2 byte di %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Errore nel caricamento dei dati dell&apos;Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Impossibile caricare i dati dell&apos;Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Cattura Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Immagine PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocità: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocità: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Gioco: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Il gioco che stai provando a caricare richiede ulteriori file che devono essere estratti dalla tua Switch prima di poter giocare. &lt;br/&gt;&lt;br/&gt;Per maggiori informazioni sull&apos;estrazione di questi file, visualizza la seguente pagina della wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Estrazione di Archivi di Sistema e Font Condivisi da una Console Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Vuoi uscire e tornare alla lista dei giochi? Continuare l&apos;emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu non ha potuto individuare un archivio di sistema della Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu non ha potuto individuare un archivio di sistema della Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archivio di Sistema Non Trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Archivio di Sistema Mancante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu non ha potuto individuare i font condivisi della Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Font Condivisi Non Trovati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Font Condivisi Mancanti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Errore Fatale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu ha riscontrato un errore fatale, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Come Caricare Il File Log&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Vuoi uscire e tornare alla lista dei giochi? Continuare l&apos;emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Errore Fatale riscontrato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Conferma Riderivazione Chiave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Stai per forzare la ri-derivazione di tutte le tue chiavi.
+Se non sai cosa significa o cosa stai per fare,
+questa azione potrebbe causare gravi problemi.
+Assicurati di volerlo fare
+e facoltativamente fai dei backup.
+
+Questo eliminerà i tuoi file di chiavi autogenerati e ri-avvierà il processo di derivazione delle chiavi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivazione chiavi...
+Questa operazione potrebbe durare fino a un minuto in
+base alle prestazioni del tuo sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivazione Chiavi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Seleziona Target dell&apos;Estrazione del RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Seleziona quale RomFS vorresti estrarre.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Sei sicuro di voler chiudere yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Sei sicuro di voler fermare l&apos;emulazione? Tutti i progressi non salvati verranno perduti.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>L&apos;applicazione in uso ha richiesto a yuzu di non uscire.
+
+Desideri uscire comunque?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Add-on</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo di file</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Dimensione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Apri Cartella Dati di Salvataggio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Apri Cartella Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Apri Cache Shader Trasferibile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Estrai RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copia il Title ID negli Appunti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Vai alla pagina di GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Proprietà</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scansiona Sottocartelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Rimuovi Cartella Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Apri Cartella</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfetto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Il gioco funziona perfettamente senza alcuni glitch audio o video, tutte le funzionalità testate funzionano come dovrebbero senza alcun metodo alternativo necessario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Buono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Il gioco presenta qualche errore grafico minore o glitch nell&apos;audio ed è giocabile dall&apos;inizio alla fine. Potrebbe richiedere dei metodi alternativi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>l gioco presenta alcuni errori gravi audio o video. E&apos; impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Scadente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Il gioco funziona, ma presenta alcuni errori gravi audio o video. È impossibile proseguire in alcune aree a causa della presenza di glitch
+persino utilizzando metodi alternativi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Il gioco è completamente ingiocabile a causa di errori gravi audio o video. È impossibile proseguire oltre la Schermata
+Iniziale.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Non si Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Il gioco si blocca quando viene avviato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Non Testato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Il gioco non è ancora stato testato.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clicca due volte per aggiungere una nuova cartella alla lista dei giochi</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Inserisci pattern per filtrare</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>387 / 1628 Shader Caricate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Caricamento di %v su %m Shader</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo Stimato 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Caricamento...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>%1 / %2 Shader Caricate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Avvio in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo Stimato %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;File</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>File Recenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Visualizza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Strumenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Aiuto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carica File...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carica Cartella...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Esci</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Ri-inizializzando le chiavi...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Riguardo yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modalità Finestra Singola</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configura...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Visualizza le Intestazioni del Dock dei Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostra Barra Filtri</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostra Barra Stato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Schermo Intero</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Riavvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carica Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Segnala Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Apri Cartella yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Cattura Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Titoli SD Installati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Titoli NAND Installati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Titoli di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Aggiungi Nuova Cartella Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[non impostato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Asse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Pulsante %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[sconosciuto]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clicca 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clicca 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clicca 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clicca 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clicca 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Assi GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Pulsanti GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[inutilizzato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Asse %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Assi GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>E&apos; stato riscontrato un errore.
+Riprova o contatta gli sviluppatori del software.
+
+Codice Errore: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>E&apos; stato riscontrato un errore su %1 a %2.
+Riprova o contatta gli sviluppatori del software.
+
+Codice Errore: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>E&apos; stato riscontrato un errore.
+Codice Errore: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Seleziona un utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selettore Profili</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Inserisci testo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Tastiera Software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Inserisci una hotkey</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stack chiamata</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>attende il mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ha dei waiter: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>proprietario handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>attende tutti gli oggetti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>attende uno dei seguenti oggetti</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>atteso da nessun thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>in funzione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>pronto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>in pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>attende ritorno dell&apos;HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormendo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>attende una risposta dell&apos;IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>attende degli oggetti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>attende un mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>aspettando la condition variable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>attende un indirizzo arbitro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormiente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>core %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processore sconosciuto %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processore = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>core ideale = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>affinity mask = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id thread = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>priorità = %1(corrente) / %2(normale)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>last running ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>non attende un mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>atteso dal thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wait Tree</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts
new file mode 100644
index 000000000..4c3870603
--- /dev/null
+++ b/dist/languages/ja_JP.ts
@@ -0,0 +1,4752 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="ja_JP" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>yuzuについて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzuはGPLv2.0の下で提供されている任天堂Switchの実験的なオープンソースエミュレータです。&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;このソフトウェアは違法に入手したゲームを遊ぶために使用されるべきではありません。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ウェブサイト&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ソースコード&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;貢献者&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ライセンス&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;任天堂 Switch&amp;quot;は任天堂の登録商標です。 yuzuは任天堂と提携しているわけではありません。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>サーバと通信中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>キャンセル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>タッチパッドの左上を&lt;br&gt;タッチして下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>次にタッチパッドの右下を&lt;br&gt;タッチして下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>設定が完了しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>ゲームの互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;テストケースを&lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu互換性リスト&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;に送信した場合、以下の情報が収集され、サイトに表示されます:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;ハードウェア情報(CPU/GPU/OS)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;実行中のyuzuバージョン&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;接続中のyuzuアカウント&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>パーフェクト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームはオーディオやグラフィックの不具合なしに完全に動作します。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>グレート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>NG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>イントロ/メニューまで</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;グラフィック、またはオーディオの重大な不具合のため、ゲームはプレイ不可能です。スタート画面から先に進めることは出来ません。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>起動せず</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームは起動時にクラッシュします。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;速度やパフォーマンスに関係なく、このゲームはこのバージョンのyuzuで最初から最後までどの程度うまく実行できていますか?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>ご協力ありがとうございます!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>送信中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>通信エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>テストケースを送信中にエラーが発生しました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>次</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>出力エンジン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>後処理エフェクトは、エミュレーション速度と一致するようにオーディオ速度を調整し、オーディオの乱れを防ぐのに役立ちます。ただしオーディオの遅延が長くなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>オーディオストレッチの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>オーディオデバイス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>共通の音量設定を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>音量設定:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>正確度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>正確</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>不安定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>デバッグモードを使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>正確度は”正確”に設定することを推奨します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>CPU最適化設定(不安定)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>これらの設定は高速化のために正確性を犠牲にします。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>FMAの統合を解除(FMAを持たないCPUにおいて速度を改善する)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;このオプションは、ネイティブFMAのサポートを持たないCPUで融合積和命令の精度を下げることで速度を改善させます。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>高速FRSQRTEとFRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;このオプションは、精度の低いネイティブ近似を使用することにより、一部の近似浮動小数点関数の速度を改善させます。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>CPU設定は、ゲームが実行されていない場合にのみ使用できます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>CPUをデバッグモードに設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>CPUデバッグモードは、開発者による使用のみを目的としています。有効にしてもよろしいですか?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>CPUの最適化を切り替え</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;デバッグ用&lt;/b&gt;
+ &lt;br&gt;
+ これらの機能が何を意味するのか分からない場合は、すべて有効にしておいてください。
+ &lt;br&gt;
+ これらの設定は、CPU精度が”デバッグモード”の場合にのみ有効になります。
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>インラインページテーブルの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;これを有効にすると、PageTable :: pointersへのアクセスが発行されたコードにインライン化されます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;これを無効にすると、すべてのメモリアクセスが強制的にMemory :: Read / Memory :: Write関数を経由します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>ブロックリンクの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;この最適化では、宛先PCが静的な場合、発行された基本ブロックが他の基本ブロックに直接ジャンプできるようにすることで、ディスパッチャーのルックアップを回避します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>リターンスタックバッファの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;この最適化は、BL命令の潜在的な戻りアドレスを追跡することにより、ディスパッチャーの検索を回避します。これは、実際のCPUのリターンスタックバッファで何が起こるかを概算します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>高速ディスパッチャの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;2層のディスパッチシステムを有効にします。アセンブリで記述されたより高速なディスパッチャには、ジャンプ先の小さなMRUキャッシュが最初に使用されます。それが失敗した場合、ディスパッチはより遅いC ++ディスパッチャーにフォールバックします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>コンテキスト消去の有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;CPUコンテキスト構造への不要なアクセスを削減するIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>定数伝播の有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;一定の伝播を伴うIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>その他の最適化を有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;その他のIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>ミスアライメントチェックの削減を有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;有効にすると、アクセスがページの境界を越えたときにのみ、ミスアライメントがトリガーされます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;無効にすると、ミスアライメントされたすべてのアクセスでミスアライメントがトリガーされます。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>CPU設定は、ゲームが実行されていない場合にのみ使用できます。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDBスタブの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>ポート:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>ログの記録</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>グローバルログフィルター</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>ログコンソールの表示(Windowsのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>ログ出力フォルダを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>自作</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>引数文字列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>オンにすると、グラフィックAPIはより遅いデバッグモードになります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>グラフィックデバッグの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>オンにすると、マクロJust In Timeコンパイラが無効になります。有効にすると、ゲームの実行が遅くなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>マクロJITの無効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>ダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>詳細なレポートサービスを有効にする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>yuzuを終了したときに自動的にリセットされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>キオスク (クエスト) モード</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>デバッグコントローラ設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzuの設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>ゲームリスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>システム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>ファイルシステム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>コントロール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>ホットキー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>デバッグ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>拡張グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>サービス</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>ストレージディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SDカード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>ゲームカード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>パス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>挿入する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>現在のゲーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>パッチマネージャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>解凍されたNSOをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>ExeFSをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Modロードディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>ダンプディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>キャッシュ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>キャッシュディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>ゲームリストのメタデータをキャッシュする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>メタデータのキャッシュをクリアする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>NANDディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>SDディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>ゲームカードのパスを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>ダンプディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Modロードディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>キャッシュディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>メタデータのキャッシュはすでに空です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作は成功しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>メタデータのキャッシュを削除できませんでした。使用中、または存在していない可能性があります。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>速度パーセントの制限</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>マルチコアCPUエミュレーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>エミュレーション実行中の終了確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>ゲーム起動時に確認を表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>バックグラウンド時にエミュレーションを停止</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>未使用時にマウスを非表示</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>デバイス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>グラフィック設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>ディスクシェーダキャッシュを使用する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>非同期GPUエミュレーションを使用する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>アスペクト比:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>デフォルト (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>4:3を強制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>21:9を強制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>ウィンドウに合わせる</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>共通の背景色を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>背景色の設定:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>背景色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGLグラフィックデバイス</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>拡張グラフィック設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>正確性レベル:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSyncは画面のティアリングを防ぎますが、一部のグラフィックカードはVSyncが有効になっているとパフォーマンスが低下します。パフォーマンスの違いに気づかない場合は、有効にしてください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>VSyncを使用(OpenGLのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>これを有効にすると、シェーダースタッターが減少します。サポートされているNvidiaデバイスでOpenGLアセンブリシェーダーを有効にします(NV_gpu_program5が必要です)。この機能は実験的なものです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>アセンブリシェーダーを使用します(実験的、Nvidia OpenGLのみ)。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>非同期シェーダーコンパイルを有効にします。これにより、シェーダースタッターが減少する場合があります。この機能は実験的なものです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>非同期シェーダー構築を使用します(実験的)。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>高速CPU時間を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>異方性フィルタリング:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>ホットキー設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>変更するには項目をダブルクリックしてください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>すべて消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>アクション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>ホットキー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>コンテキスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>キー設定の衝突</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>入力されたキーシーケンスは既に割り当てられています:%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>デフォルトのキーシーケンスは既に割り当てられています:%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>プレイヤー1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>プレイヤー2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>プレイヤー3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>プレイヤー4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>プレイヤー5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>プレイヤー6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>プレイヤー7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>プレイヤー8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>コンソールモード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>接続解除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>バイブレーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>モーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>コントローラ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joyconカラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>プレイヤー1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>L本体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Lボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>R本体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Rボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>プレイヤー2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>プレイヤー3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>プレイヤー4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>プレイヤー5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>プレイヤー6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>プレイヤー7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>プレイヤー8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>その他</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>キーボード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>タッチスクリーン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>マウス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>モーション / タッチ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>コントローラのデバッグ</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>コントローラの接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>プロコントローラ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>デュアルジョイコン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>ジョイコン左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>ジョイコン右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>携帯</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>入力デバイス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>すべて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>キーボード / マウス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>セーブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>新規</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>左スティック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>上</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>押下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>変更</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>範囲</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>デッドゾーン:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>変更範囲:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Dパッド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>マイナス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>キャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>プラス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>ホーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>ボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>右スティック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>デッドゾーン:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>変更範囲:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[待機中]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>モーション / タッチ設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>モーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>モーションプロバイダ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>感度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>タッチ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>タッチプロバイダ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>キャリブレーション:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>ボタンマッピングの使用:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>モーションとタッチ入力を提供するために、Cemuhook互換のUDP入力ソースを使用します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>サーバー:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>ポート:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>パッド:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>パッド1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>パッド2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>パッド3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>パッド4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>詳細情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>テスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>マウス(右クリック)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>エミュレータウィンドウ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;詳細情報&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>テスト中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>設定中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>テスト成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>サーバーからデータ受信成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>テスト失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>サーバーから有効なデータを受信できませんでした。&lt;br&gt;サーバーが正しくセットアップされ、アドレスとポートが正しいことを確認してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDPテスト、またはキャリブレーション設定が実行中です。&lt;br&gt;完了するまでお待ちください。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>マウス設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>マウスボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>進む:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>戻る:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>左:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>中央:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>右:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[未設定]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[キーを押す]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>ダイアログ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>タイトルID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>ファイル名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>フォーマット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>バージョン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>サイズ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>開発者</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>アドオン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>システム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>拡張グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>共通設定を使用(%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>パッチ名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>バージョン</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>プロファイルマネージャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>現在のユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>ユーザー名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>画像を設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>追加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>リネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>プロファイル管理はゲームが実行されていない場合のみ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>ユーザー名を入力</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>ユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>新しいユーザーのユーザー名を入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>新しいユーザー名を入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>削除確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>ユーザー名”%1”を削除しようとしています。間違いありませんか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>ユーザー画像を選択 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG画像 (*.jpg *.jpeg) </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>画像削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>既存画像の上書き時にエラーが発生しました: %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>ファイル削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>ファイルを削除できませんでした: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>ユーザー画像ディレクトリ作成失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>ユーザー画像保存ディレクトリ”%1”を作成できませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>ユーザー画像コピーエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>画像を”%1”から”%2”へコピー出来ませんでした。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCATは任天堂のゲームへのデータ送信方法であり、コミュニティに参加して追加コンテンツのロックを解除します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCATバックエンド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BCAT, Boxcat, 現在のイベントについての詳細情報&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Boxcatサービスはオフラインまたはインターネットに接続されていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Boxcatイベントデータを処理中にエラー発生しました。yuzu開発者に連絡してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>使用中のyuzuのバージョンは、サーバと比較して新しすぎまたは古すぎます。最新の公式リリースに更新してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Boxcatには現在イベントがありません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>現在のBoxcatイベント</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>yuzuが最新のBoxcat状況を取得中です...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>システム設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>リージョン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>自動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>キューバ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>エジプト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>アイルランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>グリニッジ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>香港</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>アイスランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>イラン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>イスラエル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>ジャマイカ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>日本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>クェゼリン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>リビア</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>ナバホ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>ポーランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>ポルトガル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>シンガポール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>トルコ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>ユニバーサル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>ズールー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>ヨーロッパ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>オーストラリア</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>韓国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>タイムゾーン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>注意:地域設定が自動選択の場合、設定が上書きされる可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>日本(日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>英語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>フランス(フランス語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>ドイツ(ドイツ語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>イタリア(イタリア語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>スペイン(スペイン語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>韓国(韓国語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>オランダ(オランダ語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>ポルトガル(ポルトガル語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>ロシア(ロシア語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>イギリス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>カナダ・フランス語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>ラテンアメリカ・スペイン語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>簡体字中国語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>繁体字中国語(正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>カスタムRTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>言語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG速度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>モノラル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>ステレオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>サラウンド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>コンソールID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>サウンド出力モード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>再作成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>システム設定はゲームが実行されていない場合のみ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>現在の仮想スイッチが新しいものに置換されます。現在の仮想スイッチは復旧できません。ゲームに予期せぬ影響を与える可能性があります。古い設定などを使うと失敗するかもしれません。続行しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>コンソールID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>タッチスクリーンマッピング設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>マッピング:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>新規</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>リネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>下の領域をクリックしてポイントを追加し、ボタンを押してバインドします。
+ポイントをドラッグして位置を変更するか、テーブルのセルをダブルクリックして値を編集します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>ポイント削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>ボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>新規プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>新規プロファイルの名称を入力</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>プロファイル削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>プロファイル%1を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>プロファイルリネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>新規名称:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[キーを押す]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>タッチスクリーン設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>警告:このページの設定は、yuzuのタッチスクリーンエミュレートの内部動作に影響します。これらを変更すると、タッチスクリーンが部分的に機能しなくなったりするなど、望ましくない動作が生じる可能性があります。それらを理解した上で、このページを使用してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>タッチパラメータ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>タッチ直径Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>指</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>タッチ直径X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>回転角</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>注意:言語を変更した時点で、設定が適用されます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>UI言語:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>テーマ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>ゲームリスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>アドオン列を表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>アイコンサイズ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>1行目:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>2行目:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>スクリーンショット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>スクリーンショットの保存場所を確認する(Windowsのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>スクリーンショットパス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>スクリーンショットパスを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>英語</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Webサービス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>ユーザー名とトークンを入力すると、yuzuがユーザー識別情報を含む可能性がある追加の統計情報データを収集することを許可することに同意したと見なされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>検証</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>サインアップ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>トークン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>ユーザー名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>私のトークンはなんですか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>テレメトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>匿名統計情報データをyuzuチームと共有</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>詳細情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>テレメトリID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>再作成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discordプレゼンス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Discordステータスに実行中のゲームを表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;詳細情報&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;サインアップ&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;私のトークンはなんですか?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>テレメトリID:0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>未設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>トークンは検証されていません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>トークンは検証されていません。トークンの変更はまだ保存されていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>検証中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>検証失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>検証に失敗しました。トークンを正しく入力したこと、およびインターネット接続が機能していることを確認してください。</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>yuzuを改善するための&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;匿名データが収集されました&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;統計情報データを共有しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>テレメトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>テキストチェック失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Webアプレットをロード中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Webアプレットを終了する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>終了</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Webアプリケーションを終了するには、ゲームが提供する&quot;終了&quot;を選択するか、メニューの&quot;Webアプレットを終了する&quot;を選択するか、&quot;Enter&quot;キーを押してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Webアプレット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>使用中のyuzuはQtWebEngineをサポートしていないため、ゲームの説明書やWebページを正しく表示できません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>ビルド中のシェーダー数</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>現在のエミュレーション速度。値が100%より高いか低い場合、エミュレーション速度がSwitchより速いか遅いことを示します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>ゲームが現在表示している1秒あたりのフレーム数。これはゲームごと、シーンごとに異なります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Switchフレームをエミュレートするのにかかる時間で、フレームリミットやV-Syncは含まれません。フルスピードエミュレーションの場合、最大で16.67ミリ秒になります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTICORE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>最近使用したファイルを消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>古いゲームフォーマットの警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>このゲームでは、分解されたROMディレクトリフォーマットを使用しています。これは、NCA、NAX、XCI、またはNSPなどに取って代わられた古いフォーマットです。分解されたROMディレクトリには、アイコン、メタデータ、およびアップデートサポートがありません。&lt;br&gt;&lt;br&gt;yuzuがサポートするSwitchフォーマットの説明については、&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;wikiをチェックしてください&lt;/a&gt;。このメッセージは二度と表示されません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>ROMロード中にエラーが発生しました!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>このROMフォーマットはサポートされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>ビデオコア初期化中にエラーが発生しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>ビデオコアの実行中にyuzuでエラーが発生しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;ログファイルをアップロードする方法&lt;/a&gt;。最新のグラフィックドライバを使用していることを確認して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>ROMロードエラー!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>不明なエラーが発生しました。詳細はログを確認して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>開始</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>データのセーブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Modデータ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>”%1”フォルダを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>フォルダが存在しません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>シェーダキャッシュを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>このタイトル用のシェーダキャッシュは存在しません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>コンテンツ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>アップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>エントリ削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>インストールされているゲーム%1を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>削除成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>インストールされたゲームを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>%1削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>ゲームはNANDにインストールされておらず、削除できません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>インストールされたアップデートを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>このタイトルのアップデートはインストールされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>このタイトルにはDLCがインストールされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1にインストールされたDLCを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>転送可能なシェーダーキャッシュを削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>カスタムゲーム設定を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>ファイル削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>転送可能なシェーダーキャッシュの削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>転送可能なシェーダーキャッシュが正常に削除されました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>転送可能なシェーダーキャッシュを削除できませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>カスタム設定の削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>このタイトルのカスタム設定は存在しません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>カスタムゲーム設定を正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>カスタムゲーム設定の削除に失敗しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFSの解析に失敗しました!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>RomFSファイルをコピー中にエラーが発生したか、ユーザー操作によりキャンセルされました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>フル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>スケルトン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>RomFSダンプモードの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>RomFSのダンプ方法を選択してください。&lt;br&gt;”完全”はすべてのファイルが新しいディレクトリにコピーされます。&lt;br&gt;”スケルトン”はディレクトリ構造を作成するだけです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFSを解析中・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>キャンセル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS解析成功!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作は成功しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>”%1”を開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>ディレクトリの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>ゲームプロパティをロード出来ませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch実行ファイル (%1);;すべてのファイル (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>ファイルのロード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>展開されているROMディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>無効なディレクトリが選択されました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>選択されたディレクトリに”main”ファイルが見つかりませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>インストール可能なスイッチファイル (*.nca *.nsp *.xci);;任天堂コンテンツアーカイブ (*.nca);;任天堂サブミッションパッケージ (*.nsp);;NXカートリッジイメージ (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>ファイルのインストール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>&quot;%1&quot;ファイルをインストールしています・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>インストール結果</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>システムアプリケーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>システムアーカイブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>システムアプリケーションアップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>ファームウェアパッケージ(Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>ファームウェアパッケージ(Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>ゲーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>ゲームアップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>ゲームDLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>差分タイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>NCAインストール種別を選択・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>インストールするNCAタイトル種別を選択して下さい:
+(ほとんどの場合、デフォルトの”ゲーム”で問題ありません。)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>インストール失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>選択されたNCAのタイトル種別が無効です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>ファイルが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>ファイル”%1”が存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>続行</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>表示エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>yuzuアカウントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>ゲームの互換性テストケースを送信するには、yuzuアカウントをリンクする必要があります。&lt;br&gt;&lt;br/&gt;yuzuアカウントをリンクするには、エミュレーション > 設定 > Web から行います。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>URLオープンエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>URL&quot;%1&quot;を開けません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>amiiboファイル (%1);;すべてのファイル (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>amiiboのロード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>amiiboデータファイルを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>amiiboデータファイル”%1”を読み込めませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>amiiboデータファイルを読み込み中にエラーが発生した</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>amiiboデータを完全には読み取ることができませんでした。%1バイトを読み込もうとしましたが、%2バイトしか読み取れませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>amiiboデータ読み込み中にエラーが発生しました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>amiiboデータをロードできませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>スクリーンショットのキャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG画像 (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>速度:%1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>速度:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>ゲーム:%1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>フレーム:%1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>ロードしようとしているゲームはプレイする前にスイッチからダンプされた追加のファイルを必要とします。&lt;br/&gt;&lt;br/&gt;これらのファイルのダンプの詳細については、次のWikiページを参照してください:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;スイッチコンソールからのシステムアーカイブと共有フォントをダンプする&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzuはSwitchのシステムアーカイブ &quot;%1&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzuはSwitchのシステムアーカイブ &quot;%1&quot; &quot;%2&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>システムアーカイブが見つかりません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>システムアーカイブが見つかりません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzuはSwitchの共有フォント &quot;%1&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>共有フォントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>共有フォントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>致命的なエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzuが致命的なエラーを検出しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください。&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;ログファイルをアップロードする方法&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>致命的なエラー発生</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>キーの再取得確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>すべてのキーを再作成しようとしています。
+これが何を意味するのか分からない場合、または何をしようとしているのか分からない場合、
+これは破壊的な可能性がある実行です。
+これがあなたが望むものであることを確認し、
+そしてオプションでバックアップを作成します。
+
+これにより、自動生成されたキーファイルが削除され、キー導出モジュールが再実行されます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>ヒューズがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - BOOT0がありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - BCPKG2-1-Normal-Mainがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - PRODINFOがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>派生コンポーネントがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>コンポーネントが見つからず、キーの導出が完了しない可能性があります。&lt;br&gt;ゲームとキーを取得するために、&lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;yuzuクイックスタートガイド&lt;/a&gt;の手順に従って下さい。&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>キーを作成中...
+システムのパフォーマンスによっては
+1分以上かかります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>派生キー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>RomFSダンプターゲットの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>ダンプしたいRomFSを選択して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>yuzuを終了してもよろしいですか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>エミュレーションを停止してもよろしいですか?セーブされていない進行状況は失われます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>現在動作中のアプリケーションはyuzuに終了しないよう要求しています。
+
+無視してとにかく終了しますか?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGLは使用できません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzuはOpenGLサポート付きでコンパイルされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkanは使用できません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzuはVulkanサポート付きでコンパイルされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>OpenGL4.3初期化エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>GPUがOpenGL 4.3をサポートしていないか、最新のグラフィックスドライバーではありません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>OpenGL初期化エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>GPUが1つ以上の必要なOpenGL拡張機能をサポートしていない可能性があります。最新のグラフィックドライバを使用していることを確認してください。&lt;br&gt;&lt;br&gt;サポートされていない拡張機能:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>互換性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>アドオン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>ファイル種別</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>サイズ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>セーブデータディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Modデータディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>シェーダキャッシュを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>インストールされているアップデートを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>全てのインストールされているDLCを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>シェーダーキャッシュを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>カスタム設定を削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>全てのインストールされているコンテンツを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>RomFSをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>タイトルIDをクリップボードへコピー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>GameDBエントリを表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>サブフォルダをスキャンする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>ゲームディレクトリを削除する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ 上へ移動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ 下へ移動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>ディレクトリの場所を開く</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>パーフェクト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>ゲームはオーディオ、またはグラフィックの不具合なしで完璧に動作し、回避策なしで期待通りに動作します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>グレート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>NG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>イントロ/メニュー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>グラフィック、またはオーディオの重大な不具合のため、ゲームはプレイ不可能です。スタート画面から先に進めることは出来ません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>起動せず</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>ゲームは起動時にクラッシュしました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>未テスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>ゲームはまだテストされていません。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>新しいゲームリストフォルダを追加するにはダブルクリックしてください。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>フィルター:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>フィルターパターンを入力</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>これらがインストールするファイルであることを確認してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>アップデート、またはDLCをインストールすると、以前にインストールしたものが上書きされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>インストール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>ファイルをNANDへインストール</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>シェーダをロード中 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>シェーダをロード中 %v / %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>予想時間 5分4秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>ロード中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>シェーダをロード中 %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>起動中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>予想時間 %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>ファイル(&amp;F)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>最近使用したファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>エミュレーション(&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>表示(&amp;V)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>デバッグ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>ツール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>ヘルプ(&amp;H)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>ファイルをNANDへインストール...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>ファイルをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>フォルダをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>終了(&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>実行(&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>中断(&amp;P)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>停止(&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>キーの再初期化...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>yuzuについて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>単一ウィンドウモード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>設定...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>ドックウィジェットヘッダーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>フィルターバーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>ステータスバーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>ウィンドウサイズをリセット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>フルスクリーン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>再起動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>amiiboをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Modページを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>クイックスタートガイドを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>yuzuディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>スクリーンショットをキャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>現在のゲームの設定...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>マイクロプロファイル</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>インストール済みSDタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>インストール済みNANDタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>システムタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>新しいゲームディレクトリを追加する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[未設定]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>十字キー %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>軸 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>ボタン %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[不明]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>クリック0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>クリック1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>クリック2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>クリック3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>クリック4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC Axis %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC Button %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[未使用]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>軸 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC Axis %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>エラーが発生しました。
+もう一度試す、またはソフト開発者に連絡してください。
+
+エラーコード: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>%2 の %1 でエラーが発生しました。
+もう一度試す、またはソフト開発者に連絡してください。
+
+エラーコード: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>エラーが発生しました。
+エラーコード: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>ユーザー選択:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>ユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>プロファイル選択</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>テキストを入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>ソフトウェアキーボード</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>ホットキーを入力</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>コールスタック</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>ミューテックス 0x%1 待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>待機:%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>オーナーハンドル: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>全オブジェクト待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>以下のオブジェクトのうちの一つを待機中</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>スレッドなしで待機</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>実行中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>準備完了</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>中断</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>HLEリターン待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>休止中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>IPC返答待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>オブジェクト待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>ミューテックス待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>条件変数待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>アドレス決定待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>休止</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>死亡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>理想的</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>コア %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>不明なプロセッサ %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>プロセッサ = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>イデアルコア = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>アフィニティマスク = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>スレッドID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>優先度 = %1(現在) / %2(通常)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>最終実行刻み = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>ミューテックス待ちではない</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>スレッドによる待機</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>待ちツリー</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts
new file mode 100644
index 000000000..0f5178458
--- /dev/null
+++ b/dist/languages/nl.ts
@@ -0,0 +1,4719 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="nl" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Over yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu is een experimentele open-bron emulator voor de Nintendo Switch, in licentie gegeven onder GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;De software zou niet gebruikt moeten worden om games te spelen die je niet legaal verkregen hebt.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Broncode&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Bijdragers&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licentie&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is een handelsmerk van Nintendo. yuzu is op geen enkele manier met Nintendo verbonden.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Communiceren met de server...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annuleren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Rapporteer Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Rapporteer Game Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Als je kiest een test case op te sturen naar de &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu compatibiliteitslijst&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, zal de volgende informatie worden verzameld en getoond op de site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Informatie (CPU / GPU / Besturingssysteem)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Welke versie van yuzu je draait&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Het verbonden yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfect</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De game werkt foutloos, zonder geluid of grafische problemen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Goed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt met kleine grafische of geluid problemen en is speelbaar van start tot eind. Kan enkele tijdelijke oplossingen nodig hebben.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt met grote grafische of geluid problemen, maar is speelbaar van start tot eind met tijdelijke oplossingen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Slecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt, maar met grote grafische of geluid problemen. Specifieke gebieden kunnen niet worden uitgespeeld vanwege problemen, zelfs met tijdelijke oplossingen. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is compleet onspeelbaar dankzij grote grafische of geluid problemen. Onmogelijk voorbij het startscherm te komen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Start Niet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De game crasht wanneer je het probeert op te starten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Onafhankelijk van snelheid of prestaties, Hoe goed speelt de game van start tot eind op deze versie van yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Bedankt voor je inzending!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Inzenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Communicatiefout</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Er was een fout tijdens het insturen van de Testcase.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Volgende</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Geluid</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Output Engine:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Dit nabewerkings effect past de snelheid van het geluid zodanig aan dat de snelheid van de emulatie en van het geluid op elkaar afgestemd zijn. Dit zorgt er wel voor dat de latency van het geluid hoger word.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Geluid Rekking Aanzetten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Geluid Apparaat:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDB Stub Aanzetten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Poort:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Loggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globale Log Filter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Laat Log Venster Zien (Alleen voor Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Open Log Locatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Argumenten Rij</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Gebruik Uitgebreide Rapporteer Services</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Deze optie hersteld wanneer je yuzu afsluit.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Geavanceerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk (Quest) Modus</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu Configuratie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Game Lijst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Systeem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profielen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Bestandssysteem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Bediening</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Sneltoetsen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Geluid</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Opslag Folders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD Kaart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamekaart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Ingevoerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Huidig Spel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Patch Beheer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dump Uitgepakte NSO&apos;s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Dump ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod Laad Root</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Dump Root</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cache Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Cache Spel Lijst Metadata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Herstel Metadata Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecteer Geëmuleerde NAND Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecteer Geëmuleerde SD Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecteer Gamekaart Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecteer Dump Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecteer Mod Laad Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecteer Cache Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>De metadata cache is al leeg.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>De operatie is succesvol voltooid.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>De metadata cache kon niet worden verwijderd. Het wordt mogelijk gebruikt of bestaat niet.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limiteer Snelheid Percentage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Bevestig sluiten terwijl emulatie nog bezig is</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Vraag voor gebruiker bij het opstartten van het spel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pauzeer Emulatie wanneer yuzu op de achtergrond openstaat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Gebruik schijf shader cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Gebruik asynchroon GPU emulatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Achtergrondkleur:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Sneltoets Instellingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Dubbel-klik op een binding om het te veranderen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Actie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Sneltoets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Context</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Ongeldige Toets Volgorde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Speler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Speler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Speler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Speler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Speler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Speler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Speler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Speler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Geavanceerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configureer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configureer Invoer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Linker Stick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Gezicht Knoppen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Rechter Stick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configureer Muis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Muisknoppen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Voorwaarts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Terug:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Links:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Middel:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Rechts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Herstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[niet ingesteld]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Herstel Standaardwaarde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[druk op knop]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Profiel Beheer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Huidige Gebruiker</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Gebruikersnaam</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Selecteer Afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Voeg Toe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Hernoem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Verwijder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Profiel beheer is alleen beschikbaar wanneer het spel niet bezig is.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Voer een Gebruikersnaam in</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Gebruikers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Voer een gebruikersnaam in voor de nieuwe gebruiker:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Voer nieuwe gebruikersnaam in:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Bevestig Verwijdering</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Je staat op het punt een gebruiker met de naam &quot;%1&quot; te verwijderen. Weet je het zeker?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Selecteer gebruiker&apos;s foto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG foto&apos;s (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Fout tijdens verwijderen afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Er is een fout opgetreden bij het overschrijven van de vorige afbeelding in: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Fout tijdens verwijderen bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Kan bestaand bestand niet verwijderen: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Fout tijdens het maken van de map met afbeeldingen van de gebruiker</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Fout tijdens het maken van map %1 om gebruikersafbeeldingen in te bewaren.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Fout tijdens het kopiëren van de gebruiker afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Kan afbeelding niet kopiëren van %1 naar %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT is Nintendo&apos;s manier om informatie naar games te versturen om de gemeenschap te stimuleren of om nieuwe inhoud te ontgrendelen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Leer meer over BCAT, Boxcat, en actuele Evenementen&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>De boxcat service is offline of je bent niet verbonden met het internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Er was een fout tijdens het processen van de boxcat evenement data. Contacteer de yuzu ontwikkelaars.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>De versie van yuzu die je gebruikt is te nieuw of te oud voor de server. Probeer te updaten naar de nieuwste officiële release van yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Er zijn momenteel geen evenementen op boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu haalt de laatste boxcat-status op...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Systeeminstellingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Noot: dit kan worden overschreven wanneer de regio instelling op automatisch selecteren staat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japans (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Engels (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Frans (Français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Duits (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiaans (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spaans (Español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinees (正體中文 / 简体中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreaans (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Nederlands (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugees (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russisch (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Brits Engels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Canadees Frans</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Latijns Amerikaans Spaans</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Vereenvoudigd Chinees</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Traditioneel Chinees (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Handmatige RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Taal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG Seed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Console ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Geluid uitvoer mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM jjjj u:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Herstel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Systeeminstellingen zijn enkel toegankelijk wanneer er geen game draait.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Dit vervangt je huidige virtuele Switch met een nieuwe. Je huidige virtuele Switch kan dan niet meer worden hersteld. Dit kan onverwachte effecten hebben in spellen. Dit werkt niet als je een oude config savegame gebruikt. Doorgaan?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Waarschuwing</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Console ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configureer Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Waarschuwing: Instellingen in deze pagina hebben invloed op de interne werking van yuzu&apos;s geemuleerde touchscreen. Veranderingen kunnen ongewenste resultaten hebben, zoals ervoor zorgen dat het touchscreen half of niet werkt. Gebruik deze pagina enkel als je weet wat je doet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Touch Parameters</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Touch Diameter Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Vinger</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Touch Diameter X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Rotatiehoek</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Herstel Standaardwaardes</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Notitie: De taal veranderen past uw configuratie toe.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Interface taal:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Thema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Game Lijst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Toon Add-Ons Kolom</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Icoon Grootte:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Rij 1 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Rij 2 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Engels</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Door je gebruikersnaam en token te geven, ga je akkoord dat yuzu extra gebruiksdata verzameld, waaronder mogelijk gebruikersidentificatie-informatie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verifieer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registreer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Gebruikersnaam:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Wat is mijn token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Deel anonieme gebruiksdata met het yuzu team</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Leer meer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Telemetrie ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenereer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Toon huidige game in je Discord status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Leer meer&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registreer&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Wat is mijn token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Telemetrie ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Niet gespecificeerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token niet geverifieerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Token is niet geverifieerd. De verandering aan uw token zijn niet opgeslagen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifiëren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificatie mislukt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificatie mislukt. Check dat uw token correct is en dat uw internet werkt.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Annonieme gegevens worden verzameld&lt;/a&gt; om yuzu te helpen verbeteren. &lt;br/&gt;&lt;br/&gt; Zou je jouw gebruiksgegevens met ons willen delen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Tekst Check Is Gefaald</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Web Applet Laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Web Applet Afsluit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Afsluiten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Om de webtoepassing af te sluiten, gebruik de bedieningselementen van het spel om afsluiten te selecteren, selecteer de optie &apos;Web Applet Afsluiten&apos; in de menubalk of druk op de &apos;Enter&apos; toets.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Deze versie van yuzu is gebouwd zonder ondersteuning van QtWebEngine, wat betekent dat yuzu de gevraagde spelhandleiding of webpagina niet correct kan weergeven.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Huidige emulatie snelheid. Waardes hoger of lager dan 100% betekent dat de emulatie sneller of langzamer loopt dan de Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Hoeveel frames per seconde de game op dit moment weergeeft. Dit zal veranderen van game naar game en van scène naar scène.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tijd gebruikt om een frame van de Switch te emuleren, waarbij framelimiteren of v-sync niet wordt meegerekend. Voor emulatie op volledige snelheid zou dit maximaal 16.67 ms zijn.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Recente Bestanden Verwijderen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Waarschuwing Verouderd Spel Formaat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Je gebruikt gedeconstrueerd ROM map formaat voor dit Spel, dit is een verouderd formaat en is vervangen door formaten zoals NCA, NAX, XCI of NSP. Gedeconstrueerd ROM map heeft geen iconen, metadata en update understeuning.&lt;br&gt;&lt;br&gt;Voor een uitleg over welke Switch formaten yuzu ondersteund, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;kijk op onze wiki&lt;/a&gt;. Dit bericht word niet nog een keer weergegeven.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Fout tijdens het laden van een ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Het formaat van de ROM is niet ondersteunt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Er is een fout opgetreden tijdens het initialiseren van de videokern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu is een fout tegengekomen tijdens het uitvoeren van de video kern, kijk alstublieft naar de log for meer details. Voor meer informatie op het vinden van de log, kijk alstublieft naar de volgende pagina : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;hoe upload je een log bestand&lt;/a&gt;. Bevestig dat je dat laaste grafische driver hebt voor je GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Een onbekende fout heeft plaatsgevonden. Kijk in de log voor meer details.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Save Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Fout tijdens het openen van %1 folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Folder bestaat niet!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Fout Bij Het Openen Van Overdraagbare Shader Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Er bestaat geen shader cache voor deze game</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS Extractie Mislukt!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Er was een fout tijdens het kopiëren van de RomFS bestanden of de gebruiker heeft de operatie geannuleerd.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Vol</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Skelet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecteer RomFS Dump Mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Selecteer alstublieft hoe je de RomFS wilt dumpen.&lt;br&gt;Volledig kopieërd alle bestanden in een map terwijl &lt;br&gt; skelet maakt alleen het map structuur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFS uitpakken...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annuleren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS Extractie Geslaagd!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>De operatie is succesvol voltooid.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Fout bij openen %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecteer Map</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Eigenschappen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>De eigenschappen van de game kunnen niet geladen worden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch Executable (%1);;Alle bestanden (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Laad Bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Open Gedecomprimeerd ROM Map</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Ongeldige Map Geselecteerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>De map die je hebt geselecteerd bevat geen &apos;main&apos; bestand.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Bestand &quot;%1&quot; Installeren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Systeem Applicatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Systeem Archief</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Systeem Applicatie Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Filmware Pakket (Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Filmware Pakket (Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Game</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Game Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Game DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecteer NCA Installatie Type...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Selecteer het type titel hoe je wilt dat deze NCA installeerd:
+(In de meeste gevallen is de standaard &apos;Game&apos; juist.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installatie Mislukt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Het type title dat je hebt geselecteerd voor de NCA is ongeldig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Bestand niet gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Bestand &quot;%1&quot; niet gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Doorgaan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Fout Weergave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Je yuzu account mist</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Om game campatibiliteit te raporteren, moet je je yuzu account koppelen.&lt;br&gt;&lt;br/&gt; Om je yuzu account te koppelen, ga naar Emulatie &amp;gt; Configuratie &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo Bestand (%1);; Alle Bestanden (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Laad Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Fout tijdens het openen van het Amiibo gegevens bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Kan Amiibo bestand &quot;%1&quot; niet lezen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Fout tijdens het lezen van het Amiibo gegevens bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Kan de volledige Amiibo gegevens niet lezen. Verwacht om %1 bytes te lezen, maar het is alleen mogelijk om %2 bytes te lezen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Fout tijdens het laden van de Amiibo data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Kan de Amiibo gegevens niet laden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot Vastleggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG afbeelding (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Snelheid: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Snelheid: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Game: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>De game die je probeert te laden heeft extra bestanden nodig van je Switch voordat je het kan spelen. &lt;br/&gt;&lt;br/&gt;Voor meer informatie over het dumpen van deze bestanden, volg alsjeblieft onze wiki pagina: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Het dumpen van Systeem Archieven en de Gedeelde Lettertypen van een Switch console &lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Wil je terug gaan naar de game lijst? Verdergaan met de emulatie zal misschien gevolgen hebben als vastlopen, beschadigde opslag data, of andere problemen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu was niet in staat om de Switch systeem archieven te vinden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu was niet in staat om de Switch systeem archieven te vinden. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Systeem Archief Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Systeem Archief Mist</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu was niet in staat om de Switch shared fonts te vinden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Shared Fonts Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Gedeelde Lettertypes Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Fatale Fout</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu is een fatale fout tegengekomen, zie de log voor meer details. Voor meer informatie over toegang krijgen tot de log, zie de volgende pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Hoe upload je een log bestand&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Zou je terug willen naar de game lijst? Doorgaan met emulatie kan resulteren in vastlapen, corrupte save gegevens, of andere problemen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Fatale Fout opgetreden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Bevestig Sleutel Herafleiding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Je bent op het punt al je sleutels geforceerd opnieuw te verkrijgen.
+Als je niet weet wat dit doet of wat je aan het doen bent,
+dit is potentieel een vernietigende actie.
+Zorg ervoor dat je zeker weet dat dit is wat je wilt doen
+en optioneel maak backups.
+
+Dit zal je automatisch gegenereerde sleutel bestanden verwijderen en de sleutel verkrijger module opnieuw starten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Dit zal misschien een paar minuten duren gebaseerd
+op je systeem&apos;s performatie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Sleutels afleiden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecteer RomFS Dump Doel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Selecteer welke RomFS je zou willen dumpen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Weet je zeker dat je yuzu wilt sluiten?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Weet je zeker dat je de emulatie wilt stoppen? Alle onopgeslagen voortgang will verloren gaan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>De momenteel actieve toepassing heeft yuzu gevraagd om niet af te sluiten.
+
+Wilt u dit omzeilen en toch afsluiten?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Naam</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Toevoegingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Bestands type</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Grootte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Open Locatie Van Save Gegevens </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Open Mod Data Locatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Open Overdraagbare Shader Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Dump RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Kopieer Titel ID naar Klembord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navigeer naar GameDB inzending</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Eigenschappen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scan Subfolders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Verwijder Game Directory</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Open Directory Locatie</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfect</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Game functioneerd foutloos, zonder auditieve of grafische glitches. Alle geteste functionaliteit werkt zoals bedoeld zonder dat er tijdelijke oplossingen nodig zijn.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Goed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Game werkt met kleine grafische of auditieve glitches en is speelbaar van start tot eind. Kan enkele workarounds nodig hebben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Game werkt met grove grafische of auditieve glitches, maar is speelbaar van start tot eind met workarounds</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Slecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Game werkt, maar met grove grafische of auditieve glitches. Specifieke gebieden kunnen niet worden uitgespeeld vanwege glitches, zelfs met workarounds.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Game is compleet onspeelbaar dankzij grove grafische of auditieve glitches. Onmogelijk voorbij het startscherm te gaan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Start Niet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>De Game crasht wanneer hij probeert op te starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Niet Getest</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Deze Game is nog niet getest.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Dubbel-klik om een ​​nieuwe map toe te voegen aan de lijst met games</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Voer patroon in om te filteren:</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Shaders Laden 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>%v Shaders Laden van de %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Geschatte Tijd 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Shaders Laden %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Starten...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Geschatte Tijd %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Recente Bestanden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Weergeven</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Hulpmiddelen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Help</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Laad Bestand...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Laad Folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>A&amp;fsluiten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pauzeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Sleutels opnieuw initialiseren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Over yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Enkel Venster Modus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configureren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Dock Widget Rubriek Weergeven</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Laat filter balk zien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Laat status balk zien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Volledig Scherm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Herstart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Laad Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Raporteer Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Open yuzu Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot Vastleggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfiel</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Geïnstalleerde SD Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Geïnstalleerde NAND Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Systeem Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Voeg Nieuwe Game Map Toe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[niet aangegeven]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Axis %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Knop %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[onbekend]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[ongebruikt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Axis %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Er is een fout opgetreden.
+Probeer het opnieuw of neem contact op met de ontwikkelaar van de software.
+
+Fout Code: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Er is een fout opgetreden bij %1 en %2.
+Probeer het opnieuw of neem contact op met de ontwikkelaar van de software.
+
+Fout Code: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Er is een fout opgetreden.
+Fout Code: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecteer een gebruiker:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Gebruikers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Profiel keuzeschakelaar</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Voer tekst in:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software Toetsenbord</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Voer een hotkey in</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Call stack</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>wachten op mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>heeft wachtende: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>eigenaar handvat: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>wachten op alle objecten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>wachten op een van de volgende objecten</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>Bezig met uitvoeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>klaar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>gepauzeerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>wachten op HLE terugkeer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>slapen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>wachten op IPC antwoord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>wachten op objecten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>wachten op mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>wachten op conditie variabele</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>wachten op adres arbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>slapend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>dood</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideaal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>kern %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Onbekende processor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>ideale kern = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>affiniteit masker = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>draad id = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioriteit = %1(huidige) / %2(normaal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>laatste lopende ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>Niet wachtend op mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>Wachtend door draad</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wacht Boom</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts
new file mode 100644
index 000000000..4cd9b6fca
--- /dev/null
+++ b/dist/languages/pl.ts
@@ -0,0 +1,4713 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pl" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>O yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu jest eksperymentalnym emulatorem o otwartym kodzie źródłowym dla Nintendo Switch w ramach licencji GLPv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;To oprogramowanie nie powinno być używane do gier, których nie nabyłeś legalnie.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Strona&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Kod źródłowy&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Współautorzy&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licencja&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; jest znakiem towarowym Nintendo. yuzu nie jest stowarzyszony w żaden sposób z Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Anuluj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Konfiguracja zakończona!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Zgłoś kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Zgłoś kompatybilność gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Jeśli postanowisz wysłać wyniki testu na &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;listę kompatybilności yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, następujące informacja zostaną zebrane i wyświetlone na stronie:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informacja o sprzęcie komputerowym (procesor / karta graficzna / system operacyjny)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Wersja yuzu, której używasz&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Połączone konto yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Idealna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa bez zarzutu, bez błędów graficznych lub dźwiękowych.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Świetna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z pomniejszymi błędami dźwiękowymi lub graficznymi, jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>W porządku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z większymi błędami dźwiękowymi lub graficznymi, ale jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Zła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z większymi błędami dźwiękowymi lub graficznymi. Niemożliwe jest przejście konkretnych miejsc nawet z obejściami.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra w żadnym stopniu nie jest grywalna ze względu na poważne błędy graficzne lub dźwiękowe. Niemożliwe jest przejście ekranu początkowego.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Nie uruchomia się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra zawiesza się podczas próby uruchomienia się.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pomijając prędkość lub wydajność, jak dobrze ta gra zachowuje się w tej wersji yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Dziękujemy za Twoją opinię!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Wysyłanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Błąd komunikacyjny</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Wystąpił błąd podczas wysyłania wyników testu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Następny</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Silnik wyjścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Ten efekt post-przetwarzania dostosowuje prędkość dźwięku tak, aby zgadzała się z dźwiękiem emulowanym pomagając przy tym zapobiec zacinaniu się dźwięku. Zwiększa jednak opóźnienie dźwięku.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Włącz rozciąganie dźwięku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Urządzenie dźwiękowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Ustaw głośność:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Głośność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Niebezpieczne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Włącz Debug Mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Ustawienia CPU są dostępny gdy gra nie jest uruchomiona </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Włącz namiastkę GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globalny filtr rejestrów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Pokaż okno rejestrów (tylko Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Otwórz miejsce rejestrów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Linijka argumentu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Ustawienia yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista Gier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>System plików</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Sterowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Skróty klawiszowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Wyszukiwanie usterek</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Usługi</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Karta SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamecard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Ścieżka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Obecna Gra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulacja CPU Wielordzeniowa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Potwierdź wyjście podczas emulacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Ustawienia API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Urządzenie:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Ustawienia Graficzne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Format obrazu:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Domyślne (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Wymuś 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Wymuś 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Rozciągnij do Okna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Ustaw kolor tła:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Kolor tła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Urządzenie graficzne OpenGL</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Ustawienia Skrótów Klawiszowych</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Wyczyść wszystko</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Przywróć domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Akcja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Skrót klawiszowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Kontekst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Przywróć ustawienia domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Gracz 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Gracz 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Gracz 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Gracz 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Gracz 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Gracz 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Gracz 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Gracz 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Tryb Konsoli</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Zadokowany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Niezadokowany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Wibracje</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Ruch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Ustaw</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Kontrolery</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Połączone</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Ustawienie wejścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Kolory Joyconów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Gracz 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Gracz 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Gracz 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Gracz 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Gracz 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Gracz 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Gracz 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Gracz 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Inne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Klawiatura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Ekran dotykowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Ruch / Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Konfiguruj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debuguj kontroler</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Ustawienie wejścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Połacz Kontroler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Para Joyconów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Lewy Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Prawy Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Urządzenie Wejściowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Klawiatura/Mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Zapisz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Nowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Usuń</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Lewa gałka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Góra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Lewo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Prawo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Dół</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Naciśnięty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modyfikator</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Zasięg</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Martwa strefa: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Zasięg Modyfikatora: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>D-Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Minus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Przednie klawisze</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Prawa gałka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Martwa strefa: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Zasięg Modyfikatora: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[oczekiwanie]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Konfiguruj Ruch / Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Ruch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Czułość:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Kalibracja:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Konfiguruj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Serwer:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Dowiedz się więcej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Test</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Myszka (Prawy Przycisk)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Konfigurowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Test Udany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Ustaw mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Przyciski myszy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Do przodu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Do tyłu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Lewy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Środkowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Prawy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[nie ustawione]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Przywróć ustawienie domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[naciśnij przycisk]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Informacje</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nazwa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>Identyfikator gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nazwa pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Format</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Wersja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Rozmiar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Deweloper</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Dodatki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Grafika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Wersja</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Menedżer Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Obecny użytkownik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nazwa Użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Ustaw zdjęcie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Dodaj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Zmień nazwę</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Usuń</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Menedżer Profili nie jest dostępny gdy gra jest uruchomiona.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Wpisz nazwę użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Użytkownicy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Wprowadź nazwę dla nowego użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Wpisz nową nazwę użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Potwierdź usunięcie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Zamierzasz usunąć użytkownika &quot;%1&quot;. Jesteś pewien?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Ustaw zdjęcie użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Obrazki JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Bład usunięcia zdjęcia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Błąd podczas próby nadpisania poprzedniego zdjęcia dla: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Błąd usunięcia pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Nie można usunąć istniejącego pliku: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Błąd podczas tworzenia folderu ze zdjęciem użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Nie można utworzyć ścieżki %1 do przechowywania zdjęć użytkownika.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Błąd kopiowania zdjęcia użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Nie można skopiować zdjęcia z %1 do %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Ustawienia systemu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Region:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Automatyczny</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egipt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Irlandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Izrael</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamajka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japonia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polska</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugalia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Uwaga: można to zmienić, gdy ustawienie regionu jest wybierane automatycznie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japoński (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Angielski (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francuski (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Niemiecki (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Włoski (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Hiszpański (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chiński</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreański (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Duński (Holandia)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugalski (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Rosyjski (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Tajwański</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Angielski (Brytyjski)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Fancuski (Kanada)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Hiszpański (Latin American)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chiński (Uproszczony)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chiński tradycyjny (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Język</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Ziarno RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Indentyfikator konsoli:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Tryb wyjścia dźwięku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Wygeneruj ponownie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Ustawienia systemu są dostępne tylko wtedy, gdy gra nie jest uruchomiona.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>To zamieni twojego obecnego Switch&apos;a z nowym. Twojego obecnego Switch&apos;a nie będzie można przywrócić. To może wywołać nieoczekiwane problemy w grach. To może nie zadziałać, jeśli używasz nieaktualnej konfiguracji zapisu gry. Kontynuować?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Ostrzeżenie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Identyfikator konsoli: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Skonfiguj ekran dotykowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Ostrzeżenie: Ustawienia na tej stronie mają wpływ na działanie emulowanego ekranu dotykowego Yuzu. ch zmiana może spowodować niepożądane zachowanie, takie jak częściowo lub całkowicie nie działający ekran dotykowy. Powinieneś/naś używać tej strony tylko wtedy, gdy wiesz, co robisz.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parametry dotyku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Średnica dotyku Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Palec</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Średnica dotyku X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Kąt rotacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Przywróć domyślne</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista Gier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Angielski</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Usługa internetowa yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Podając swoją nazwę użytkownika i token, zgadzasz się na umożliwienie yuzu zbierania dodatkowych danych o użytkowaniu, które mogą zawierać informacje umożliwiające identyfikację użytkownika.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Zweryfikuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Zaloguj się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nazwa użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Czym jest mój token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Udostępniaj anonimowe dane o użytkowaniu zespołowi yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Dowiedz się więcej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Identyfikator telemetrii:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Wygeneruj ponownie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Obecność na discordzie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Pokazuj obecną grę w twoim statusie Discorda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Dowiedz się więcej&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Zaloguj się&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Czym jest mój token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Identyfikator telemetrii: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Weryfikowanie...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Weryfikowanie nieudane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dane anonimowe są gromadzone&lt;/a&gt; aby ulepszyć yuzu. &lt;br/&gt;&lt;br/&gt;Czy chcesz udostępnić nam swoje dane o użytkowaniu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Sprawdzanie tekstu nie powiodło się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Aktualna prędkość emulacji. Wartości większe lub niższe niż 100% wskazują, że emulacja działa szybciej lub wolniej niż Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Ile klatek na sekundę gra aktualnie wyświetla. To będzie się różnić w zależności od gry, od sceny do sceny.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Czas potrzebny do emulacji klatki na sekundę Switcha, nie licząc ograniczania klatek ani v-sync. Dla emulacji pełnej szybkości powinno to wynosić co najwyżej 16,67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Usuń &quot;Ostatnie pliki&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>OSTRZEŻENIE! Nieaktualny format gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Używasz zdekonstruowanego formatu katalogu ROM dla tej gry, który jest przestarzałym formatem, który został zastąpiony przez inne, takie jak NCA, NAX, XCI lub NSP. W zdekonstruowanych katalogach ROM brakuje ikon, metadanych i obsługi aktualizacji.&lt;br&gt;&lt;br&gt; Aby znaleźć wyjaśnienie różnych formatów Switch obsługiwanych przez yuzu,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt; sprawdź nasze wiki&lt;/a&gt;. Ta wiadomość nie pojawi się ponownie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Błąd podczas wczytywania ROMu!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Ten format ROMu nie jest wspierany.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Wystąpił błąd podczas inicjowania rdzenia wideo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu napotkał błąd podczas działania rdzenia wideo, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Jak przesłać plik log? &lt;/a&gt;Upewnij się, że masz najnowsze sterowniki karty graficznej.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Wystąpił nieznany błąd. Więcej informacji można znaleźć w pliku log.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Błąd podczas otwarcia folderu %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Folder nie istnieje!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Wypakowanie RomFS nieudane!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Wystąpił błąd podczas kopiowania plików RomFS lub użytkownik anulował operację.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Wybierz tryb zrzutu RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Proszę wybrać w jaki sposób chcesz, aby zrzut pliku RomFS został wykonany. &lt;br&gt;Pełna kopia ze wszystkimi plikami do nowego folderu, gdy &lt;br&gt;skielet utworzy tylko strukturę folderu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Wypakowywanie RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Anuluj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Wypakowanie RomFS zakończone pomyślnie!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Operacja zakończona sukcesem.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Wybierz folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Właściwości tej gry nie mogły zostać załadowane.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Plik wykonywalny Switcha (%1);;Wszystkie pliki (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Załaduj plik...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Otwórz folder wypakowanego ROMu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Wybrano niewłaściwy folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Folder wybrany przez ciebie nie zawiera &apos;głownego&apos; pliku.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalowanie pliku &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplikacja systemowa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archiwum systemu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Aktualizacja aplikacji systemowej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paczka systemowa (Typ A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paczka systemowa (Typ B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Gra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Aktualizacja gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Dodatek do gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Tytuł Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Wybierz typ instalacji NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Wybierz typ tytułu, do którego chcesz zainstalować ten NCA, jako:
+(W większości przypadków domyślna &quot;gra&quot; jest w porządku.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Instalacja nieudana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Typ tytułu wybrany dla NCA jest nieprawidłowy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Nie znaleziono pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Nie znaleziono pliku &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Kontynuuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Brakuje konta Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Aby przesłać test zgodności gry, musisz połączyć swoje konto yuzu.&lt;br&gt;&lt;br/&gt; Aby połączyć swoje konto yuzu, przejdź do opcji Emulacja &amp;gt; Konfiguracja &amp;gt; Sieć.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Plik Amiibo (%1);;Wszyskie pliki (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Załaduj Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Błąd otwarcia pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Nie można otworzyć pliku Amiibo &quot;%1&quot; do odczytu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Błąd podczas odczytu pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Nie można w pełni odczytać danych Amiibo. Oczekiwano odczytu %1 bajtów, ale był on w stanie odczytać tylko %2 bajty.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Błąd podczas ładowania pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Nie można załadować danych Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Zrób zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Obrazek PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Prędkość: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Prędkość: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Gra: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Klatka: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Gra, którą próbujesz wczytać, wymaga dodatkowych plików z Switch&apos;a, które zostaną zrzucone przed graniem.&lt;br/&gt;&lt;br/&gt; Aby uzyskać więcej informacji na temat wyrzucania tych plików, odwiedź następującą stronę wiki:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt; Zrzut archiw systemu i udostępnionych czcionek z konsoli Nintendo Switch&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy.
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archiwum systemu nie znalezione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Czcionki nie zostały znalezione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Fatalny błąd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu napotkał błąd, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Jak przesłać plik log&lt;/a&gt;?&lt;br/&gt;&lt;br/&gt; Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Potwierdź ponowną aktywacje klucza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Zamierzasz zmusić wszystkie swoje klucze do ponownej aktywacji.
+Jeśli nie wiesz, co to oznacza i co robisz,
+jest to potencjalnie destrukcyjne działanie.
+Upewnij się, że to jest to, czego chcesz
+i opcjonalnie tworzyć kopie zapasowe.
+
+Spowoduje to usunięcie wygenerowanych automatycznie plików kluczy i ponowne uruchomienie modułu pochodnego klucza.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Wyprowadzanie kluczy...
+Zależnie od tego może potrwać do minuty
+na wydajność twojego systemu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Wyprowadzanie kluczy...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Wybierz cel zrzutu RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Proszę wybrać RomFS, jakie chcesz zrzucić.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Czy na pewno chcesz zamknąć yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Czy na pewno chcesz zatrzymać emulację? Wszystkie niezapisane postępy zostaną utracone.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nazwa gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Dodatki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Typ pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Rozmiar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Otwórz lokalizację zapisów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Otwórz lokalizację modyfikacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Zrzuć RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Kopiuj identyfikator gry do schowka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Nawiguj do wpisu kompatybilności gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfekcyjnie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Funkcje gry są bezbłędne, bez żadnych zakłóceń audio i graficznych, wszystkie przetestowane funkcje działają zgodnie z przeznaczeniem bez
+wszelkich potrzebnych obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Świetnie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Funkcje gry z drobnymi usterkami graficznymi lub dźwiękowymi i można je odtwarzać od początku do końca. Może wymagać niektórych
+obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>W porządku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Funkcje gry z dużymi usterkami graficznymi lub dźwiękowymi, ale gra jest odtwarzana od początku do końca z użyciem
+obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Zła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Funkcje gry, ale z dużymi usterkami graficznymi lub dźwiękowymi. Nie można wykonać postępu w określonych obszarach z powodu problemów
+nawet z obejściami.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Gra jest całkowicie niemożliwa do zagrania z powodu poważnych usterków graficznych lub dźwiękowych. Nie można przejść ekran
+startowy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Nie uruchamia się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Ta gra się zawiesza przy próbie startu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Nie testowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Ta gra nie została jeszcze przetestowana.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Kliknij podwójnie aby dodać folder do listy gier</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Wpisz typ do filtra</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Uruchamianie...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Plik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Ostatnie pliki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulacja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Widok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Narzędzia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Pomoc</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Załaduj plik...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Załaduj folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Wyjście</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pauza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicjalizuj klucze...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>O yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Tryb jednego okna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Konfiguruj...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Wyświetlaj nagłówki Dock Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Pokaż pasek fitrowania</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Pokaż pasek statusu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Pełny ekran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Zrestartuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Załaduj Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Zgłoś kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Otwórz folder yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Zrób zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[nie ustawione]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Krzyżak %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Oś %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Przycisk %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[nieznane]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[nieużywane]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Oś %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Wybierz użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Wybór profilu</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Wpisz tekst:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Klawiatura systemowa</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stos wywołań</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>czekam na mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ma oczekujących: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>uchwyt właściciela: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>czekam na wszystkie objekty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>oczekiwanie na jeden z następujących obiektów</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>Uruchomione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>Gotowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>Spauzowana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>czekam na powrót HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>spanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>czekam na odpowiedź IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>oczekiwanie na obiekty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>czekam na mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>czekam na arbitra adresu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>drzemiący</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>śmierć</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>rdzeń %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Nieznany procesor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>procesor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>idealny rdzeń = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>maska powinowactwa = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>identyfikator wątku = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>piorytet = %1(obecny) / %2(normalny)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>ostatnie działające kleszcze = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>nie czekam na mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>czekanie na wątek</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Drzewo czekania</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts
new file mode 100644
index 000000000..c842793f4
--- /dev/null
+++ b/dist/languages/pt_BR.ts
@@ -0,0 +1,4757 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pt_BR" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Sobre o yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu é um emulador experimental de código aberto para o Nintendo Switch licenciado sob a GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Esse programa não deve ser utilizado para jogar jogos que você não obteve legalmente.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código fonte&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidores&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licença&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; é uma marca comercial da Nintendo. O yuzu não é afiliado com a Nintendo de nenhuma forma.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Comunicando com o servidor...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Toque no canto superior esquerdo &lt;br&gt;do seu touchpad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Agora toque no canto inferior direito &lt;br&gt;do seu touchpad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configuração concluída!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Informar compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Informar compatibilidade de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Ao enviar um caso de teste à &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;lista de compatibilidade do yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, as seguintes informações serão recolhidas e exibidas no site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informações de hardware (CPU / GPU / sistema operacional)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Qual versão do yuzu você está usando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A conta do yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona impecavelmente, sem falhas no áudio ou nos gráficos.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir algumas soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Razoável</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com grandes falhas gráficas ou de áudio, mas é jogável do início ao fim com o uso de soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Ruim</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas específicas por conta de tais problemas, mesmo com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da tela inicial do jogo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sem considerar velocidade e desempenho, quão bem o jogo se comporta, do início ao fim, nesta versão do yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Agradecemos pelo seu relatório!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Enviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erro de comunicação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Um erro ocorreu ao enviar o caso de teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Próximo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Mecanismo de saída:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efeito de pós-processamento ajusta a velocidade do áudio para acompanhar a velocidade de emulação e ajuda a evitar cortes no áudio. No entanto, isto aumenta a latência do áudio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Ativar alongamento de áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de áudio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usar volume global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Definir volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Precisão:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Preciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Não seguro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Ativar modo de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Recomendamos definir a precisão para &quot;Preciso&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Ajustes de otimização não seguros de CPU </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Estes ajustes reduzem a precisão para aprimorar a velocidade.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Não usar FMA (melhora o desempenho em CPUs sem FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opção melhora o desempenho reduzindo a precisão de instruções de multiplicação-adição unificada (FMA) em CPUs sem suporte nativo a este recurso.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>FRSQRTE e FRECPE mais rápidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opção melhora o desempenho de algumas instruções de ponto flutuante usando aproximações nativas menos precisas.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Os ajustes de CPU só estão disponíveis enquanto o jogo não estiver em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Ativando o modo de depuração de CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>O modo de depuração de CPU é intencionado para uso por desenvolvedores. Deseja mesmo ativá-lo?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Ative ou desative otimizações de CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Apenas para depuração.&lt;/b&gt;
+ &lt;br&gt;
+ Se você não tem certeza do que estas opções fazem, mantenha-as todas ativadas.
+ &lt;br&gt;
+ Estes ajustes apenas têm efeito quando a precisão da CPU for definida como &quot;Ativar modo de depuração&quot;.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Ativar tabelas de página em linha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Esta otimização acelera acessos de memória pelo programa convidado.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando ativada, permite o acesso inline a PageTable::pointers no código emitido.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando desativada, força a passagem de todos os acessos de memória pelas funções Memory::Read/Memory::Write.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Ativar vinculação de blocos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta otimização evita buscas do dispatcher ao permitir que blocos básicos emitidos pulem diretamente para outros blocos básicos se o PC de destino for estático.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Ativar buffer de pilha de retorno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta otimização evita buscas do dispatcher ao monitorar possíveis endereços de retorno de instruções BL. Isto se aproxima do que ocorre em um buffer de pilha de retorno em um CPU real.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Ativar dispatcher rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa um sistema de dispatch de dois níveis. Um dispatcher mais rápido, escrito em assembly e que possui um pequeno cache MRU de destinos de pulo, é usado primeiro. Caso falhe, o dispatcher mais lento escrito em C++ será usado em seu lugar.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Ativar eliminação de contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa uma otimização da IR que reduz acessos desnecessários à estrutura de contexto da CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Ativar propagação de constantes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa otimizações da IR que envolvem propagação de constantes.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Ativar otimizações diversas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa otimizações diversas para a IR.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Ativar redução de checagem de desalinhamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando ativada, um desalinhamento só será disparado quando um acesso cruza o limite de uma página.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando desativada, um desalinhamento será disparado em todos os acessos desalinhados.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Os ajustes de CPU estão disponíveis apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Ativar GDB stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Registros de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro global de registros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar console de registros (apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir local dos registros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Linha de argumentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Quando ativado, a API gráfica entra em um modo de depuração mais lento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Ativar depuração de gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Quando ativado, desativa o macro compilador Just in Time. Ativar isto faz os jogos rodarem mais lentamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Desativar macro JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Extrair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ativar serviços de relatório detalhado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Isto será restaurado automaticamente assim que o yuzu for fechado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo quiosque (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Configurar controle de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Padrão</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configurações do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Interface</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de arquivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GráficosAvançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Rede</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Serviços</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Pastas de armazenamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Cartão SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartão de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Caminho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jogo atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gerenciador de patches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Extrair NSOs descompactados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Extrair ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Raiz de carregamento de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Extrair raiz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Ajustes de cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Diretório do cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadados da lista de jogos em cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Restaurar cache de metadados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecione a pasta da NAND emulada...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecione a pasta do SD emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecione o local do Gamecard...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecione a pasta de extração...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecione a pasta de carregamento de mods...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecione a pasta de cache...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>O cache de metadados já está vazio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi concluída com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>O cache de metadados não pôde ser excluído. Ele pode estar em uso no momento ou não existe.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limitar percentual de velocidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulação de CPU multinúcleo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmar saída quando a emulação estiver em execução</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Escolher um usuário ao iniciar um jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar emulação quando a janela ficar em segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Esconder cursor do mouse quando em inatividade</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Configurações de API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Configurações gráficas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar cache de shaders em disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulação assíncrona da GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Proporção de tela:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Padrão (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Forçar 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Forçar 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Esticar para a janela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usar cor de fundo global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Configurar cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Dispositivo gráfico do OpenGL</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Configurações gráficas avançadas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Nível de precisão:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>A sincronização vertical (VSync) evita que as imagens do jogo pareçam cortadas, porém algumas placas gráficas apresentam redução de desempenho quando estiver ativa. Deixe-a ativada se você não reparar alguma diferença de desempenho.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>Usar sincronização vertical (apenas OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Ativar isto reduz engasgos de shaders. Ativa os shaders assembly do OpenGL em dispositivos compatíveis da Nvidia (é necessária a extensão NV_gpu_program5). Esta é uma opção experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Usar shaders assembly (experimental, apenas OpenGL + Nvidia)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Realiza a compilação de shaders de forma assíncrona, o que pode reduzir engasgos de shaders. Esta opção é experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Usar compilação assíncrona de shaders (experimental)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Usar tempo de resposta rápido da GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Filtragem anisotrópica:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Configurações de teclas de atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clique duas vezes em um atalho para alterá-lo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Limpar tudo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Ação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Combinação de teclas já utilizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>A sequência de teclas pressionada já esta atribuída para: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Restaurar padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>A sequência de teclas padrão já esta atribuida para: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigurarEntrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Modo do console</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Na base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Fora da base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Movimento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Conectado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Cores dos Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Joycon esq.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Botão L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Joycon dir.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Botão R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Outro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Teclado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Movimento/toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Controle de depuração</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Conectar controle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Par de Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Joycon Esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Joycon Direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Dispositivo de entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Qualquer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Teclado/mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Salvar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Analógico esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Cima</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Esquerda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Direita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Baixo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Pressionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Alcance</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Zona morta: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Alcance de modificador: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>D-pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Menos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Capturar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Botão Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botões de rosto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Analógico direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Zona morta: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Alcance de modificador: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[esperando]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Configurar movimento/toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Movimento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Fonte dos dados de movimento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilidade:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Fonte dos dados de toque:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibração:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Usar mapeamento de botões:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Configuração CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Você pode utilizar qualquer dispositivo de entrada compatível com o Cemuhook UDP para prover dados de movimento e toque.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Servidor:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Direcionais:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Direcional 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Direcional 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Direcional 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Direcional 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Saiba mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Mouse (botão direito)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Janela do emulador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saiba mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Configurando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Teste bem-sucedido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation> Dados foram recebidos do servidor com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>O teste falhou</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Não foi possível receber dados válidos do servidor.&lt;br&gt;Verifique se o servidor foi configurado corretamente e o endereço e porta estão corretos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>Um teste UDP ou configuração de calibração está em curso no momento.&lt;br&gt;Aguarde até a sua conclusão.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botões do mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Dianteiro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Traseiro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Esquerdo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Meio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Direito:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[não definido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pressione uma tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Diálogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Informações</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>ID do título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nome do arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Versão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Desenvolvedor</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Gráficos avanç.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Usar configuração global (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Nome do patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Versão</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gerenciador de perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Usuário atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome de usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Definir imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Adicionar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Esta tela só fica disponível apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Escreva o nome de usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Usuários</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Digite o nome do novo usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Digite um novo nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmar exclusão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Você está prestes a excluir o usuário &quot;%1&quot;. Tem certeza?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Selecione a imagem do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagens JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Erro ao excluir a imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocorreu um erro ao tentar substituir a imagem anterior em: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erro ao excluir arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Não foi possível excluir o arquivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erro ao criar a pasta de imagens do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Não foi possível criar a pasta %1 para armazenar as imagens do usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erro ao copiar a imagem do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Não foi possível copiar a imagem de %1 para %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT é a maneira que a Nintendo usa para enviar dados aos jogos para engajar sua comunidade e desbloquear conteúdo adicional.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend do BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Saiba mais sobre o BCAT, Boxcat e eventos atuais&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>O serviço Boxcat está offline ou você não está conectado à internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Ocorreu um erro ao processar os dados de eventos do Boxcat. Entre em contato com os desenvolvedores do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>A versão do yuzu que você está usando é ou recente demais ou antiga demais para o servidor. Tente atualizar para a versão oficial mais recente do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Não há eventos no Boxcat atualmente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Eventos Boxcat atuais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>O yuzu está baixando o status mais recente do Boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Configurações de sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Região:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Automático</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islândia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Irã</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Ilhas Marshall</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Líbia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polônia </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Turquia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>EUA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Austrália</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Coréia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Fuso horário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: isso pode ser substituído caso a configuração de região automática esteja ativada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japônes (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglês (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francês (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemão (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espanhol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandês (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Português</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglês britânico (British English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francês canadense (Canadian French)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espanhol latino-americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinês simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinês tradicional (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Data e hora personalizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Idioma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semente RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estéreo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID do console:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de saída de som</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>As configurações de sistema são acessíveis apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Isto substituirá o seu Switch virtual atual por um novo. O seu Switch virtual atual não poderá ser recuperado. Isto pode causar efeitos inesperados em jogos. Isto pode falhar caso você use um jogo salvo com configurações desatualizadas registradas nele. Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Aviso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID do console: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Configurar mapeamento de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Mapeamento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Clique na área inferior para adicionar um ponto, e então pressione um botão para mapear.
+Mova os pontos para mudar a posição, ou clique duas vezes nas células da tabela para editar os valores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Excluir ponto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Botão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Novo perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Insira o nome do novo perfil.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Excluir perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Excluir perfil %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Renomear perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Novo nome:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[pressione a tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar tela de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Aviso: Os ajustes nesta página afetam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em comportamentos indesejáveis, tais como a tela de toque funcionar parcialmente ou não responder por completo. Apenas faça alterações caso você saiba o que está fazendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parâmetros de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diâmetro de toque Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diâmetro de toque X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ângulo rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar padrões</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: alterar o idioma aplicará as suas configurações.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Idioma da interface:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar coluna de adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamanho do ícone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texto da 1ª linha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texto da 2ª linha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Capturas de tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Perguntar onde salvar capturas de tela (apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Pasta para capturas de tela:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Selecione a pasta de capturas de tela...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Ao informar seu usuário e token, você concorda em permitir ao yuzu recolher dados de uso adicionais, que podem incluir informações de identificação de usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Cadastrar-se</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qual é o meu token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartilhar anonimamente dados de uso com a equipe do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saiba mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Gerar um novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presença no Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar o jogo atual no seu status do Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saiba mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Cadastrar-se&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Qual é o meu token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Não especificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token não verificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>O token não foi verificado. A alteração no seu token não foi salva.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verificando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Falha na verificação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Falha na verificação. Verifique se o seu token foi inserido corretamente e se a sua conexão à internet está funcionando.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dados anônimos são recolhidos&lt;/a&gt; para ajudar a melhorar o yuzu. &lt;br/&gt;&lt;br/&gt;Gostaria de compartilhar os seus dados de uso conosco?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Falha na verificação de texto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Carregando applet web...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Sair do applet web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para sair do aplicativo web, use os controles fornecidos pelo jogo para sair dele, selecione a opção &quot;Sair do applet web&quot; na barra de menu ou pressione a tecla &quot;Enter&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Applet web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versão do yuzu foi compilada sem suporte a QtWebEngine, o que significa que o yuzu não pode exibir adequadamente o manual do jogo ou a página da web solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>A quantidade de shaders sendo construídos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocidade atual de emulação. Valores maiores ou menores que 100% indicam que a emulação está rodando mais rápida ou lentamente que em um Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quantos quadros por segundo o jogo está exibindo atualmente. Isto irá variar de jogo para jogo e cena para cena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo que leva para emular um quadro do Switch, sem considerar o limitador de taxa de quadros ou a sincronização vertical. Um valor menor ou igual a 16.67 ms indica que a emulação está em velocidade plena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>NA BASE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTINÚCLEO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpar arquivos recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Aviso - formato de jogo desatualizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Você está usando neste jogo o formato de ROM desconstruída e extraída em uma pasta, que é um formato desatualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Pastas desconstruídas de ROMs não possuem ícones, metadados e suporte a atualizações.&lt;br&gt;&lt;br&gt;Para saber mais sobre os vários formatos de ROMs de Switch compatíveis com o yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;confira a nossa wiki&lt;/a&gt;. Esta mensagem não será exibida novamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erro ao carregar a ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>O formato da ROM não é suportado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Ocorreu um erro ao inicializar o núcleo de vídeo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>O yuzu encontrou um erro ao executar o núcleo de vídeo. Consulte o registro para mais detalhes. Para mais informações em como acessar o registro, veja a seguinte página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como enviar o arquivo de registro&lt;/a&gt;. Verifique se você está usando o driver de vídeo mais atualizado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>Erro ao carregar a ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ocorreu um erro desconhecido. Consulte o registro para mais detalhes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Dados de jogos salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Dados de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erro ao abrir a pasta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>A pasta não existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erro ao abrir o cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Não existe um cache de shaders para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Conteúdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Atualização</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Remover item</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Remover o jogo instalado %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Removido com sucesso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>O jogo base foi removido com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Erro ao remover %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>O jogo base não está instalado na NAND e não pode ser removido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>A atualização instalada foi removida com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Não há nenhuma atualização instalada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Não há nenhum DLC instalado para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1 DLC(s) instalados foram removidos com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>Excluir o cache de shaders transferível?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Remover configurações customizadas do jogo?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Remover arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Erro ao remover cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>O cache de shaders transferível foi removido com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Falha ao remover o cache de shaders transferível.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Erro ao remover as configurações customizadas do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Não há uma configuração customizada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>As configurações customizadas do jogo foram removidas com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Falha ao remover as configurações customizadas do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Falha ao extrair RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Extração completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Apenas estrutura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecione o modo de extração do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Selecione a forma como você gostaria que o RomFS seja extraído.&lt;br&gt;&quot;Extração completa&quot; copiará todos os arquivos para a nova pasta, enquanto que &lt;br&gt;&quot;Apenas estrutura&quot; criará apenas a estrutura de pastas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraindo RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extração do RomFS concluida!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi concluída com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erro ao abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecionar pasta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>As propriedades do jogo não puderam ser carregadas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Executável do Switch (%1);;Todos os arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carregar arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir pasta da ROM extraída</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Pasta inválida selecionada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>A pasta que você selecionou não contém um arquivo &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Arquivo de Switch instalável (*.nca *.nsp *.xci);; Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Instalar arquivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando arquivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>Resultados da instalação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Arquivo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Atualização de aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacote de firmware (tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacote de firmware (tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Atualização de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Título delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecione o tipo de instalação do NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Selecione o tipo de título como o qual você gostaria de instalar este NCA:
+(Na maioria dos casos, o padrão &apos;Jogo&apos; serve bem.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Falha ao instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>O tipo de título que você selecionou para o NCA é inválido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Arquivo não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Arquivo &quot;%1&quot; não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Tela de erro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Conta do yuzu faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar um caso de teste de compatibilidade de jogo, você precisa entrar com a sua conta do yuzu.&lt;br&gt;&lt;br/&gt;Para isso, vá para Emulação &amp;gt; Configurar... &amp;gt; Rede.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Erro ao abrir URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>Não foi possível abrir o URL &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Arquivo Amiibo (%1);; Todos os arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carregar Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erro ao abrir arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Não foi possível abrir o arquivo de Amiibo &quot;%1&quot; para leitura.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erro ao ler arquivo de dados de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Não foi possível ler completamente os dados do Amiibo. O yuzu esperava ler %1 bytes, mas foi capaz de ler apenas %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erro ao carregar dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Não foi possível carregar os dados do Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Capturar tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagem PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidade: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidade: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jogo: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Quadro: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O jogo que você está tentando carregar precisa que arquivos adicionais do seu Switch sejam extraídos antes de jogá-lo.&lt;br/&gt;&lt;br/&gt;Para saber mais sobre como extrair esses arquivos, visite a seguinte página da wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Extraindo arquivos de sistema e fontes compartilhadas de um Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt; Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Arquivo do sistema não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Arquivo de sistema faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>O yuzu não foi capaz de encontrar as fontes compartilhadas do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fontes compartilhadas não encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Fonte compartilhada faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O yuzu encontrou um erro fatal. Consulte o registro para mais detalhes. Para mais informações sobre como acessar o registro, consulte a seguinte página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como enviar o arquivo de registro&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Erro fatal encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirmar rederivação de chave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Você está prestes a rederivar todas as suas chaves forçadamente.
+Se você não sabe o que isso significa ou o que você está fazendo,
+esta é uma ação potencialmente destrutiva.
+Por favor, confirme que você quer mesmo fazer isto
+e opcionalmente faça cópias de segurança.
+
+Isto excluirá o seus arquivos de chaves geradas automaticamente, e reexecutar o módulo de derivação de chaves.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Faltando fusíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - Faltando BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - Faltando BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Faltando PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Faltando componentes de derivação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Há componentes faltando que podem impedir a conclusão do processo de derivação de chaves. &lt;br&gt;Por favor, siga &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;o guia de iniciação rápida&lt;/a&gt; para obter todas as suas chaves e jogos. &lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando chaves...
+Isto pode demorar até um minuto, dependendo
+do desempenho do seu sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivando chaves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecionar alvo de extração do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Selecione qual RomFS você quer extrair.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Você deseja mesmo fechar o yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Deseja mesmo parar a emulação? Qualquer progresso não salvo será perdido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>O aplicativo rodando no momento solicitou ao yuzu para não sair.
+
+Deseja ignorar isso e sair mesmo assim?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL não disponível!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>O yuzu não foi compilado com suporte para OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan não disponível!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>O yuzu não foi compilado com suporte para Vulkan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>Erro ao inicializar OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Sua GPU pode não suportar OpenGL 4.3, ou você não tem os drivers gráficos mais recentes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Erro ao inicializar o OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Por favor, verifique se você possui a última versão dos drivers gráficos.&lt;br&gt;&lt;br&gt;Extensões não supportadas:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir local dos jogos salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir local dos dados de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Remover</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Remover atualização instalada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Remover todos os DLCs instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Remover cache de shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Remover configuração customizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Remover todo o conteúdo instalado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Extrair RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar ID do título para a área de transferência</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Abrir artigo do jogo no GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Examinar subpastas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Remover pasta de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Mover para cima</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Mover para baixo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir local da pasta</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>O jogo funciona impecavelmente, sem falhas gráficas ou de áudio. Todas as funcionalidades testadas
+do jogo funcionam como deveriam, sem nenhuma solução alternativa necessária.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir
+algumas soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Razoável</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>O jogo funciona com graves falhas gráficas ou de áudio, mas é jogável do início ao fim
+com o uso de soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Ruim</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas
+específicas por conta de tais problemas, mesmo com soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da
+tela inicial do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Não testado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Esse jogo ainda não foi testado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clique duas vezes para adicionar uma pasta à lista de jogos</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Digite o padrão para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Por favor, confirme que esses são os arquivos que deseja instalar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Instalar uma atualização ou DLC irá sobrescrever a instalada anteriormente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Instalar arquivos para a NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Carregando shaders 387/1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Carregando shaders (%v de %m)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Carregando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Carregando shaders %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Iniciando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Arquivos recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Exibir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Ferramentas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ajuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Instalar arquivos para a NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carregar arquivo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carregar pasta...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;air</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Parar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicializar chaves...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Sobre o yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo de janela única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Exibir barra de títulos de widgets afixados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Exibir barra de filtro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Exibir barra de status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Restaurar tamanho padrão de janela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Tela cheia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carregar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Informar compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Abrir pagina de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Abrir o guia de iniciação rápida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir pasta do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Capturar tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Configurar jogo atual..</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos instalados no SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos instalados na NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Adicionar pasta de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[não definido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eixo %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botão %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[desconhecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clique 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clique 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clique 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clique 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clique 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Eixo GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Botão GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[não utilizado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eixo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Eixo GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ocorreu um erro.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código de erro: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ocorreu um erro em %1 em %2.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código de erro: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ocorreu um erro.
+Código do erro: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecione um usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Usuários</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Seletor de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Digite o texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Teclado de software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Digite uma combinação de teclas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pilha de chamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando pelo mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>possui os waiters %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>manejo de proprietário: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando por todos os objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando por um dos seguintes objetos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>não aguardando pelo thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>rodando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>pronto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando pelo retorno do HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormindo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>esperando para resposta do IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando por objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>aguardando por variável da condição</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando para endereção o árbitro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>inativo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processador desconhecido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidade = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>thread id = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridade = %1(atual) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>últimos ticks executados = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>não aguardando para mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>aguardado pelo thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Árvore de espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts
new file mode 100644
index 000000000..5ba0bcf3b
--- /dev/null
+++ b/dist/languages/pt_PT.ts
@@ -0,0 +1,4725 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pt_PT" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Sobre Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu é um emulador experimental de código aberto para a Nintendo Switch licenciado sob GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Este software não deve ser usado para jogar jogos que você não tenha obtido legalmente.&lt;/span&gt;&lt;/p&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código Fonte&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidores&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licença&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; é uma marca comercial da Nintendo. Yuzu não é afiliado com a Nintendo de qualquer forma.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Reportar Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Reportar compatibilidade de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Se você optar por enviar um caso de teste para a&lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;lista de compatibilidade do yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;As seguintes informações serão coletadas e exibidas no site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informações de Hardware (CPU / GPU / Sistema operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Que versão do yuzu você está executando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A conta yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Jogo funciona na perfeição sem falhas de áudio ou gráficas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do início ao fim. Pode exigir algumas soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do início ao fim com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mau</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona, mas com grandes falhas gráficas ou de áudio. Não é possível progredir em áreas específicas devido a falhas, mesmo com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Introdução / Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível passar da tela inicial.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não Inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo trava ao tentar iniciar.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independente da velocidade ou performance, como este jogo funciona de principio ao fim nesta versão do yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Obrigado pelo seu envio!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Entregando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erro de comunicação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Ocorreu um erro enquanto enviava o caso de teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Próximo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motor de Saída:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efeito de pós-processamento ajusta a velocidade do áudio para corresponder à velocidade de emulação e ajuda a evitar o engasgue do audio. Isto, no entanto, aumenta a latência de áudio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activar alongamento de audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de áudio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activar GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Entrando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro de registro global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar o registro da consola (Apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir a localização do registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Argumentos String</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ativar o Reporte de Serviços detalhado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Isto vai ser resetado automáticamente quando o yuzu fechar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo Quiosque</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuração yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>IU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de Ficheiros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controlos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de Atalhos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Rede</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Serviços</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Diretórios de armazenamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Cartão SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartão de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Caminho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jogo Atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestor de Patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dump NSOs Descompactados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Dump ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Raiz dos Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Raiz do Dump</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Armazenamento em cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Diretório do cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadata da Lista de Jogos em Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Resetar a Cache da Metadata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecione o Diretório NAND Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecione o Diretório SD Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecione o Diretório do Cartão de Jogo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecionar o diretório do Dump...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecionar o Diretório do Mod Load ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecionar o diretório do Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>O cache de metadata já está vazio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi completa com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Não foi possível excluir o cache de metadata. Pode estar em uso ou inexistente.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Percentagem do limitador de velocidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirme a saída enquanto a emulação está em execução</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Solicitar para o utilizador na inicialização do jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar o emulador quando estiver em segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar cache do disk shader</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulação assíncrona de GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Definições de Teclas de Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clique duas vezes numa ligação para alterá-la.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Ação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Tecla de Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation> Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Sequência de teclas em conflito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Analógico Esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botôes de Rosto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Analógico Direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar Rato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botões do Rato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Frente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Atrás:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Esquerda:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Meio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Direita:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[não configurado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pressiona a tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestor de Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utilizador Atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome de Utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Definir Imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Adicionar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Remover</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>O gestor de perfis só está disponível apenas quando o jogo não está em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Introduza o Nome de Utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utilizadores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Introduza um nome de utilizador para o novo utilizador:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Introduza um novo nome de utilizador:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmar para eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Estás preste a eliminar o utilizador com o nome &quot;%1&quot;. Tens a certeza?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Definir Imagem de utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagens JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Error ao eliminar a imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocorreu um erro ao tentar substituir imagem anterior em: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erro ao eliminar o arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Não é possível eliminar o arquivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erro ao criar o diretório de imagens do utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Não é possível criar o diretório %1 para armazenar imagens do utilizador.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erro ao copiar a imagem do utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Não é possível copiar a imagem de %1 para %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>O BCAT é a forma pela qual a Nintendo envia dados aos jogos para envolver a sua comunidade e desbloquear conteúdos adicionais.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Saiba mais sobre o BCAT, Boxcat e eventos atuais&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>O serviço boxcat está offline ou não estás ligado à Internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Ocorreu um erro ao processar os dados do evento boxcat. Entre em contato com os desenvolvedores do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>A versão em uso do yuzu é muito nova ou muito antiga para o servidor. Tente atualizar para a ultima versão oficial do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>De momento, não há eventos no boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu está a atualizar o boxcat ...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Configurações de Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: isto pode ser substituído quando a configuração da região é de seleção automática</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonês (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francês (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemão (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espanhol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandês (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Português (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglês Britânico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francês Canadense</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espanhol Latino-Americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinês Simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinês Tradicional (正 體 中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC personalizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Língua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semente de RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estéreo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID da consola:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de saída de som</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>As configurações do sistema estão disponíveis apenas quando o jogo não está em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Isto substituirá o seu Switch virtual actual por um novo. Seu Switch virtual actual não será recuperável. Isso pode ter efeitos inesperados nos jogos. Isto pode falhar, se você usar uma gravação de jogo de configuração desatualizado. Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Aviso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID da Consola: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar Ecrã Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Aviso: As configurações nesta página afectam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em um comportamento indesejável, como a tela de toque não funcionando parcialmente ou até mesmo na sua totatildade. Você só deve usar esta página se souber o que está fazendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parâmetros de Toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diâmetro de Toque Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diâmetro de Toque X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ângulo rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar Padrões</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Alterar o idioma aplicará sua configuração.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Idioma da interface:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar coluna de Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamanho do ícone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Linha 1 Texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Linha 2 Texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Serviço Web do Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Ao fornecer seu nome de usuário e token, você concorda em permitir que o yuzu colete dados de uso adicionais, que podem incluir informações de identificação do usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Inscrever-se</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>O que é o meu token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartilhar dados de uso anônimos com a equipa Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saber mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de Telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presença do Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar o Jogo Atual no seu Estado de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saber mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Inscrever-se&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;O que é o meu Token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de Telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Não especificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token não verificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>O token não foi verificado. A alteração do token não foi gravada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>A verificar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificação Falhada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificação Falhada. Verifique se introduziu seu nome de utilizador e o token correctamente e se a conexão com a Internet está operacional.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dados anônimos são coletados&lt;/a&gt;para ajudar a melhorar o yuzu.&lt;br/&gt;&lt;br/&gt;Gostaria de compartilhar seus dados de uso conosco?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Falha na verificação de texto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>A Carregar o Web Applet ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Sair do Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para sair do aplicativo web, use os controlos fornecidos pelo jogo para selecionar sair, selecione a opção &apos;Sair do Web Applet&apos; na barra de menus ou pressione a tecla &apos;Enter&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versão do yuzu foi criada sem o suporte ao QtWebEngine, o que significa que o yuzu não pode exibir corretamente o manual do jogo ou a página da web solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocidade da emulação actual. Valores acima ou abaixo de 100% indicam que a emulação está sendo executada mais depressa ou mais devagar do que a Switch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quantos quadros por segundo o jogo está exibindo de momento. Isto irá variar de jogo para jogo e de cena para cena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo gasto para emular um frame da Switch, sem contar o a limitação de quadros ou o v-sync. Para emulação de velocidade máxima, esta deve ser no máximo 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpar Arquivos Recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Aviso de Formato de Jogo Desactualizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Você está usando o formato de directório ROM desconstruído para este jogo, que é um formato desactualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Os directórios de ROM não construídos não possuem ícones, metadados e suporte de actualização.&lt;br&gt;&lt;br&gt;Para uma explicação dos vários formatos de Switch que o yuzu suporta,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Verifique a nossa Wiki&lt;/a&gt;. Esta mensagem não será mostrada novamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erro ao carregar o ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>O formato do ROM não é suportado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Ocorreu um erro ao inicializar o núcleo do vídeo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu encontrou um erro ao executar o núcleo de vídeo, consulte o log para obter mais detalhes. Para obter mais informações sobre como acessar o log, consulte a seguinte página:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como carregar o arquivo de log&lt;/a&gt;Assegure-se que tem os drivers gráficos mais recentes para sua GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ocorreu um erro desconhecido. Por favor, veja o log para mais detalhes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Começar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Save Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erro ao abrir a pasta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>A Pasta não existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erro ao abrir os Shader Cache transferíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>O Shader Cache para este titulo não existe.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>A Extração de RomFS falhou!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Cheio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Esqueleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecione o modo de despejo do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Por favor, selecione a forma como você gostaria que o RomFS fosse despejado&lt;br&gt;Full irá copiar todos os arquivos para o novo diretório enquanto&lt;br&gt;skeleton criará apenas a estrutura de diretórios.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraindo o RomFS ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extração de RomFS Bem-Sucedida!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi completa com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erro ao abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecione o Diretório</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>As propriedades do jogo não puderam ser carregadas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Executáveis Switch (%1);;Todos os Ficheiros (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carregar Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir o directório ROM extraído</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Diretório inválido selecionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>O diretório que você selecionou não contém um arquivo &apos;Main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando arquivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicação do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Arquivo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Atualização do aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacote de Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacote de Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Actualização do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Título Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecione o tipo de instalação do NCA ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Por favor, selecione o tipo de título que você gostaria de instalar este NCA como:
+(Na maioria dos casos, o padrão &apos;Jogo&apos; é suficiente).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Falha na instalação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>O tipo de título que você selecionou para o NCA é inválido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Arquivo não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Arquivo &quot;%1&quot; não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Visualização de erros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Conta Yuzu Ausente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar um caso de teste de compatibilidade de jogos, você deve vincular sua conta yuzu.&lt;br&gt;&lt;br/&gt;Para vincular sua conta yuzu, vá para Emulação &amp;gt; Configuração &amp;gt; Rede.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Arquivo Amiibo (%1);; Todos os Arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carregar Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erro ao abrir o arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Não é possível abrir o arquivo Amiibo &quot;%1&quot; para leitura.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erro ao ler o arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Não é possível ler completamente os dados do Amiibo. Espera-se que leia %1 bytes, mas só conseguiu ler %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erro ao carregar dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Não foi possível carregar os dados do Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagem PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidade: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidade: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jogo: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Quadro: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O jogo que você está tentando carregar requer arquivos adicionais do seu Switch para serem despejados antes de jogar.&lt;br/&gt;&lt;br/&gt;Para obter mais informações sobre como despejar esses arquivos, consulte a seguinte página da wiki:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Despejando arquivos do sistema e as fontes compartilhadas de uma consola Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>O yuzu não conseguiu localizar um arquivo de sistema do Switch. % 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>O yuzu não conseguiu localizar um arquivo de sistema do Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Arquivo do Sistema Não Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Arquivo de Sistema em falta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu não conseguiu localizar as fontes compartilhadas do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fontes compartilhadas não encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Fontes compartilhadas em falta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu encontrou um erro fatal, por favor veja o registro para mais detalhes. Para mais informações sobre como acessar o registro, por favor, veja a seguinte página:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como carregar o arquivo de registro&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Ocorreu um Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirme a rederivação da chave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Você está prestes a forçar a rederivação de todas as suas chaves.
+Se você não sabe o que isso significa ou o que você está fazendo,
+esta é uma acção potencialmente destrutiva.
+Por favor, certifique-se que isto é o que você quer
+e opcionalmente faça backups.
+
+Isso irá excluir os seus arquivos de chave gerados automaticamente e executará novamente o módulo de derivação de chave.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando chaves ...
+Isto pode demorar até um minuto, dependendo
+do desempenho do seu sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivando Chaves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecione o destino de despejo do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Por favor, selecione qual o RomFS que você gostaria de despejar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Tem a certeza que quer fechar o yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Tem a certeza de que quer parar a emulação? Qualquer progresso não salvo será perdido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>O aplicativo atualmente em execução solicitou que o yuzu não fechasse.
+
+Deseja ignorar isso e sair mesmo assim?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Acrescentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir Localização de Dados Salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir a Localização de Dados do Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir Shader Cache transferíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Despejar RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar título de ID para a área de transferência</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navegue para a Entrada da Base de Dados de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Examinar Sub-pastas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Remover diretório do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir Localização do diretório</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>O Jogo Funciona na Perfeição sem falhas de áudio ou gráficas, todas as funcionalidades testadas funcionam como planeadas sem
+quaisquer soluções alternativas necessárias.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>O Jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do principio ao fim. Pode exigir algumas
+soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>O Jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do principio ao fim com
+soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mau</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Jogo Funcional, mas com grandes falhas gráficas ou de áudio. Incapaz de progredir em áreas específicas devido a falhas
+mesmo com soluções alternativas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Introdução / Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>O Jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível passar da Tela
+Inicial</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não Inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>O jogo trava ao tentar iniciar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Não Testado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>O jogo ainda não foi testado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clique duas vezes para adicionar uma nova pasta à lista de jogos</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Digite o padrão para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>A Carregar Shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>A Carregar Shaders %v por %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo Estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>A Carregar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>A Carregar Shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>A iniciar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo Estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Arquivos Recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Vista</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Ferramentas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ajuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carregar Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carregar Pasta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Começar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Parar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicialize as chaves ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Sobre yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo de Janela Única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Exibir Cabeçalhos de Ferramenta de Doca</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostrar Barra de Filtros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostrar Barra de Estado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Tela Cheia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carregar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Reportar Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir Pasta yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>Micro Perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos SD instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos NAND instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Adicionar novo diretório de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[não configurado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eixo %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botão %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[Desconhecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[sem uso]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eixo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ocorreu um erro.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código do Erro: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ocorreu um erro em %1 até %2.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código do Erro: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ocorreu um erro.
+Código de Erro: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecione um usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utilizadores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selector de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Digite o texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Teclado de Software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Introduza a tecla de atalho</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pilha de Chamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando por mutex 0x% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>has waiters: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>owner handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando por todos os objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando por todos os objectos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>correr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>preparado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando pelo retorno de HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormindo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>aguardando resposta do IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando por objectos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>A espera da variável de condição</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando pelo árbitro de endereço</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processador desconhecido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidade =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id do segmento =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridade =%1(atual) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>últimos tiques em execução =%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>não esperar por mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>esperado por tópico</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Esquema de espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts
new file mode 100644
index 000000000..357b8e416
--- /dev/null
+++ b/dist/languages/ru_RU.ts
@@ -0,0 +1,4720 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="ru_RU" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>О yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu - экспериментальный эмулятор для Nintendo Switch с открытым исходным кодом, лицензированный под GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Это ПО не должно использоваться для запуска игр, которые были получены нелегально.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Веб-сайт&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Исходный код&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Авторы&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Лицензия&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; является торговой маркой Nintendo. yuzu никак не связан с Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Сообщить о совместимости</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Сообщить о совместимости игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Если вы захотите отправить отчет в &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Список Совместимости yuzu&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Следующая информация будет собрана и отображена на сайте:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; Информация об Аппаратном Обеспечении (ЦП / ГП / Операционная Система)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Версия yuzu&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Подключенный аккаунт yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра функционирует отлично, без звуковых и графических артефактов.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Отлично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Нормально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Плохо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Вступление/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;В игру невозможно играть из-за графических или звуковых артефактов. Невозможно продвинуться дальше Стартового Экрана.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не Запускается</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра падает при старте.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Независимо от скорости и производительности, насколько хорошо эта игра работает от начала до конца в текущей версии yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Спасибо за ваш отчет!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Отправка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Ошибка соединения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Произошла ошибка при отправке Отчета</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Далее</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Механизм вывода:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Этот эффект пост-обработки регулирует скорость звука в соответствии со скоростью эмуляции и помогает предотвратить заикание звука. Это, однако, увеличивает задержку звука.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Включить растяжение звука</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Звуковое Устройство:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Громкость:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Включить GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Журналирование</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Глобальный Фильтр Журнала</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Показывать Журнал в Консоли (Только Для Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Открыть Расположение Журнала</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Строка Аргументов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Включить Службы Подробных Отчётов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Это будет автоматически сброшено после закрытия yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Дополнительно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Параметры yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Интерфейс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Список Игр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Профили</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Файловая система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Управление</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Горячие клавиши</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Отладка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Графика</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Веб</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Сервисы</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Директории Хранения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD карта</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Картридж</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Путь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Вставлен</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Текущая Игра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Управление Патчами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Дампить Распакованные NCO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Дампить ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Кэширование</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Директория Кэша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Сбросить Кэш Метаданных</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Выбрать Папку Для Эмулируемого NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Выбрать Папку Для Эмулируемого SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Выберите Папку с Дампами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Выбрать Папку с Кэшем...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Кэш метаданных уже пустой.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операция выполнена успешно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Кэш метаданных не может быть удален. Он может использоваться или отсутствовать.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Ограничить Скорость в Процентах</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Подтверждать выход во время эмуляции</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Приостанавливать эмуляцию, когда в фоновом режиме</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Использовать кэш шейдеров на диске</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Использовать асинхронную эмуляцию ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Фоновый Цвет:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Настройки Горячих Клавиш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Действие</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Сочетание</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Контекст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Конфликтующее Сочетание Клавиш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Игрок 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Игрок 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Игрок 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Игрок 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Игрок 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Игрок 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Игрок 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Игрок 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Дополнительно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Настроить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Настроить Ввод</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Левый Стик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Основные Кнопки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Правый Стик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Настроить Мышь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Кнопки на Мыши</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Вперед:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Назад:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Левая:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Средняя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Правая:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Очистить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Настройки по Умолчанию</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[нажмите кнопку]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Управление Профилями</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Текущий Пользователь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Имя Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Добавить Изображение</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Добавить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Переименовать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Удалить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Управление профилями доступно только тогда, когда игра не запущена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Введите Имя Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Пользователи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Введите имя пользователя для нового профиля:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Введите новое имя пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Подтвердите Удаление</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Вы собираетесь удалить пользователя &quot;%1&quot;. Вы уверены?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Выберите Изображение для Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG Images (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Ошибка при удалении изображения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ошибка при попытке перезаписи предыдущего изображения в: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Ошибка при удалении файла</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Не получилось удалить существующий файл: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Ошибка при создании папки пользовательских изображений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Не получилось создать папку %1 для хранения изображений пользователя.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Ошибка при копировании изображения пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Не получилось скопировать изображение из %1 в %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT - это способ Nintendo отправлять данные в игры, чтобы привлечь сообщество и разблокировать дополнительный контент.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Произошла ошибка при обработке данных событий boxcat. Свяжитесь с разработчиками yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>Используемая вами версия yuzu слишком новая или слишком старая по сравнению с версией на сервере. Попробуйте обновить yuzu до последней официальной версии.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Настройки Системы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Примечание: может быть перезаписано если регион выбирается автоматически</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Японский (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Английский (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Французский (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Немецкий (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Итальянский (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Испанский (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Китайский (Chinese)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Корейский (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Голландский (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Португальский (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Русский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Тайваньский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Британский Английский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Канадский Французский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Латиноамериканский Испанский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Упрощенный Китайский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Традиционный Китайский (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Пользовательский RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Язык</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Зерно RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Моно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Стерео</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Объёмный звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Идентификатор Консоли:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Режим вывода звука</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Перегенерировать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Настройки системы доступны только тогда, когда игра не запущена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Это заменит ваш текущий виртуальный Switch новым. Ваш текущий виртуальный Switch будет безвозвратно потерян. Это может иметь неожиданные последствия в играх. Может не сработать, если вы используете устаревшую конфигурацию сохраненных игр. Продолжить?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Внимание</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Идентификатор Консоли: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Настроить Сенсорный Экран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Внимание: Настройки на этой странице влияют на внутреннюю работу эмулируемого сенсорного экрана yuzu. Их изменение может привести к нежелательному поведению, как например частичная или полная неработоспособность сенсорного экрана. Используйте их, только если вы знаете, что делаете.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Параметры Касания</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Диаметр Касания Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Палец</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Диаметр Касания X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Угол Поворота</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Настройки по Умолчанию</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Примечание: Изменение языка приведет к применению настроек.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Язык интерфейса:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Тема:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Список Игр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Показывать Столбец Дополнений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Размер Иконок:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Текст 1 Строки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Текст 2 Строки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Английский</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Веб Сервис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Предоставляя ваше имя пользователя и токен, вы разрешаете yuzu собирать дополнительную информацию об использовании, которая может включать данные идентифицирующие пользователя.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Подтвердить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Регистрация</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Токен:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Имя Пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Что такое токен?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Телеметрия</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Делиться анонимной информацией об использовании с командой yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Узнать больше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Телеметрии:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Перегенерировать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Интеграция с Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Показывать Текущую Игру в вашем Статусе Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Узнать больше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Регистрация&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Что такое токен?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Телеметрии: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Отсутствует</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Токен не подтвержден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Токен не подтвержден. Ваш токен не был изменен.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Проверка...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Ошибка подтверждения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Ошибка подтверждения. Убедитесь в том, что токен введен корректно, и ваше интернет соединение активно.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Анонимные данные собираются&lt;/a&gt; для улучшения yuzu. &lt;br/&gt;&lt;br/&gt;Хотели бы вы делиться данными об использовании с нами?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Телеметрия</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Ошибка Проверки Текста</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Выход</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Эта версия yuzu была собрана без поддержки QtWebEngine, что означает, что yuzu не может нормально отобразить руководство к игре или запрошенную веб-страницу.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Текущая скорость эмуляции. Значения выше или ниже 100% указывают на то, что эмуляция идет быстрее или медленнее, чем на Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Количество кадров в секунду в данный момент. Значение будет меняться от игры к игре и от сцене к сцене.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Время, которое нужно для эмуляции 1 кадра Switch, не принимая во внимание ограничение FPS или v-sync. Для полноскоростной эмуляции значение должно быть не больше 16,67 мс.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Очистить Недавние Файлы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Предупреждение Устаревший Формат Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Для этой игры вы используете разархивированный формат РОМа, который является устаревшим и был заменен другими, такими как NCA, NAX, XCI или NSP. В разархивированных каталогах РОМа отсутствуют значки, метаданные и поддержка обновлений. &lt;br&gt;&lt;br&gt;Для получения информации о различных форматах Switch, поддерживаемых yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;просмотрите нашу вики&lt;/a&gt;. Это сообщение больше не будет отображаться.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Ошибка при загрузке РОМа!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Формат РОМа не поддерживается.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Произошла ошибка при инициализации видеоядра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu обнаружил ошибку во время работы видеоядра. Проверьте журнал для подробностей. Информацию о доступе к журналу можно найти на этой странице: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Как Отправить Файл Журнала.&lt;/a&gt; Убедитесь, что у вас установлены последние графические драйверы.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Произошла неизвестная ошибка. Пожалуйста, проверьте лог для подробностей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Начать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Данные Сохранений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Данные Модов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Ошибка при Открытии Папки %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Папка не существует!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Ошибка при Открытии Переносного Кеша Шейдеров</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Шейдерный кеш для этого идентификатора не существует.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Не удалось извлечь RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Произошла ошибка при копировании файлов RomFS или пользователь отменил операцию.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Полный</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Выберите Режим Дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Пожалуйста, выберите, как вы хотите выполнить дамп RomFS. &lt;br&gt;Полное скопирует все файлы в новый каталог, в то время как &lt;br&gt;Структура создаст только структуру каталогов.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Извлечение RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Отмена</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Извлечение RomFS Прошло Успешно!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операция выполнена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Ошибка Открытия %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Выбрать Путь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Свойства</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Не удалось загрузить свойства игры.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Исполняемый файл Switch (%1);;Все файлы (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Загрузить Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Открыть Папку Извлечённого РОМа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Выбран Неверный Каталог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Каталог, который вы выбрали, не содержит «основного» файла.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Установка файла &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Системное Приложение</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Системный Архив</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Обновление Системного Приложения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Контейнер Прошивки (Тип А)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Контейнер Прошивки (Тип Б)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Игра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Обновление Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Загружаемый Контент</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta Название</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Выберите Тип Установки NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Пожалуйста, выберите тип приложения, который вы хотите установить для этого NCA:
+ (В большинстве случаев, простое «Игра» подходит.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Ошибка Установки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Тип приложения, который вы выбрали для NCA, недействителен.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Файл не найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Файл &quot;%1&quot; не найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Продолжить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Показ Ошибок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Отсутствует Аккаунт yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Чтобы отправить отчет о совместимости игры, необходимо привязать свою учетную запись yuzu.&lt;br&gt;&lt;br/&gt;Чтобы привязать свою учетную запись yuzu, перейдите в раздел Эмуляция &amp;gt; Настройка &amp;gt; Веб.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Файл Amiibo (%1);; Все Файлы (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Загрузить Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Ошибка открытия файла данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Невозможно открыть файл Amiibo &quot;%1&quot; для чтения.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Ошибка чтения файла данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Невозможно полностью прочитать данные Amiibo. Ожидалось прочитать %1 байт, но удалось прочитать только %2 байт.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Ошибка загрузки данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Невозможно загрузить данные Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Сделать Скриншот</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG Image (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Скорость: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Скорость: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Игра: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Один кадр: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Игра, которую вы пытаетесь загрузить, требует, чтобы дополнительные файлы были сдамплены с вашего Switch перед началом игры. &lt;br/&gt;&lt;br/&gt;Для получения дополнительной информации о дампе этих файлов см. следующую вики-страницу: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Дамп Системных Архивов и Общих Шрифтов с консоли&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Хотите вернуться к списку игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu не удалось найти системный архив Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu не удалось найти системный архив Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Системный Архив Не Найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Отсутствует Системный Архив</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu не удалось найти общие шрифты Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Общие Шрифты Не Найдены</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Общие Шрифты Отсутствуют</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Фатальная Ошибка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu столкнулся с фатальной ошибкой, смотрите подробности в журнале. Информацию о доступе к журналу можно найти на этой странице: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Как Отправить Файл Журнала&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Хотите вернуться в список игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Обнаружена Фатальная ошибка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Подтвердите Перерасчет Ключа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Вы собираетесь принудительно пересчитать все ваши ключи.
+Если вы не знаете, что это значит или что вы делаете,
+это потенциально разрушительное действие.
+Пожалуйста, убедитесь, что это то, что вы хотите
+и при желании сделайте резервные копии.
+
+Это удалит ваши автоматически сгенерированные файлы ключей и повторно запустит модуль расчета ключей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Получение ключей...
+Это может занять до минуты в зависимости
+от производительности вашей системы.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Получение Ключей</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Выберите Цель для Дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Пожалуйста, выберите, какой RomFS вы хотите сдампить.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Вы уверены, что хотите закрыть yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Вы уверены, что хотите остановить эмуляцию? Любой несохраненный прогресс будет потерян.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Запущенное в настоящий момент приложение просит yuzu не завершать работу.
+
+Проигнорировать и выйти в любом случае?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Имя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Совместимость</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Дополнения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Тип Файла</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Размер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Открыть Папку Сохранений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Открыть Папку Модов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Открыть Переносной Кеш Шейдеров</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Сдампить RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Скопировать ID приложения в буфер обмена</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Перейти к записи GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Свойства</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Сканировать Подпапки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Удалить Папку с Играми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Открыть Расположение Папки</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Игра идёт безупречно, без звуковых или графических артефактов, все протестированные функции работают без обходных путей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Отлично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Нормально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Плохо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Вступление/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>В игру невозможно играть из-за графических или звуковых артефактов. Невозможно продвинуться дальше Стартового Экрана.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не Запускается</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Игра падает при запуске.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Не Проверено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Игру ещё не проверяли на совместимость.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Двойной клик, чтобы добавить новую папку в список игр</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Поиск:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Введите текст для поиска</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Загрузка Шейдера 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Загрузка Шейдера %v из %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Примерное время 5м 4с</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Загрузка...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Загрузка Шейдеров %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Запуск...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Примерное Время %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Недавние Файлы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Эмуляция</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Вид</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Отладка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Инструменты</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Помощь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Загрузить Файл...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Загрузить Папку...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Выход</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Начать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Стоп</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Переинициализировать ключи...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>О yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Режим Одного Окна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Настроить...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Отображать Заголовки Виджетов Дока</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Показать Панель Поиска</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Показать Панель Статуса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Полный Экран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Перезапустить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Загрузить Amiibo…</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Сообщить о Совместимости</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Открыть Папку yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Сделать Скриншот</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>МикроПрофиль</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Установленные SD Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Установленные NAND Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Системные Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Добавить Новую Папку с Играми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Д-Пад %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Ось %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Кнопка %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[неизвестно]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[не используется]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Ось %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Произошла ошибка.
+Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО.
+
+Код Ошибки: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Произошла ошибка на %1 в %2.
+Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО.
+
+Код Ошибки: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Произошла ошибка.
+Код Ошибки: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Выберите Пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Пользователи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Выбор Профиля</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Введите текст:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Программная Клавиатура</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Введите сочетание</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Стэк вызовов</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>ожидание мьютекса 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ожидающие: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>ссылка на владельца: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>в ожидании всех объектов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>в ожидании одного из объектов</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>работает</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>готов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>приостановлен</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>ожидание возврата HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>сон</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>ожидание ответа IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>ожидание объектов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>ожидание мьютекса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>ожидание условной переменной </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>ожидание адресного арбитра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>бездействует</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>мертв</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>ядро %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Неизвестный процессор %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>процессор = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>идеальное ядро = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>маска сходства = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id потока = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>приоритет = %1(текущий) / %2(обычный)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>последние тики = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>не ожидает мьютекс</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>ожидается потоком</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Дерево Ожидания</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts
new file mode 100644
index 000000000..f953f224a
--- /dev/null
+++ b/dist/languages/zh_CN.ts
@@ -0,0 +1,4747 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="zh_CN" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>关于 yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu 是一个实验性的开源 Nintendo Switch 模拟器,以 GPLv2.0+ 授权。&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;这个软件不应该用来运行非法取得的游戏。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;网站&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;源代码&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;贡献者&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;许可证&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; 是任天堂的商标。yuzu 与任天堂没有任何关系。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>正在与服务器通信…</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>取消</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>触摸你的触摸板&lt;br&gt;的左上角。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>触摸你的触摸板&lt;br&gt;的右下角。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>配置完成!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>确定</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>报告兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>报告游戏兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;如果您选择向 &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu 兼容性列表&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;提交测试用例的话,以下信息将会被收集并显示在网站上:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;设备硬件信息 (CPU / GPU / 操作系统)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;您正在使用的 yuzu 版本&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;已关联的 yuzu 账户信息&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>完美</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行完美,没有音频或图形问题。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>良好</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>一般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>较差</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>开场 / 菜单</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏完全没法玩,图像或音频有重大错误。通过开场菜单后无法继续。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>无法打开</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;在启动游戏时直接崩溃了。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;body&gt;&lt;html&gt;&lt;head/&gt;&lt;p&gt;在不考虑速度或帧率的情况下,使用此版本 yuzu 玩这款游戏的情况如何?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>感谢您向我们提交信息!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>提交中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>网络错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>在提交测试用例时发生错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>下一步</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>输出引擎:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>这种后处理效果可以调整音频速度以匹配模拟速度,并有助于防止音频卡顿。 但是会增加音频延迟。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>启动音频拉伸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>音频设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>使用全局音量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>精度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>精确</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>低精度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>启用调试模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>我们推荐将精度设置为“精确”。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>低精度 CPU 优化选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>这些设置项提高了速度,但精度有所降低。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>低精度 FMA (在 CPU 不支持 FMA 指令集的情况下提高性能)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过降低积和熔加运算的精度而提高模拟器在不支持 FMA 指令集 CPU 上的运行速度。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>快速 FRSQRTE 和 FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过使用精度较低的近似值来提高某些浮点函数的运算速度。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,CPU 设置才可用。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>CPU 调试模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>CPU 调试模式仅用于开发人员。您确定要打开这个选项吗?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>切换 CPU 优化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;
+&lt;b&gt;仅供调试使用。&lt;/b&gt;
+&lt;br&gt;
+如果您不确定这些选项的作用,则保持它们的启用状态。
+&lt;br&gt;
+这些选项仅在 CPU 精度处于“调试模式”时生效。
+&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>启用内嵌页表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div style=&quot;white-space: nowrap&quot;&gt;这个选项提升了来宾程序的内存访问速度。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;启用内嵌到 PageTable::指向已发射代码的指针。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;禁用此选项将强制通过 Memory::Read/Memory::Write 函数进行内存访问。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>启用块链接</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过允许发出的基本块直接跳转到其他基本块(如果目标 PC 是静态的)来避免调度器的查找。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>启用返回堆栈缓冲区</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过跟踪 BL 指令的潜在返回地址来避免调度器查找。这近似于 CPU 返回堆栈缓冲区的情况。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>启用快速调度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用两层调度系统。首先使用一个更快的调度程序跳转至目标 MRU 缓存。如果失败,调度返回到较慢的 C++ 调度程序。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>启用上下文消除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用 IR 优化,以减少 CPU 对上下文结构的不必要访问。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>启用恒定传播</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用涉及恒定传播的 IR 优化。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>启用其他优化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用其他的 IR 优化。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>减少偏差检查</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div style=&quot;white-space: nowrap&quot;&gt;启用时,只有当访问越过页面边界时才会触发偏移。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;禁用时,所有未对齐的访问都会触发偏移。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,CPU 设置才可用。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>开启 GDB 调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>端口:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>日志</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>全局日志过滤器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>显示日志窗口(仅限 Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>打开日志位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>自制游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>参数字符串</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>启用时,图形 API 将进入较慢的调试模式。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>启用图形调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>启用时,将禁用宏即时编译器。这会降低游戏运行速度。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>禁用宏 JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>转储</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>启用详细报告服务</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>当 yuzu 关闭时会自动重置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk (Quest) 模式</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>调试控制器设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>界面</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>游戏列表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>用户配置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>文件系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>控制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>热键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>高级图形选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>网络</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>服务</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>存储目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD 卡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>游戏卡带</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>路径</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>已插入</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>当前游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>补丁管理</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>转储已解压的 NSO 文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>转储 ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod 加载根目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>转储根目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>缓存中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>缓存目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>缓存游戏列表数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>重置缓存数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>选择模拟 NAND 目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>选择模拟 SD 卡目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>选择游戏卡带路径...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>选择转储目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>选择 Mod 载入目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>选择缓存目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>缓存数据已为空。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作已成功完成。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>缓存数据删除失败。它可能不存在或正在被使用。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>运行速度限制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>多核 CPU 仿真</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>在游戏运行时退出需要确认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>游戏启动时提示选择用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>模拟器位于后台时暂停模拟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>自动隐藏鼠标光标</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>图形设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>使用磁盘着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>使用 GPU 异步模拟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>屏幕纵横比:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>默认 (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>强制 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>强制 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>拉伸窗口</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>使用全局背景颜色</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>设置背景颜色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>背景颜色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGL 图形设备</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>高级图形选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>精度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>垂直同步可防止画面产生撕裂感。但启用垂直同步后,某些设备性能可能会有所降低。如果您没有感到性能差异,请保持启用状态。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>启用垂直同步 (仅限 OpenGL 模式)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>在支持 NV_gpu_program5 的 Nvidia 设备上启用 OpenGL 程序集着色器。启用此项将减少着色器卡顿。实验性功能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>启用程序集着色器 (实验性,仅限 Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>启用异步着色器编译,这可能会减少着色器卡顿。实验性功能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>启用异步着色器编译 (实验性)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>启用快速 GPU 时钟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>各向异性过滤:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16×</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>热键设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>双击已绑定的项目以改变设定。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>全部清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>作用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>热键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>按键冲突</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>输入的密钥序列已分配给: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>默认密钥序列已分配给: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>玩家 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>玩家 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>玩家 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>玩家 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>玩家 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>玩家 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>玩家 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>玩家 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>高级</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>控制台模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Docked</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Undocked</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>震动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>体感</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>控制器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>已连接</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joycon 颜色</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>玩家 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>左侧主体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>左侧按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>右侧主体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>右侧按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>玩家 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>玩家 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>玩家 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>玩家 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>玩家 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>玩家 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>玩家 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>其他</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>键盘</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>触摸屏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>鼠标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>体感/触摸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>控制器调试</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>已连接控制器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>双 Joycons 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>左 Joycon 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>右 Joycon 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>掌机模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>输入设备</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>任意</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>键盘/鼠标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>用户配置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>保存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>新建</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>左摇杆</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>上</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>按下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>轻推</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>灵敏度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>摇杆死区:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>摇杆灵敏度:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>十字方向键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>-</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>主要按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>右摇杆</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>摇杆死区:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>摇杆灵敏度:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[等待中]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>体感/触摸设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>体感</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>体感设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>灵敏度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>触摸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>触摸设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>触摸校准:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>使用按键映射:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>您可以使用任何与 Cemuhook 兼容的 UDP 输入源来提供体感和触摸输入。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>服务器:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>端口:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>了解更多</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>测试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>鼠标 (右键)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>模拟器窗口</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;了解更多&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>测试中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>配置中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>测试成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>已成功地从服务器获取数据。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>测试失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>无法从服务器获取数据。&lt;br&gt;请验证服务器是否正在运行,以及地址和端口是否配置正确。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDP 测试或触摸校准正在进行中。&lt;br&gt;请耐心等待。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>鼠标设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>鼠标按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>前:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>后:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>左:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>中:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>右:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[未设置]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[请按一个键]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>对话框</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>信息</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>游戏 ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>文件名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>格式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>版本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>开发商</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>附加项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>高级图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>使用全局设置 (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>补丁名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>版本</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>用户配置管理</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>当前用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>用户名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>设置图像</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>添加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>重命名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>移除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,才能进行用户配置的管理。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>输入用户名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>输入新用户的用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>输入新的用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>确认删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>您确定要删除用户名为 “%1” 的用户吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>选择用户图像</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG 图像 (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>删除图像时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>尝试覆盖该用户的现有图像时出错: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>删除文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>无法删除文件: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>创建用户图像目录时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>无法创建存储用户图像的目录 %1 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>复制用户图像时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>无法将图像从 %1 复制到 %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT 是任天堂向游戏发送数据的一种方式,以此吸引游戏玩家,并可能解锁额外的内容。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT 后端</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;了解关于 BCAT、Boxcat 和当前事件的更多信息&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Boxcat 服务处于离线状态,或者您没有连接到互联网。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>处理 Boxcat 事件数据时出错。请联系 yuzu 开发者。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>您使用的 yuzu 版本过新或过旧。尝试更新到官方的最新版本。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>当前 Boxcat 上没有事件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>当前 Boxcat 事件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu 正在检索 Boxcat 的最新状态...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>系统设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>地区:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>自动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>欧洲中部时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>古巴标准时间&amp;古巴夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>古巴</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>东欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>埃及</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>爱尔兰</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>东部标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>东部标准时间&amp;东部夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>国家标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>爱尔兰国家标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>格林威治标准时间 (GMT)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>格林威治</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>中国香港</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>美国夏威夷时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>冰岛</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>伊朗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>以色列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>牙买加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>日本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>夸贾林环礁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>利比亚</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>中欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>山区标准时间 (北美)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>山区标准时间&amp;山区夏令时 (北美)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>纳瓦霍</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>新西兰时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>新西兰-查塔姆群岛</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>波兰</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>葡萄牙</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>中国标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>太平洋标准时间&amp;太平洋夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>新加坡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>土耳其</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>世界时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>协调世界时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>欧洲-莫斯科时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>西欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>祖鲁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>美国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>欧洲</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>澳大利亚</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>朝鲜</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>中国台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>时区:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>注意:当“地区”设置是“自动选择”时,此设置可能会被覆盖。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>日语 (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>英语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>法语 (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>德语 (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>意大利语 (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>西班牙语 (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>韩语 (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>荷兰语 (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>葡萄牙语 (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>俄语 (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>台湾中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>英式英语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>加拿大法语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>拉美西班牙语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>简体中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>繁体中文 (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>自定义系统时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>语言</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>随机数生成器种子</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>单声道</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>立体声</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>环绕声</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>设备 ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>声音输出模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>重置 ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,系统设置才可用。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>这将使用一个新的虚拟 Switch 取代你当前的虚拟 Switch。您当前的虚拟 Switch 将无法恢复。在部分游戏中可能会出现意外效果。如果你使用一个过时的配置存档这可能会失败。确定要继续吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>设备 ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>配置触摸屏映射</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>映射:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>新建</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>重命名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>单击屏幕底部区域添加点位,然后按下按键进行绑定。
+拖动点位以改变位置,或双击列表项更改绑定。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>删除点位</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>保存自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>为新的自定义设置命名。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>删除自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>真的要删除自定义设置 %1 吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>重命名自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>新名称:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[请按一个键]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>触摸屏设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>警告:此页面中的设置会影响 yuzu 的模拟触摸屏的内部工作方式。改变它们可能造成不良后果,例如触摸屏完全或部分失效。如果您不知道自己在做什么,请不要使用此页面。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>触摸参数</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>触摸直径 Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>手指</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>触摸直径 X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>旋转角度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>恢复默认</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>注意: 切换语言将应用您的配置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>界面语言:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>主题:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>游戏列表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>显示“附加项”列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>图标大小:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>第一行:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>第二行:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>询问保存截图的位置 (仅限 Windows )</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>截图保存位置:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>选择截图保存位置...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>英语</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu 网络服务</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>提供您的用户名和令牌意味着您同意让 yuzu 收集额外的使用数据,其中可能包括用户识别信息。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>验证</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>注册</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>令牌:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>我的令牌是?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>使用数据共享</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>与 yuzu 团队共享匿名使用数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>了解更多</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>数据 ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>重新生成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord 状态</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>在您的 Discord 状态中显示当前游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;了解更多&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;注册&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;我的令牌是?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>数据 ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>未指定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>令牌未被验证</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>令牌未被验证。您对用户名和令牌的更改尚未保存。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>验证中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>验证失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>验证失败。请检查您输入的令牌是否正确,并且确保您的互联网连接正常。</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;我们收集匿名数据&lt;/a&gt;来帮助改进 yuzu 。&lt;br/&gt;&lt;br/&gt;您愿意和我们分享您的使用数据吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>使用数据共享</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>文本检查失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>正在加载 Web 应用程序...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>退出 Web 应用程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>退出</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>若要退出 Web 应用程序,请使用游戏内提供的方式退出,在菜单栏中选择“退出 Web 应用程序”选项,或者按 &quot;Enter&quot; 键。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web 应用程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>此版本的 yuzu 没有 QtWebEngine 的支持。这意味着 yuzu 无法正确显示所请求的游戏手册或网页。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>当前正在构建的着色器的数量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>当前的模拟速度。高于或低于 100% 的值表示模拟正在运行得比实际 Switch 更快或更慢。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>游戏当前运行的帧率。这将因游戏和场景的不同而有所变化。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>在不计算速度限制和垂直同步的情况下,模拟一个 Switch 帧的实际时间。若要进行全速模拟,这个数值不应超过 16.67 毫秒。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>异步</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>多核</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>清除最近文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>过时游戏格式警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>目前使用的游戏为解体的 ROM 目录格式,这是一种过时的格式,已被其他格式替代,如 NCA,NAX,XCI 或 NSP。解体的 ROM 目录缺少图标、元数据和更新支持。&lt;br&gt;&lt;br&gt;有关 yuzu 支持的各种 Switch 格式的说明,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;请查看我们的 wiki&lt;/a&gt;。此消息将不会再次出现。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>加载 ROM 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>该 ROM 格式不受支持。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>在初始化视频核心时发生错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu 在运行视频核心时遇到错误,请查看日志了解更多详细信息。有关查看日志的更多信息,请参阅以下页面:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;。请确保您的 GPU 安装了最新的图形驱动程序。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>加载 ROM 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>发生了未知错误。请查看日志了解详情。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>开始</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>保存数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod 数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>打开 %1 文件夹时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>文件夹不存在!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>打开可转移着色器缓存时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>这个游戏的着色器缓存不存在。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>删除项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>删除已安装的游戏 %1 ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>删除成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>成功删除已安装的游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>删除 %1 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>该游戏未安装于 NAND 中,无法删除。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>成功删除已安装的游戏更新。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>这个游戏没有任何已安装的更新。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>这个游戏没有任何已安装的 DLC 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>成功删除游戏 %1 安装的 DLC 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>删除着色器缓存?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>移除自定义游戏设置?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>删除文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>删除着色器缓存时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>成功删除着色器缓存。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>删除着色器缓存失败。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>移除自定义游戏设置时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>这个游戏的自定义设置不存在。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>成功移除自定义游戏设置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>移除自定义游戏设置失败。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS 提取失败!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>复制 RomFS 文件时出错,或用户取消了操作。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>完整</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>框架</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>选择 RomFS 转储模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>请选择希望 RomFS 转储的方式。&lt;br&gt;“Full” 会将所有文件复制到新目录中,而&lt;br&gt;“Skeleton” 只会创建目录结构。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>正在提取 RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>取消</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS 提取成功!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作成功完成。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>打开 %1 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>选择目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>无法加载该游戏的属性信息。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch 可执行文件 (%1);;所有文件 (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>加载文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>打开提取的 ROM 目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>选择的目录无效</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>选择的目录不包含 “main” 文件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>可安装的 Switch 文件 (*.nca *.nsp *.xci);;任天堂内容档案 (*.nca);;任天堂应用包 (*.nsp);;NX 卡带镜像 (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>安装文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>正在安装文件 &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>安装结果</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>系统应用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>系统档案</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>系统应用更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>固件包 (A型)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>固件包 (B型)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>游戏 DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>差量程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>选择 NCA 安装类型...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>请选择此 NCA 的程序类型:
+(在大多数情况下,选择默认的“游戏”即可。)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>安装失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>选择的 NCA 程序类型无效。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>找不到文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>文件 &quot;%1&quot; 未找到</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>继续</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>错误显示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>未设置 yuzu 账户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>要提交游戏兼容性测试用例,您必须设置您的 yuzu 帐户。&lt;br&gt;&lt;br/&gt;要设置您的 yuzu 帐户,请转到模拟 &amp;gt; 设置 &amp;gt; 网络。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>打开 URL 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>无法打开 URL : &quot;%1&quot; 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo 文件 (%1);; 全部文件 (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>加载 Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>打开 Amiibo 数据文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>无法打开 Amiibo 文件 %1。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>读取 Amiibo 数据文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>无法完全读取 Amiibo 数据。应读取 %1 个字节,但实际仅能读取 %2 个字节。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>加载 Amiibo 数据时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>无法加载 Amiibo 数据。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG 图像 (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>速度: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>速度: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>FPS: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>帧延迟:%1 毫秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>您正尝试启动的游戏需要从 Switch 转储的其他文件。&lt;br/&gt;&lt;br/&gt;有关转储这些文件的更多信息,请参阅以下 wiki 页面:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>Yuzu 找不到 Switch 系统档案 %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>Yuzu 找不到 Switch 系统档案: %1, %2 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>未找到系统档案</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>系统档案缺失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>Yuzu 找不到 Swtich 共享字体 %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>未找到共享字体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>共享字体文件缺失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>致命错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu 遇到了致命错误,请查看日志了解详情。有关查找日志的更多信息,请参阅以下页面:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>发生致命错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>确认重新生成密钥</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>即将强制重新生成您的全部密钥。
+如果您不清楚这意味着什么,或您在做什么,
+这可能具有破坏性后果。
+请确保您希望这样做,并且做好备份。
+
+这将删除您自动生成的密钥文件并重新运行密钥生成模块。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>项目丢失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation>- 丢失 BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - 丢失 BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation>- 丢失 PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>组件丢失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>缺少组件可能会影响密钥的使用。&lt;br&gt;请查看&lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;yuzu 快速导航&lt;/a&gt;以获得你的密钥和游戏。&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>正在生成密钥...
+这可能需要最多一分钟,具体取决于
+您的系统性能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>生成密钥</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>选择 RomFS 转储目标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>请选择希望转储的 RomFS。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>您确定要关闭 yuzu 吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>您确定要停止模拟吗?未保存的进度将会丢失。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>当前运行的应用程序请求 yuzu 不要退出。
+
+您希望忽略并退出吗?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL 模式不可用!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu 没有使用 OpenGL 进行编译。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan 模式不可用!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu 没有使用 Vulkan 进行编译。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>初始化 OpenGL 4.3 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>您的 GPU 可能不支持 OpenGL 4.3 ,或者您没有安装最新的显卡驱动。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>初始化 OpenGL 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>您的 GPU 可能不支持某些必需的 OpenGL 扩展。请确保您已经安装最新的显卡驱动。&lt;br&gt;&lt;br&gt;不支持的扩展:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>附加项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>文件类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>打开存档位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>打开 MOD 数据位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>打开可转移着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>删除已安装的游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>删除所有已安装 DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>删除着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>删除自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>删除所有安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>转储 RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>复制游戏 ID 到剪贴板</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>查看兼容性报告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>扫描子文件夹</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>移除游戏目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ 向上移动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ 向下移动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>打开目录位置</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>完美</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>游戏功能完美,没有音频或图形问题,所有测试功能按预期工作,无需需要任何解决办法。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>良好</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>一般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>较差</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>开场 / 菜单</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>游戏完全没法玩,图像或音频有重大错误。通过开场菜单后无法继续。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>无法打开</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>在启动游戏时直接崩溃了。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>未测试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>游戏尚未经过测试。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>双击以添加新的游戏文件夹</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>搜索:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>搜索游戏</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>请确认这些您想要安装的文件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>安装游戏更新或 DLC 时,会覆盖以前安装的内容。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>安装</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>安装文件到 NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>正在加载着色器: 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>正在加载着色器: %v / %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>所需时间: 5 分 4 秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>加载中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>正在加载着色器: %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>启动中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>所需时间: %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>文件 (&amp;F)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>最近文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>模拟 (&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>视图 (&amp;V)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>工具</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>帮助 (&amp;H)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>安装文件到 NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>加载文件...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>加载文件夹...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>退出 (&amp;X)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>开始 (&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>暂停 (&amp;P)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>停止 (&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>重新生成密钥...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>关于 yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>单窗口模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>设置...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>显示停靠小部件的标题</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>显示搜索栏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>显示状态栏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>重置窗口大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>全屏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>重新启动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>加载 Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>报告兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>打开 Mod 页面</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>查看快速导航</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>打开 yuzu 文件夹</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>配置当前游戏...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>SD 卡中安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>NAND 中安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>系统项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>添加游戏目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[未设置]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>方向键 %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>轴 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>按键 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[未知]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>点击 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>点击 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>点击 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>点击 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>点击 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC 轴 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC 按键 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[未使用]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>轴 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC 轴 %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>发生了一个错误。
+请再试一次或联系开发者。
+
+错误代码: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>在 %2 处的 %1 上发生了一个错误。
+请再试一次或联系开发者。
+
+错误代码: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>发生了一个错误。
+错误代码: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>选择一个用户:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>选择用户</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>输入文本:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>软件键盘</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>键入热键</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>调用栈</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>正在等待互斥锁 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>等待中: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>所有者句柄: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>正在等待所有对象</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>正在等待下列对象中的一个</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>没有等待的线程</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>运行中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>就绪</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>暂停</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>等待 HLE 返回</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>睡眠</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>等待 IPC 响应</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>等待对象</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>等待互斥锁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>等待条件变量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>等待 address arbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>休眠</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>死亡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>核心 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>未知的处理器 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>处理器 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>理想核心 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>关联掩码 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>线程 ID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>优先级 = %1 (实时) / %2 (正常)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>最后运行频率 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>未等待互斥锁</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>等待中的线程</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>等待树</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/license.md b/dist/license.md
index f1ff35c95..e9bc87656 100644
--- a/dist/license.md
+++ b/dist/license.md
@@ -5,6 +5,7 @@ Icon Name | License | Origin/Author
qt_themes/default/icons/16x16/checked.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/failed.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
+qt_themes/default/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
@@ -12,6 +13,7 @@ qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
+qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
@@ -19,6 +21,7 @@ qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
+qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
diff --git a/dist/qt_themes/colorful_dark/icons/16x16/refresh.png b/dist/qt_themes/colorful_dark/icons/16x16/refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/16x16/refresh.png
Binary files differ
diff --git a/dist/qt_themes/colorful_dark/icons/16x16/view-refresh.png b/dist/qt_themes/colorful_dark/icons/16x16/view-refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/16x16/view-refresh.png
Binary files differ
diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc
index 27a6cc87d..0abcb4e83 100644
--- a/dist/qt_themes/colorful_dark/style.qrc
+++ b/dist/qt_themes/colorful_dark/style.qrc
@@ -2,6 +2,7 @@
<qresource prefix="icons/colorful_dark">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
+ <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/16x16/lock.png b/dist/qt_themes/colorful_midnight_blue/icons/16x16/lock.png
new file mode 100644
index 000000000..32c505848
--- /dev/null
+++ b/dist/qt_themes/colorful_midnight_blue/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/16x16/refresh.png b/dist/qt_themes/colorful_midnight_blue/icons/16x16/refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/colorful_midnight_blue/icons/16x16/refresh.png
Binary files differ
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/16x16/view-refresh.png b/dist/qt_themes/colorful_midnight_blue/icons/16x16/view-refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/colorful_midnight_blue/icons/16x16/view-refresh.png
Binary files differ
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/index.theme b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
new file mode 100644
index 000000000..e23bfe6f9
--- /dev/null
+++ b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
@@ -0,0 +1,8 @@
+[Icon Theme]
+Name=colorful_midnight_blue
+Comment=Colorful theme (Midnight Blue style)
+Inherits=default
+Directories=16x16
+
+[16x16]
+Size=16
diff --git a/dist/qt_themes/colorful_midnight_blue/style.qrc b/dist/qt_themes/colorful_midnight_blue/style.qrc
new file mode 100644
index 000000000..bf367099a
--- /dev/null
+++ b/dist/qt_themes/colorful_midnight_blue/style.qrc
@@ -0,0 +1,58 @@
+<RCC>
+ <qresource prefix="icons/colorful_midnight_blue">
+ <file alias="index.theme">icons/index.theme</file>
+ <file alias="16x16/lock.png">icons/16x16/lock.png</file>
+ <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
+ <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
+ <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
+ <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
+ <file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
+ <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
+ <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
+ </qresource>
+
+ <qresource prefix="qss_icons">
+ <file alias="rc/up_arrow_disabled.png">../qdarkstyle_midnight_blue/rc/up_arrow_disabled.png</file>
+ <file alias="rc/Hmovetoolbar.png">../qdarkstyle_midnight_blue/rc/Hmovetoolbar.png</file>
+ <file alias="rc/stylesheet-branch-end.png">../qdarkstyle_midnight_blue/rc/stylesheet-branch-end.png</file>
+ <file alias="rc/branch_closed-on.png">../qdarkstyle_midnight_blue/rc/branch_closed-on.png</file>
+ <file alias="rc/stylesheet-vline.png">../qdarkstyle_midnight_blue/rc/stylesheet-vline.png</file>
+ <file alias="rc/branch_closed.png">../qdarkstyle_midnight_blue/rc/branch_closed.png</file>
+ <file alias="rc/branch_open-on.png">../qdarkstyle_midnight_blue/rc/branch_open-on.png</file>
+ <file alias="rc/transparent.png">../qdarkstyle_midnight_blue/rc/transparent.png</file>
+ <file alias="rc/right_arrow_disabled.png">../qdarkstyle_midnight_blue/rc/right_arrow_disabled.png</file>
+ <file alias="rc/sizegrip.png">../qdarkstyle_midnight_blue/rc/sizegrip.png</file>
+ <file alias="rc/close.png">../qdarkstyle_midnight_blue/rc/close.png</file>
+ <file alias="rc/close-hover.png">../qdarkstyle_midnight_blue/rc/close-hover.png</file>
+ <file alias="rc/close-pressed.png">../qdarkstyle_midnight_blue/rc/close-pressed.png</file>
+ <file alias="rc/down_arrow.png">../qdarkstyle_midnight_blue/rc/down_arrow.png</file>
+ <file alias="rc/Vmovetoolbar.png">../qdarkstyle_midnight_blue/rc/Vmovetoolbar.png</file>
+ <file alias="rc/left_arrow.png">../qdarkstyle_midnight_blue/rc/left_arrow.png</file>
+ <file alias="rc/stylesheet-branch-more.png">../qdarkstyle_midnight_blue/rc/stylesheet-branch-more.png</file>
+ <file alias="rc/up_arrow.png">../qdarkstyle_midnight_blue/rc/up_arrow.png</file>
+ <file alias="rc/right_arrow.png">../qdarkstyle_midnight_blue/rc/right_arrow.png</file>
+ <file alias="rc/left_arrow_disabled.png">../qdarkstyle_midnight_blue/rc/left_arrow_disabled.png</file>
+ <file alias="rc/Hsepartoolbar.png">../qdarkstyle_midnight_blue/rc/Hsepartoolbar.png</file>
+ <file alias="rc/branch_open.png">../qdarkstyle_midnight_blue/rc/branch_open.png</file>
+ <file alias="rc/Vsepartoolbar.png">../qdarkstyle_midnight_blue/rc/Vsepartoolbar.png</file>
+ <file alias="rc/down_arrow_disabled.png">../qdarkstyle_midnight_blue/rc/down_arrow_disabled.png</file>
+ <file alias="rc/undock.png">../qdarkstyle_midnight_blue/rc/undock.png</file>
+ <file alias="rc/checkbox_checked_disabled.png">../qdarkstyle_midnight_blue/rc/checkbox_checked_disabled.png</file>
+ <file alias="rc/checkbox_checked_focus.png">../qdarkstyle_midnight_blue/rc/checkbox_checked_focus.png</file>
+ <file alias="rc/checkbox_checked.png">../qdarkstyle_midnight_blue/rc/checkbox_checked.png</file>
+ <file alias="rc/checkbox_indeterminate.png">../qdarkstyle_midnight_blue/rc/checkbox_indeterminate.png</file>
+ <file alias="rc/checkbox_indeterminate_focus.png">../qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus.png</file>
+ <file alias="rc/checkbox_unchecked_disabled.png">../qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled.png</file>
+ <file alias="rc/checkbox_unchecked_focus.png">../qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus.png</file>
+ <file alias="rc/checkbox_unchecked.png">../qdarkstyle_midnight_blue/rc/checkbox_unchecked.png</file>
+ <file alias="rc/radio_checked_disabled.png">../qdarkstyle_midnight_blue/rc/radio_checked_disabled.png</file>
+ <file alias="rc/radio_checked_focus.png">../qdarkstyle_midnight_blue/rc/radio_checked_focus.png</file>
+ <file alias="rc/radio_checked.png">../qdarkstyle_midnight_blue/rc/radio_checked.png</file>
+ <file alias="rc/radio_unchecked_disabled.png">../qdarkstyle_midnight_blue/rc/radio_unchecked_disabled.png</file>
+ <file alias="rc/radio_unchecked_focus.png">../qdarkstyle_midnight_blue/rc/radio_unchecked_focus.png</file>
+ <file alias="rc/radio_unchecked.png">../qdarkstyle_midnight_blue/rc/radio_unchecked.png</file>
+ </qresource>
+ <qresource prefix="colorful_midnight_blue">
+ <file alias="style.qss">../qdarkstyle_midnight_blue/style.qss</file>
+ </qresource>
+</RCC>
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index c51fdb26c..2182f33f3 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -4,6 +4,7 @@
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
+ <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
diff --git a/dist/qt_themes/default/icons/16x16/refresh.png b/dist/qt_themes/default/icons/16x16/refresh.png
new file mode 100644
index 000000000..69f9474ac
--- /dev/null
+++ b/dist/qt_themes/default/icons/16x16/refresh.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/16x16/view-refresh.png b/dist/qt_themes/default/icons/16x16/view-refresh.png
new file mode 100644
index 000000000..69f9474ac
--- /dev/null
+++ b/dist/qt_themes/default/icons/16x16/view-refresh.png
Binary files differ
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss
index 6b5953e38..836dd25ca 100644
--- a/dist/qt_themes/default/style.qss
+++ b/dist/qt_themes/default/style.qss
@@ -1,3 +1,7 @@
+QAbstractSpinBox {
+ min-height: 19px;
+}
+
QPushButton#TogglableStatusBarButton {
color: #959595;
border: 1px solid transparent;
@@ -30,6 +34,250 @@ QPushButton#RendererStatusBarButton:checked {
color: #e85c00;
}
-QPushButton#RendererStatusBarButton:!checked{
+QPushButton#RendererStatusBarButton:!checked {
color: #0066ff;
}
+
+QPushButton#buttonRefreshDevices {
+ min-width: 21px;
+ min-height: 21px;
+ max-width: 21px;
+ max-height: 21px;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #f5f5f5;
+}
+
+QWidget#topControllerApplet {
+ border-bottom: 1px solid #828790
+}
+
+QWidget#bottomPerGameInput,
+QWidget#bottomControllerApplet {
+ border-top: 1px solid #828790
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #fff;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #828790;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+}
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #d9d9d9;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: 0px;
+ margin-right: -4px;
+ margin-bottom: 4px;
+}
+
+QCheckBox#checkboxPlayer1Connected,
+QCheckBox#checkboxPlayer2Connected,
+QCheckBox#checkboxPlayer3Connected,
+QCheckBox#checkboxPlayer4Connected,
+QCheckBox#checkboxPlayer5Connected,
+QCheckBox#checkboxPlayer6Connected,
+QCheckBox#checkboxPlayer7Connected,
+QCheckBox#checkboxPlayer8Connected {
+ spacing: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox {
+ spacing: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
+QCheckBox#checkboxPlayer1Connected::indicator,
+QCheckBox#checkboxPlayer2Connected::indicator,
+QCheckBox#checkboxPlayer3Connected::indicator,
+QCheckBox#checkboxPlayer4Connected::indicator,
+QCheckBox#checkboxPlayer5Connected::indicator,
+QCheckBox#checkboxPlayer6Connected::indicator,
+QCheckBox#checkboxPlayer7Connected::indicator,
+QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 14px;
+ height: 14px;
+}
+
+QGroupBox#groupPlayer1Connected::indicator,
+QGroupBox#groupPlayer2Connected::indicator,
+QGroupBox#groupPlayer3Connected::indicator,
+QGroupBox#groupPlayer4Connected::indicator,
+QGroupBox#groupPlayer5Connected::indicator,
+QGroupBox#groupPlayer6Connected::indicator,
+QGroupBox#groupPlayer7Connected::indicator,
+QGroupBox#groupPlayer8Connected::indicator {
+ width: 16px;
+ height: 16px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
+QCheckBox#checkboxPlayer1Connected::indicator:checked,
+QCheckBox#checkboxPlayer2Connected::indicator:checked,
+QCheckBox#checkboxPlayer3Connected::indicator:checked,
+QCheckBox#checkboxPlayer4Connected::indicator:checked,
+QCheckBox#checkboxPlayer5Connected::indicator:checked,
+QCheckBox#checkboxPlayer6Connected::indicator:checked,
+QCheckBox#checkboxPlayer7Connected::indicator:checked,
+QCheckBox#checkboxPlayer8Connected::indicator:checked,
+QGroupBox#groupConnectedController::indicator:checked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: #39ff14;
+ image: none;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
+QGroupBox#groupConnectedController::indicator:unchecked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: transparent;
+ image: none;
+}
+
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
+}
diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/refresh.png b/dist/qt_themes/qdarkstyle/icons/16x16/refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/16x16/refresh.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/view-refresh.png b/dist/qt_themes/qdarkstyle/icons/16x16/view-refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/16x16/view-refresh.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png
index 830cfee65..dd891298f 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png
index cb63cc2fa..44bf145f8 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png
index 671be273b..60238b21d 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png
index 41024f768..830cfee65 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png
index abdc01d90..cb63cc2fa 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png
index 415f9b6e1..671be273b 100644
--- a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png
+++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index c2c14c28a..2b91204f3 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -2,6 +2,7 @@
<qresource prefix="icons/qdarkstyle">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
+ <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
@@ -51,6 +52,6 @@
<file>rc/radio_unchecked.png</file>
</qresource>
<qresource prefix="qdarkstyle">
- <file>style.qss</file>
+ <file>style.qss</file>
</qresource>
</RCC>
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index 7d088a719..2a1e8ddeb 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -40,8 +40,8 @@ QCheckBox:disabled {
QCheckBox::indicator,
QGroupBox::indicator {
- width: 18px;
- height: 18px;
+ width: 16px;
+ height: 16px;
}
QGroupBox::indicator {
@@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled {
}
QRadioButton {
- spacing: 5px;
- outline: none;
color: #eff0f1;
+ spacing: 3px;
+ padding: 0px;
+ border: none;
+ outline: none;
margin-bottom: 2px;
}
+QGroupBox QRadioButton {
+ padding-left: 0px;
+ padding-right: 7px;
+}
+
QRadioButton:disabled {
color: #76797C;
}
@@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
- border-width: 1px;
- border-color: #54575B;
- border-style: solid;
- padding: 6px 4px;
+ border: 1px solid #54575B;
border-radius: 2px;
+ padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
+ min-height: 13px;
background-color: #232629;
}
@@ -553,8 +559,9 @@ QComboBox {
selection-background-color: #3daee9;
border: 1px solid #54575B;
border-radius: 2px;
- padding: 4px 6px;
- min-width: 75px;
+ padding: 0px 4px 0px 4px;
+ min-width: 60px;
+ min-height: 23px;
background-color: #232629;
}
@@ -608,26 +615,26 @@ QComboBox::down-arrow:focus {
}
QAbstractSpinBox {
- padding: 4px 6px;
border: 1px solid #54575B;
background-color: #232629;
color: #eff0f1;
border-radius: 2px;
- min-width: 75px;
+ min-width: 52px;
+ min-height: 23px;
}
QAbstractSpinBox:up-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center right;
- left: -6px;
+ left: -2px;
}
QAbstractSpinBox:down-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center left;
- right: -6px;
+ right: -2px;
}
QAbstractSpinBox::up-arrow,
@@ -654,7 +661,11 @@ QAbstractSpinBox::down-arrow:hover {
image: url(:/qss_icons/rc/down_arrow.png);
}
-QLabel,
+QLabel {
+ border: 0;
+ background: transparent;
+}
+
QTabWidget {
border: 0;
}
@@ -673,10 +684,6 @@ QTabWidget::pane {
border-bottom-left-radius: 2px;
}
-QTabWidget::tab-bar {
- overflow: visible;
-}
-
QTabBar {
qproperty-drawBase: 0;
border-radius: 3px;
@@ -1237,6 +1244,7 @@ QPlainTextEdit:disabled {
background-color: #2b2e31;
}
+
QPushButton#TogglableStatusBarButton {
min-width: 0px;
color: #656565;
@@ -1271,6 +1279,288 @@ QPushButton#RendererStatusBarButton:checked {
color: #e85c00;
}
-QPushButton#RendererStatusBarButton:!checked{
- color: #00ccdd;
-} \ No newline at end of file
+QPushButton#RendererStatusBarButton:!checked {
+ color: #00ccdd;
+}
+
+QPushButton#buttonRefreshDevices {
+ min-width: 23px;
+ min-height: 23px;
+ max-width: 23px;
+ max-height: 23px;
+ padding: 0px 0px;
+}
+
+QSpinBox#spinboxLStickRange,
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+ min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
+QGroupBox#motionGroup::indicator,
+QGroupBox#vibrationGroup::indicator {
+ margin-left: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox::title,
+QGroupBox#motionGroup::title,
+QGroupBox#vibrationGroup::title {
+ spacing: 2px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #232629;
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #31363b;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #76797c;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+ margin-top: 0px;
+}
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #54575b;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: -2px;
+ margin-right: -4px;
+ margin-bottom: 6px;
+}
+
+QCheckBox#checkboxPlayer1Connected,
+QCheckBox#checkboxPlayer2Connected,
+QCheckBox#checkboxPlayer3Connected,
+QCheckBox#checkboxPlayer4Connected,
+QCheckBox#checkboxPlayer5Connected,
+QCheckBox#checkboxPlayer6Connected,
+QCheckBox#checkboxPlayer7Connected,
+QCheckBox#checkboxPlayer8Connected {
+ spacing: 0px;
+}
+
+QWidget#Player1LEDs,
+QWidget#Player2LEDs,
+QWidget#Player3LEDs,
+QWidget#Player4LEDs,
+QWidget#Player5LEDs,
+QWidget#Player6LEDs,
+QWidget#Player7LEDs,
+QWidget#Player8LEDs {
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox {
+ spacing: 0px;
+ margin-bottom: 0px;
+ margin-right: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
+QCheckBox#checkboxPlayer1Connected::indicator,
+QCheckBox#checkboxPlayer2Connected::indicator,
+QCheckBox#checkboxPlayer3Connected::indicator,
+QCheckBox#checkboxPlayer4Connected::indicator,
+QCheckBox#checkboxPlayer5Connected::indicator,
+QCheckBox#checkboxPlayer6Connected::indicator,
+QCheckBox#checkboxPlayer7Connected::indicator,
+QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 14px;
+ height: 14px;
+}
+
+QGroupBox#groupPlayer1Connected::indicator,
+QGroupBox#groupPlayer2Connected::indicator,
+QGroupBox#groupPlayer3Connected::indicator,
+QGroupBox#groupPlayer4Connected::indicator,
+QGroupBox#groupPlayer5Connected::indicator,
+QGroupBox#groupPlayer6Connected::indicator,
+QGroupBox#groupPlayer7Connected::indicator,
+QGroupBox#groupPlayer8Connected::indicator {
+ width: 16px;
+ height: 16px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
+QCheckBox#checkboxPlayer1Connected::indicator:checked,
+QCheckBox#checkboxPlayer2Connected::indicator:checked,
+QCheckBox#checkboxPlayer3Connected::indicator:checked,
+QCheckBox#checkboxPlayer4Connected::indicator:checked,
+QCheckBox#checkboxPlayer5Connected::indicator:checked,
+QCheckBox#checkboxPlayer6Connected::indicator:checked,
+QCheckBox#checkboxPlayer7Connected::indicator:checked,
+QCheckBox#checkboxPlayer8Connected::indicator:checked,
+QGroupBox#groupConnectedController::indicator:checked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: #39ff14;
+ image: none;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
+QGroupBox#groupConnectedController::indicator:unchecked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: transparent;
+ image: none;
+}
+
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
+}
+
+/* touchscreen mapping widget */
+TouchScreenPreview {
+ qproperty-dotHighlightColor: #3daee9;
+}
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/LICENSE.rst b/dist/qt_themes/qdarkstyle_midnight_blue/LICENSE.rst
new file mode 100644
index 000000000..e22b68735
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/LICENSE.rst
@@ -0,0 +1,405 @@
+License
+=======
+
+The MIT License (MIT) - Code
+----------------------------
+
+Copyright (c) 2013-2019 Colin Duquesnoy
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Creative Commons Attribution International 4.0 - Images
+-------------------------------------------------------
+
+QDarkStyle (c) 2013-2019 Colin Duquesnoy
+QDarkStyle (c) 2019-2019 Daniel Cosmo Pizetta
+
+Creative Commons Corporation (“Creative Commons”) is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an “as-is” basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright and
+certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+- **Considerations for licensors:** Our public licenses are intended
+ for use by those authorized to give the public permission to use
+ material in ways otherwise restricted by copyright and certain other
+ rights. Our licenses are irrevocable. Licensors should read and
+ understand the terms and conditions of the license they choose before
+ applying it. Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the material as
+ expected. Licensors should clearly mark any material not subject to
+ the license. This includes other CC-licensed material, or material
+ used under an exception or limitation to copyright. `More
+ considerations for
+ licensors <http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors>`__.
+
+- **Considerations for the public:** By using one of our public
+ licenses, a licensor grants the public permission to use the licensed
+ material under specified terms and conditions. If the licensor’s
+ permission is not necessary for any reason–for example, because of
+ any applicable exception or limitation to copyright–then that use is
+ not regulated by the license. Our licenses grant only permissions
+ under copyright and certain other rights that a licensor has
+ authority to grant. Use of the licensed material may still be
+ restricted for other reasons, including because others have copyright
+ or other rights in the material. A licensor may make special
+ requests, such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to respect
+ those requests where reasonable. `More considerations for the
+ public <http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees>`__.
+
+
+Creative Commons Attribution 4.0 International Public License
+-------------------------------------------------------------
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution 4.0 International Public License ("Public License"). To the
+extent this Public License may be interpreted as a contract, You are
+granted the Licensed Rights in consideration of Your acceptance of these
+terms and conditions, and the Licensor grants You such rights in
+consideration of benefits the Licensor receives from making the Licensed
+Material available under these terms and conditions.
+
+Section 1 – Definitions
+~~~~~~~~~~~~~~~~~~~~~~~
+
+a. **Adapted Material** means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material and
+ in which the Licensed Material is translated, altered, arranged,
+ transformed, or otherwise modified in a manner requiring permission
+ under the Copyright and Similar Rights held by the Licensor. For
+ purposes of this Public License, where the Licensed Material is a
+ musical work, performance, or sound recording, Adapted Material is
+ always produced where the Licensed Material is synched in timed
+ relation with a moving image.
+
+b. **Adapter's License** means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+c. **Copyright and Similar Rights** means copyright and/or similar
+ rights closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or categorized.
+ For purposes of this Public License, the rights specified in Section
+ 2(b)(1)-(2) are not Copyright and Similar Rights.
+
+d. **Effective Technological Measures** means those measures that, in
+ the absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright Treaty
+ adopted on December 20, 1996, and/or similar international
+ agreements.
+
+e. **Exceptions and Limitations** means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+f. **Licensed Material** means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public License.
+
+g. **Licensed Rights** means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to all
+ Copyright and Similar Rights that apply to Your use of the Licensed
+ Material and that the Licensor has authority to license.
+
+h. **Licensor** means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+i. **Share** means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such as
+ reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the public
+ may access the material from a place and at a time individually
+ chosen by them.
+
+j. **Sui Generis Database Rights** means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases, as
+ amended and/or succeeded, as well as other essentially equivalent
+ rights anywhere in the world.
+
+k. **You** means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+Section 2 – Scope
+~~~~~~~~~~~~~~~~~
+
+a. **License grant.**
+
+1. Subject to the terms and conditions of this Public License, the
+ Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to exercise the
+ Licensed Rights in the Licensed Material to:
+
+ A. reproduce and Share the Licensed Material, in whole or in part;
+ and
+
+ B. produce, reproduce, and Share Adapted Material.
+
+2. **Exceptions and Limitations.** For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public License
+ does not apply, and You do not need to comply with its terms and
+ conditions.
+
+3. **Term.** The term of this Public License is specified in Section
+ 6(a).
+
+4. **Media and formats; technical modifications allowed.** The Licensor
+ authorizes You to exercise the Licensed Rights in all media and
+ formats whether now known or hereafter created, and to make technical
+ modifications necessary to do so. The Licensor waives and/or agrees
+ not to assert any right or authority to forbid You from making
+ technical modifications necessary to exercise the Licensed Rights,
+ including technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License, simply
+ making modifications authorized by this Section 2(a)(4) never
+ produces Adapted Material.
+
+5. **Downstream recipients.**
+
+ A. **Offer from the Licensor – Licensed Material.** Every recipient
+ of the Licensed Material automatically receives an offer from the
+ Licensor to exercise the Licensed Rights under the terms and
+ conditions of this Public License.
+
+ B. **No downstream restrictions.** You may not offer or impose any
+ additional or different terms or conditions on, or apply any
+ Effective Technological Measures to, the Licensed Material if doing
+ so restricts exercise of the Licensed Rights by any recipient of the
+ Licensed Material.
+
+6. **No endorsement.** Nothing in this Public License constitutes or may
+ be construed as permission to assert or imply that You are, or that
+ Your use of the Licensed Material is, connected with, or sponsored,
+ endorsed, or granted official status by, the Licensor or others
+ designated to receive attribution as provided in Section
+ 3(a)(1)(A)(i).
+
+b. **Other rights.**
+
+1. Moral rights, such as the right of integrity, are not licensed under
+ this Public License, nor are publicity, privacy, and/or other similar
+ personality rights; however, to the extent possible, the Licensor
+ waives and/or agrees not to assert any such rights held by the
+ Licensor to the limited extent necessary to allow You to exercise the
+ Licensed Rights, but not otherwise.
+
+2. Patent and trademark rights are not licensed under this Public
+ License.
+
+3. To the extent possible, the Licensor waives any right to collect
+ royalties from You for the exercise of the Licensed Rights, whether
+ directly or through a collecting society under any voluntary or
+ waivable statutory or compulsory licensing scheme. In all other cases
+ the Licensor expressly reserves any right to collect such royalties.
+
+Section 3 – License Conditions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+a. **Attribution.**
+
+1. If You Share the Licensed Material (including in modified form), You
+ must:
+
+ A. retain the following if it is supplied by the Licensor with the
+ Licensed Material:
+
+ i. identification of the creator(s) of the Licensed Material and any
+ others designated to receive attribution, in any reasonable manner
+ requested by the Licensor (including by pseudonym if designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
+
+ B. indicate if You modified the Licensed Material and retain an
+ indication of any previous modifications; and
+
+ C. indicate the Licensed Material is licensed under this Public
+ License, and include the text of, or the URI or hyperlink to, this
+ Public License.
+
+2. You may satisfy the conditions in Section 3(a)(1) in any reasonable
+ manner based on the medium, means, and context in which You Share the
+ Licensed Material. For example, it may be reasonable to satisfy the
+ conditions by providing a URI or hyperlink to a resource that
+ includes the required information.
+
+3. If requested by the Licensor, You must remove any of the information
+ required by Section 3(a)(1)(A) to the extent reasonably practicable.
+
+4. If You Share Adapted Material You produce, the Adapter's License You
+ apply must not prevent recipients of the Adapted Material from
+ complying with this Public License.
+
+Section 4 – Sui Generis Database Rights
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Where the Licensed Rights include Sui Generis Database Rights that apply
+to Your use of the Licensed Material:
+
+a. for the avoidance of doubt, Section 2(a)(1) grants You the right to
+ extract, reuse, reproduce, and Share all or a substantial portion of
+ the contents of the database;
+
+b. if You include all or a substantial portion of the database contents
+ in a database in which You have Sui Generis Database Rights, then the
+ database in which You have Sui Generis Database Rights (but not its
+ individual contents) is Adapted Material; and
+
+c. You must comply with the conditions in Section 3(a) if You Share all
+ or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+Section 5 – Disclaimer of Warranties and Limitation of Liability
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+a. Unless otherwise separately undertaken by the Licensor, to the
+ extent possible, the Licensor offers the Licensed Material as-is and
+ as-available, and makes no representations or warranties of any kind
+ concerning the Licensed Material, whether express, implied,
+ statutory, or other. This includes, without limitation, warranties of
+ title, merchantability, fitness for a particular purpose,
+ non-infringement, absence of latent or other defects, accuracy, or
+ the presence or absence of errors, whether or not known or
+ discoverable. Where disclaimers of warranties are not allowed in full
+ or in part, this disclaimer may not apply to You.
+
+b. To the extent possible, in no event will the Licensor be liable to
+ You on any legal theory (including, without limitation, negligence)
+ or otherwise for any direct, special, indirect, incidental,
+ consequential, punitive, exemplary, or other losses, costs, expenses,
+ or damages arising out of this Public License or use of the Licensed
+ Material, even if the Licensor has been advised of the possibility of
+ such losses, costs, expenses, or damages. Where a limitation of
+ liability is not allowed in full or in part, this limitation may not
+ apply to You.
+
+c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent possible,
+ most closely approximates an absolute disclaimer and waiver of all
+ liability.
+
+Section 6 – Term and Termination
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+a. This Public License applies for the term of the Copyright and Similar
+ Rights licensed here. However, if You fail to comply with this Public
+ License, then Your rights under this Public License terminate
+ automatically.
+
+b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+1. automatically as of the date the violation is cured, provided it is
+ cured within 30 days of Your discovery of the violation; or
+
+2. upon express reinstatement by the Licensor.
+
+For the avoidance of doubt, this Section 6(b) does not affect any right
+the Licensor may have to seek remedies for Your violations of this
+Public License.
+
+c. For the avoidance of doubt, the Licensor may also offer the Licensed
+ Material under separate terms or conditions or stop distributing the
+ Licensed Material at any time; however, doing so will not terminate
+ this Public License.
+
+d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+Section 7 – Other Terms and Conditions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+a. The Licensor shall not be bound by any additional or different terms
+ or conditions communicated by You unless expressly agreed.
+
+b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and independent
+ of the terms and conditions of this Public License.
+
+Section 8 – Interpretation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+a. For the avoidance of doubt, this Public License does not, and shall
+ not be interpreted to, reduce, limit, restrict, or impose conditions
+ on any use of the Licensed Material that could lawfully be made
+ without permission under this Public License.
+
+b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+d. Nothing in this Public License constitutes or may be interpreted as a
+ limitation upon, or waiver of, any privileges and immunities that
+ apply to the Licensor or You, including from the legal processes of
+ any jurisdiction or authority.
+
+ Creative Commons is not a party to its public licenses.
+ Notwithstanding, Creative Commons may elect to apply one of its
+ public licenses to material it publishes and in those instances will
+ be considered the “Licensor.” Except for the limited purpose of
+ indicating that material is shared under a Creative Commons public
+ license or as otherwise permitted by the Creative Commons policies
+ published at
+ `creativecommons.org/policies <http://creativecommons.org/policies>`__,
+ Creative Commons does not authorize the use of the trademark
+ “Creative Commons” or any other trademark or logo of Creative
+ Commons without its prior written consent including, without
+ limitation, in connection with any unauthorized modifications to any
+ of its public licenses or any other arrangements, understandings, or
+ agreements concerning use of licensed material. For the avoidance of
+ doubt, this paragraph does not form part of the public licenses.
+
+ Creative Commons may be contacted at creativecommons.org
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/lock.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/lock.png
new file mode 100644
index 000000000..c750a39e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/refresh.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/refresh.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/view-refresh.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/view-refresh.png
new file mode 100644
index 000000000..d4afd76f9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/16x16/view-refresh.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/256x256/plus_folder.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..303f9a321
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/bad_folder.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..4a9709623
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/chip.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/chip.png
new file mode 100644
index 000000000..973fabd05
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/folder.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/folder.png
new file mode 100644
index 000000000..0f1e987d6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/plus.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/plus.png
new file mode 100644
index 000000000..16cc8b4f4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/sd_card.png
new file mode 100644
index 000000000..0291c6542
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme
new file mode 100644
index 000000000..447a6c8be
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme
@@ -0,0 +1,14 @@
+[Icon Theme]
+Name=qdarkstyle_midnight_blue
+Comment=dark theme
+Inherits=default
+Directories=16x16,48x48,256x256
+
+[16x16]
+Size=16
+
+[48x48]
+Size=48
+
+[256x256]
+Size=256
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hmovetoolbar.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hmovetoolbar.png
new file mode 100644
index 000000000..cead99ed1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hmovetoolbar.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hsepartoolbar.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hsepartoolbar.png
new file mode 100644
index 000000000..7f183c8b3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Hsepartoolbar.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vmovetoolbar.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vmovetoolbar.png
new file mode 100644
index 000000000..512edcecd
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vmovetoolbar.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vsepartoolbar.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vsepartoolbar.png
new file mode 100644
index 000000000..d9dc1561b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/Vsepartoolbar.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down.png
new file mode 100644
index 000000000..c4e6894ba
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down@2x.png
new file mode 100644
index 000000000..bb8cbed0d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled.png
new file mode 100644
index 000000000..aa1d06c08
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled@2x.png
new file mode 100644
index 000000000..86bf434b8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus.png
new file mode 100644
index 000000000..1c42ee8f6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus@2x.png
new file mode 100644
index 000000000..7374637c5
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed.png
new file mode 100644
index 000000000..8139ee3e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed@2x.png
new file mode 100644
index 000000000..5e9d225ff
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_down_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left.png
new file mode 100644
index 000000000..ef929fdf0
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left@2x.png
new file mode 100644
index 000000000..c8923d6f4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled.png
new file mode 100644
index 000000000..9c69561a7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled@2x.png
new file mode 100644
index 000000000..e52114312
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus.png
new file mode 100644
index 000000000..a1f070455
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus@2x.png
new file mode 100644
index 000000000..c4267e856
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed.png
new file mode 100644
index 000000000..bd706cbdd
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed@2x.png
new file mode 100644
index 000000000..341b2e541
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_left_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right.png
new file mode 100644
index 000000000..4f3388505
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right@2x.png
new file mode 100644
index 000000000..94b260965
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled.png
new file mode 100644
index 000000000..0fbc6b04c
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled@2x.png
new file mode 100644
index 000000000..8e9272a5b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus.png
new file mode 100644
index 000000000..764940945
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus@2x.png
new file mode 100644
index 000000000..6d52b5fa3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed.png
new file mode 100644
index 000000000..a5f04522a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed@2x.png
new file mode 100644
index 000000000..6f6a8130c
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_right_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up.png
new file mode 100644
index 000000000..61d7574a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up@2x.png
new file mode 100644
index 000000000..d711fae16
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled.png
new file mode 100644
index 000000000..18e8ecd8d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled@2x.png
new file mode 100644
index 000000000..fb4defb52
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus.png
new file mode 100644
index 000000000..a7acd9b66
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus@2x.png
new file mode 100644
index 000000000..9cd982a1d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed.png
new file mode 100644
index 000000000..390a80e21
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed@2x.png
new file mode 100644
index 000000000..dd352cff3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/arrow_up_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon.png
new file mode 100644
index 000000000..37a6158cc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon@2x.png
new file mode 100644
index 000000000..e6e5cb916
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled.png
new file mode 100644
index 000000000..37a6158cc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled@2x.png
new file mode 100644
index 000000000..e6e5cb916
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus.png
new file mode 100644
index 000000000..37a6158cc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus@2x.png
new file mode 100644
index 000000000..e6e5cb916
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed.png
new file mode 100644
index 000000000..37a6158cc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed@2x.png
new file mode 100644
index 000000000..e6e5cb916
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/base_icon_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed-on.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed-on.png
new file mode 100644
index 000000000..d081e9b3b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed-on.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed.png
new file mode 100644
index 000000000..53e2c51f5
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed@2x.png
new file mode 100644
index 000000000..06cdefa5f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled.png
new file mode 100644
index 000000000..5106a1438
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled@2x.png
new file mode 100644
index 000000000..180bae9e6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus.png
new file mode 100644
index 000000000..c227f9f71
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus@2x.png
new file mode 100644
index 000000000..ad23d0d33
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed.png
new file mode 100644
index 000000000..90845a81f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed@2x.png
new file mode 100644
index 000000000..60aaeb7fb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_closed_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end.png
new file mode 100644
index 000000000..08b5559b2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end@2x.png
new file mode 100644
index 000000000..ae6dbe991
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled.png
new file mode 100644
index 000000000..027a8894a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled@2x.png
new file mode 100644
index 000000000..43c1b0c76
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus.png
new file mode 100644
index 000000000..fdb3160bb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus@2x.png
new file mode 100644
index 000000000..3ca890449
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed.png
new file mode 100644
index 000000000..1c2432dd4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed@2x.png
new file mode 100644
index 000000000..af0f8fa5a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_end_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line.png
new file mode 100644
index 000000000..a3a564e44
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line@2x.png
new file mode 100644
index 000000000..1dbf71fc7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled.png
new file mode 100644
index 000000000..ecc7e6d93
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled@2x.png
new file mode 100644
index 000000000..adc6446c9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus.png
new file mode 100644
index 000000000..0037f175a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus@2x.png
new file mode 100644
index 000000000..cb257a914
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed.png
new file mode 100644
index 000000000..2d0856527
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed@2x.png
new file mode 100644
index 000000000..803708fb4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_line_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more.png
new file mode 100644
index 000000000..31b6cee87
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more@2x.png
new file mode 100644
index 000000000..f1f7a67f1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled.png
new file mode 100644
index 000000000..d4b604905
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled@2x.png
new file mode 100644
index 000000000..3ef752108
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus.png
new file mode 100644
index 000000000..943c13d0b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus@2x.png
new file mode 100644
index 000000000..9f53ef1fa
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed.png
new file mode 100644
index 000000000..9037ed3b3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed@2x.png
new file mode 100644
index 000000000..675d52c76
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_more_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open-on.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open-on.png
new file mode 100644
index 000000000..ec372b27d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open-on.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open.png
new file mode 100644
index 000000000..0861d0bc7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open@2x.png
new file mode 100644
index 000000000..8850f7367
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled.png
new file mode 100644
index 000000000..b6c80243b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled@2x.png
new file mode 100644
index 000000000..15ce9f265
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus.png
new file mode 100644
index 000000000..eadb0962a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus@2x.png
new file mode 100644
index 000000000..7dfcbbe8a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed.png
new file mode 100644
index 000000000..2b22e8d08
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed@2x.png
new file mode 100644
index 000000000..269a0cbee
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/branch_open_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked.png
new file mode 100644
index 000000000..e7ed08081
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked@2x.png
new file mode 100644
index 000000000..35f2ade58
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled.png
new file mode 100644
index 000000000..512b0a3e4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled@2x.png
new file mode 100644
index 000000000..557383ec8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus.png
new file mode 100644
index 000000000..0b90412f2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus@2x.png
new file mode 100644
index 000000000..7aee03cbb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed.png
new file mode 100644
index 000000000..3d4c869b7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed@2x.png
new file mode 100644
index 000000000..bfbc14b94
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_checked_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate.png
new file mode 100644
index 000000000..c21ab99bf
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate@2x.png
new file mode 100644
index 000000000..2fc29cee6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled.png
new file mode 100644
index 000000000..1d3c21492
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled@2x.png
new file mode 100644
index 000000000..bb8e7a747
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus.png
new file mode 100644
index 000000000..13ca4a7a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus@2x.png
new file mode 100644
index 000000000..3907eb8d4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed.png
new file mode 100644
index 000000000..12f83ceba
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed@2x.png
new file mode 100644
index 000000000..5ff4f6629
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_indeterminate_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked.png
new file mode 100644
index 000000000..e2da452fa
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked@2x.png
new file mode 100644
index 000000000..3732d5406
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled.png
new file mode 100644
index 000000000..c2e30c690
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled@2x.png
new file mode 100644
index 000000000..c4bddb6eb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus.png
new file mode 100644
index 000000000..c57f04d9f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus@2x.png
new file mode 100644
index 000000000..1776ad048
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed.png
new file mode 100644
index 000000000..be41236e1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed@2x.png
new file mode 100644
index 000000000..b1ad7c72f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/checkbox_unchecked_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-hover.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-hover.png
new file mode 100644
index 000000000..657943a66
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-hover.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-pressed.png
new file mode 100644
index 000000000..937d00598
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close-pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/close.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close.png
new file mode 100644
index 000000000..bc0f57610
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/close.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow.png
new file mode 100644
index 000000000..e271f7f90
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow_disabled.png
new file mode 100644
index 000000000..5805d9842
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/down_arrow_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow.png
new file mode 100644
index 000000000..f808d2d72
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow_disabled.png
new file mode 100644
index 000000000..f5b9af8a3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/left_arrow_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal.png
new file mode 100644
index 000000000..11bc5c003
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal@2x.png
new file mode 100644
index 000000000..c229ac963
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled.png
new file mode 100644
index 000000000..204df8058
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled@2x.png
new file mode 100644
index 000000000..a4713c565
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus.png
new file mode 100644
index 000000000..ecda0c10b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus@2x.png
new file mode 100644
index 000000000..84397efdb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed.png
new file mode 100644
index 000000000..fd5d864ca
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed@2x.png
new file mode 100644
index 000000000..140552e4f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_horizontal_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical.png
new file mode 100644
index 000000000..a3a564e44
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical@2x.png
new file mode 100644
index 000000000..1dbf71fc7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled.png
new file mode 100644
index 000000000..ecc7e6d93
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled@2x.png
new file mode 100644
index 000000000..adc6446c9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus.png
new file mode 100644
index 000000000..0037f175a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus@2x.png
new file mode 100644
index 000000000..cb257a914
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed.png
new file mode 100644
index 000000000..2d0856527
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed@2x.png
new file mode 100644
index 000000000..803708fb4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/line_vertical_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked.png
new file mode 100644
index 000000000..6f1fd6ca6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked@2x.png
new file mode 100644
index 000000000..228ffdbf2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled.png
new file mode 100644
index 000000000..27788530d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled@2x.png
new file mode 100644
index 000000000..930bfaf70
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus.png
new file mode 100644
index 000000000..ca8e8bc9a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus@2x.png
new file mode 100644
index 000000000..aa0f1152b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed.png
new file mode 100644
index 000000000..6e391a0ff
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed@2x.png
new file mode 100644
index 000000000..0512731ae
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_checked_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked.png
new file mode 100644
index 000000000..763306bdc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked@2x.png
new file mode 100644
index 000000000..28b6a0784
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled.png
new file mode 100644
index 000000000..fc0b12f78
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled@2x.png
new file mode 100644
index 000000000..d31f2b4b9
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus.png
new file mode 100644
index 000000000..9c87b01e4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus@2x.png
new file mode 100644
index 000000000..4b4c7321d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed.png
new file mode 100644
index 000000000..709e31633
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed@2x.png
new file mode 100644
index 000000000..b014de5f0
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/radio_unchecked_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow.png
new file mode 100644
index 000000000..9b0a4e6a7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow_disabled.png
new file mode 100644
index 000000000..5c0bee402
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/right_arrow_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/sizegrip.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/sizegrip.png
new file mode 100644
index 000000000..350583aaa
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/sizegrip.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-end.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-end.png
new file mode 100644
index 000000000..cb5d3b51f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-end.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-more.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-more.png
new file mode 100644
index 000000000..62711409d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-branch-more.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-vline.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-vline.png
new file mode 100644
index 000000000..87536cce1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/stylesheet-vline.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal.png
new file mode 100644
index 000000000..012ea2dfb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal@2x.png
new file mode 100644
index 000000000..520c34f98
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled.png
new file mode 100644
index 000000000..1f91df98f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled@2x.png
new file mode 100644
index 000000000..738008f92
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus.png
new file mode 100644
index 000000000..999b3c7d8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus@2x.png
new file mode 100644
index 000000000..f8e40b7d1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed.png
new file mode 100644
index 000000000..c31b69deb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed@2x.png
new file mode 100644
index 000000000..2f4cb41c7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_horizontal_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical.png
new file mode 100644
index 000000000..16473bfd8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical@2x.png
new file mode 100644
index 000000000..90a5caee3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled.png
new file mode 100644
index 000000000..2d240edb5
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled@2x.png
new file mode 100644
index 000000000..fd1df30f1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus.png
new file mode 100644
index 000000000..58cda1f80
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus@2x.png
new file mode 100644
index 000000000..9222b4fd8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed.png
new file mode 100644
index 000000000..e7d641926
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed@2x.png
new file mode 100644
index 000000000..9c438faf4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_move_vertical_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal.png
new file mode 100644
index 000000000..3c0acbdcc
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal@2x.png
new file mode 100644
index 000000000..fb4e24c88
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled.png
new file mode 100644
index 000000000..32f7e8ca6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled@2x.png
new file mode 100644
index 000000000..f7bec188b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus.png
new file mode 100644
index 000000000..91c19d65c
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus@2x.png
new file mode 100644
index 000000000..c4829918d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed.png
new file mode 100644
index 000000000..7a7f91737
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed@2x.png
new file mode 100644
index 000000000..d65773b48
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_horizontal_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical.png
new file mode 100644
index 000000000..4dde3f37f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical@2x.png
new file mode 100644
index 000000000..fe97c0de3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled.png
new file mode 100644
index 000000000..7426ae2de
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled@2x.png
new file mode 100644
index 000000000..7acc6d33e
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus.png
new file mode 100644
index 000000000..6e3c12143
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus@2x.png
new file mode 100644
index 000000000..cac3a56c2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed.png
new file mode 100644
index 000000000..b777784b8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed@2x.png
new file mode 100644
index 000000000..7ed878fd3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/toolbar_separator_vertical_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent.png
new file mode 100644
index 000000000..8b241c4a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent@2x.png
new file mode 100644
index 000000000..2c3df7a5e
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled.png
new file mode 100644
index 000000000..8b241c4a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled@2x.png
new file mode 100644
index 000000000..2c3df7a5e
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus.png
new file mode 100644
index 000000000..8b241c4a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus@2x.png
new file mode 100644
index 000000000..2c3df7a5e
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed.png
new file mode 100644
index 000000000..8b241c4a4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed@2x.png
new file mode 100644
index 000000000..2c3df7a5e
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/transparent_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/undock.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/undock.png
new file mode 100644
index 000000000..88691d779
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/undock.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow.png
new file mode 100644
index 000000000..abcc72452
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow_disabled.png
new file mode 100644
index 000000000..b9c8e3b53
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/up_arrow_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close.png
new file mode 100644
index 000000000..6f55c3ae7
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close@2x.png
new file mode 100644
index 000000000..ff644f2e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled.png
new file mode 100644
index 000000000..22694e31d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled@2x.png
new file mode 100644
index 000000000..ebc97db70
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus.png
new file mode 100644
index 000000000..f017eda31
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus@2x.png
new file mode 100644
index 000000000..5a354d796
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed.png
new file mode 100644
index 000000000..04b922dd0
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed@2x.png
new file mode 100644
index 000000000..58c0bf592
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_close_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip.png
new file mode 100644
index 000000000..0528049bb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip@2x.png
new file mode 100644
index 000000000..1ca1b073c
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled.png
new file mode 100644
index 000000000..15f55c056
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled@2x.png
new file mode 100644
index 000000000..33a4588e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus.png
new file mode 100644
index 000000000..06e76c31f
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus@2x.png
new file mode 100644
index 000000000..58c2d06e4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed.png
new file mode 100644
index 000000000..b3a566cdb
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed@2x.png
new file mode 100644
index 000000000..e9da94049
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_grip_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize.png
new file mode 100644
index 000000000..f60981615
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize@2x.png
new file mode 100644
index 000000000..30f728f02
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled.png
new file mode 100644
index 000000000..29db1c9b1
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled@2x.png
new file mode 100644
index 000000000..1572ca2fe
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus.png
new file mode 100644
index 000000000..cb592f598
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus@2x.png
new file mode 100644
index 000000000..6f6465169
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed.png
new file mode 100644
index 000000000..6962440ac
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed@2x.png
new file mode 100644
index 000000000..cb028272b
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_minimize_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock.png
new file mode 100644
index 000000000..616da991a
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock@2x.png
new file mode 100644
index 000000000..511036bf2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled.png
new file mode 100644
index 000000000..a2b3d25b2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled@2x.png
new file mode 100644
index 000000000..638ec8104
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_disabled@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus.png
new file mode 100644
index 000000000..ae6dc4a60
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus@2x.png
new file mode 100644
index 000000000..d06dd1eac
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_focus@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed.png
new file mode 100644
index 000000000..e9142ded2
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed@2x.png b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed@2x.png
new file mode 100644
index 000000000..a597420f3
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/rc/window_undock_pressed@2x.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
new file mode 100644
index 000000000..579e73ece
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
@@ -0,0 +1,226 @@
+<RCC>
+ <qresource prefix="icons/qdarkstyle_midnight_blue">
+ <file alias="index.theme">icons/index.theme</file>
+ <file alias="16x16/lock.png">icons/16x16/lock.png</file>
+ <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
+ <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
+ <file alias="48x48/chip.png">icons/48x48/chip.png</file>
+ <file alias="48x48/folder.png">icons/48x48/folder.png</file>
+ <file alias="48x48/plus.png">icons/48x48/plus.png</file>
+ <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
+ <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
+ </qresource>
+ <qresource prefix="qss_icons">
+ <file>rc/arrow_down.png</file>
+ <file>rc/arrow_down@2x.png</file>
+ <file>rc/arrow_down_disabled.png</file>
+ <file>rc/arrow_down_disabled@2x.png</file>
+ <file>rc/arrow_down_focus.png</file>
+ <file>rc/arrow_down_focus@2x.png</file>
+ <file>rc/arrow_down_pressed.png</file>
+ <file>rc/arrow_down_pressed@2x.png</file>
+ <file>rc/arrow_left.png</file>
+ <file>rc/arrow_left@2x.png</file>
+ <file>rc/arrow_left_disabled.png</file>
+ <file>rc/arrow_left_disabled@2x.png</file>
+ <file>rc/arrow_left_focus.png</file>
+ <file>rc/arrow_left_focus@2x.png</file>
+ <file>rc/arrow_left_pressed.png</file>
+ <file>rc/arrow_left_pressed@2x.png</file>
+ <file>rc/arrow_right.png</file>
+ <file>rc/arrow_right@2x.png</file>
+ <file>rc/arrow_right_disabled.png</file>
+ <file>rc/arrow_right_disabled@2x.png</file>
+ <file>rc/arrow_right_focus.png</file>
+ <file>rc/arrow_right_focus@2x.png</file>
+ <file>rc/arrow_right_pressed.png</file>
+ <file>rc/arrow_right_pressed@2x.png</file>
+ <file>rc/arrow_up.png</file>
+ <file>rc/arrow_up@2x.png</file>
+ <file>rc/arrow_up_disabled.png</file>
+ <file>rc/arrow_up_disabled@2x.png</file>
+ <file>rc/arrow_up_focus.png</file>
+ <file>rc/arrow_up_focus@2x.png</file>
+ <file>rc/arrow_up_pressed.png</file>
+ <file>rc/arrow_up_pressed@2x.png</file>
+ <file>rc/base_icon.png</file>
+ <file>rc/base_icon@2x.png</file>
+ <file>rc/base_icon_disabled.png</file>
+ <file>rc/base_icon_disabled@2x.png</file>
+ <file>rc/base_icon_focus.png</file>
+ <file>rc/base_icon_focus@2x.png</file>
+ <file>rc/base_icon_pressed.png</file>
+ <file>rc/base_icon_pressed@2x.png</file>
+ <file>rc/branch_closed.png</file>
+ <file>rc/branch_closed@2x.png</file>
+ <file>rc/branch_closed_disabled.png</file>
+ <file>rc/branch_closed_disabled@2x.png</file>
+ <file>rc/branch_closed_focus.png</file>
+ <file>rc/branch_closed_focus@2x.png</file>
+ <file>rc/branch_closed_pressed.png</file>
+ <file>rc/branch_closed_pressed@2x.png</file>
+ <file>rc/branch_end.png</file>
+ <file>rc/branch_end@2x.png</file>
+ <file>rc/branch_end_disabled.png</file>
+ <file>rc/branch_end_disabled@2x.png</file>
+ <file>rc/branch_end_focus.png</file>
+ <file>rc/branch_end_focus@2x.png</file>
+ <file>rc/branch_end_pressed.png</file>
+ <file>rc/branch_end_pressed@2x.png</file>
+ <file>rc/branch_line.png</file>
+ <file>rc/branch_line@2x.png</file>
+ <file>rc/branch_line_disabled.png</file>
+ <file>rc/branch_line_disabled@2x.png</file>
+ <file>rc/branch_line_focus.png</file>
+ <file>rc/branch_line_focus@2x.png</file>
+ <file>rc/branch_line_pressed.png</file>
+ <file>rc/branch_line_pressed@2x.png</file>
+ <file>rc/branch_more.png</file>
+ <file>rc/branch_more@2x.png</file>
+ <file>rc/branch_more_disabled.png</file>
+ <file>rc/branch_more_disabled@2x.png</file>
+ <file>rc/branch_more_focus.png</file>
+ <file>rc/branch_more_focus@2x.png</file>
+ <file>rc/branch_more_pressed.png</file>
+ <file>rc/branch_more_pressed@2x.png</file>
+ <file>rc/branch_open.png</file>
+ <file>rc/branch_open@2x.png</file>
+ <file>rc/branch_open_disabled.png</file>
+ <file>rc/branch_open_disabled@2x.png</file>
+ <file>rc/branch_open_focus.png</file>
+ <file>rc/branch_open_focus@2x.png</file>
+ <file>rc/branch_open_pressed.png</file>
+ <file>rc/branch_open_pressed@2x.png</file>
+ <file>rc/checkbox_checked.png</file>
+ <file>rc/checkbox_checked@2x.png</file>
+ <file>rc/checkbox_checked_disabled.png</file>
+ <file>rc/checkbox_checked_disabled@2x.png</file>
+ <file>rc/checkbox_checked_focus.png</file>
+ <file>rc/checkbox_checked_focus@2x.png</file>
+ <file>rc/checkbox_checked_pressed.png</file>
+ <file>rc/checkbox_checked_pressed@2x.png</file>
+ <file>rc/checkbox_indeterminate.png</file>
+ <file>rc/checkbox_indeterminate@2x.png</file>
+ <file>rc/checkbox_indeterminate_disabled.png</file>
+ <file>rc/checkbox_indeterminate_disabled@2x.png</file>
+ <file>rc/checkbox_indeterminate_focus.png</file>
+ <file>rc/checkbox_indeterminate_focus@2x.png</file>
+ <file>rc/checkbox_indeterminate_pressed.png</file>
+ <file>rc/checkbox_indeterminate_pressed@2x.png</file>
+ <file>rc/checkbox_unchecked.png</file>
+ <file>rc/checkbox_unchecked@2x.png</file>
+ <file>rc/checkbox_unchecked_disabled.png</file>
+ <file>rc/checkbox_unchecked_disabled@2x.png</file>
+ <file>rc/checkbox_unchecked_focus.png</file>
+ <file>rc/checkbox_unchecked_focus@2x.png</file>
+ <file>rc/checkbox_unchecked_pressed.png</file>
+ <file>rc/checkbox_unchecked_pressed@2x.png</file>
+ <file>rc/line_horizontal.png</file>
+ <file>rc/line_horizontal@2x.png</file>
+ <file>rc/line_horizontal_disabled.png</file>
+ <file>rc/line_horizontal_disabled@2x.png</file>
+ <file>rc/line_horizontal_focus.png</file>
+ <file>rc/line_horizontal_focus@2x.png</file>
+ <file>rc/line_horizontal_pressed.png</file>
+ <file>rc/line_horizontal_pressed@2x.png</file>
+ <file>rc/line_vertical.png</file>
+ <file>rc/line_vertical@2x.png</file>
+ <file>rc/line_vertical_disabled.png</file>
+ <file>rc/line_vertical_disabled@2x.png</file>
+ <file>rc/line_vertical_focus.png</file>
+ <file>rc/line_vertical_focus@2x.png</file>
+ <file>rc/line_vertical_pressed.png</file>
+ <file>rc/line_vertical_pressed@2x.png</file>
+ <file>rc/radio_checked.png</file>
+ <file>rc/radio_checked@2x.png</file>
+ <file>rc/radio_checked_disabled.png</file>
+ <file>rc/radio_checked_disabled@2x.png</file>
+ <file>rc/radio_checked_focus.png</file>
+ <file>rc/radio_checked_focus@2x.png</file>
+ <file>rc/radio_checked_pressed.png</file>
+ <file>rc/radio_checked_pressed@2x.png</file>
+ <file>rc/radio_unchecked.png</file>
+ <file>rc/radio_unchecked@2x.png</file>
+ <file>rc/radio_unchecked_disabled.png</file>
+ <file>rc/radio_unchecked_disabled@2x.png</file>
+ <file>rc/radio_unchecked_focus.png</file>
+ <file>rc/radio_unchecked_focus@2x.png</file>
+ <file>rc/radio_unchecked_pressed.png</file>
+ <file>rc/radio_unchecked_pressed@2x.png</file>
+ <file>rc/toolbar_move_horizontal.png</file>
+ <file>rc/toolbar_move_horizontal@2x.png</file>
+ <file>rc/toolbar_move_horizontal_disabled.png</file>
+ <file>rc/toolbar_move_horizontal_disabled@2x.png</file>
+ <file>rc/toolbar_move_horizontal_focus.png</file>
+ <file>rc/toolbar_move_horizontal_focus@2x.png</file>
+ <file>rc/toolbar_move_horizontal_pressed.png</file>
+ <file>rc/toolbar_move_horizontal_pressed@2x.png</file>
+ <file>rc/toolbar_move_vertical.png</file>
+ <file>rc/toolbar_move_vertical@2x.png</file>
+ <file>rc/toolbar_move_vertical_disabled.png</file>
+ <file>rc/toolbar_move_vertical_disabled@2x.png</file>
+ <file>rc/toolbar_move_vertical_focus.png</file>
+ <file>rc/toolbar_move_vertical_focus@2x.png</file>
+ <file>rc/toolbar_move_vertical_pressed.png</file>
+ <file>rc/toolbar_move_vertical_pressed@2x.png</file>
+ <file>rc/toolbar_separator_horizontal.png</file>
+ <file>rc/toolbar_separator_horizontal@2x.png</file>
+ <file>rc/toolbar_separator_horizontal_disabled.png</file>
+ <file>rc/toolbar_separator_horizontal_disabled@2x.png</file>
+ <file>rc/toolbar_separator_horizontal_focus.png</file>
+ <file>rc/toolbar_separator_horizontal_focus@2x.png</file>
+ <file>rc/toolbar_separator_horizontal_pressed.png</file>
+ <file>rc/toolbar_separator_horizontal_pressed@2x.png</file>
+ <file>rc/toolbar_separator_vertical.png</file>
+ <file>rc/toolbar_separator_vertical@2x.png</file>
+ <file>rc/toolbar_separator_vertical_disabled.png</file>
+ <file>rc/toolbar_separator_vertical_disabled@2x.png</file>
+ <file>rc/toolbar_separator_vertical_focus.png</file>
+ <file>rc/toolbar_separator_vertical_focus@2x.png</file>
+ <file>rc/toolbar_separator_vertical_pressed.png</file>
+ <file>rc/toolbar_separator_vertical_pressed@2x.png</file>
+ <file>rc/transparent.png</file>
+ <file>rc/transparent@2x.png</file>
+ <file>rc/transparent_disabled.png</file>
+ <file>rc/transparent_disabled@2x.png</file>
+ <file>rc/transparent_focus.png</file>
+ <file>rc/transparent_focus@2x.png</file>
+ <file>rc/transparent_pressed.png</file>
+ <file>rc/transparent_pressed@2x.png</file>
+ <file>rc/window_close.png</file>
+ <file>rc/window_close@2x.png</file>
+ <file>rc/window_close_disabled.png</file>
+ <file>rc/window_close_disabled@2x.png</file>
+ <file>rc/window_close_focus.png</file>
+ <file>rc/window_close_focus@2x.png</file>
+ <file>rc/window_close_pressed.png</file>
+ <file>rc/window_close_pressed@2x.png</file>
+ <file>rc/window_grip.png</file>
+ <file>rc/window_grip@2x.png</file>
+ <file>rc/window_grip_disabled.png</file>
+ <file>rc/window_grip_disabled@2x.png</file>
+ <file>rc/window_grip_focus.png</file>
+ <file>rc/window_grip_focus@2x.png</file>
+ <file>rc/window_grip_pressed.png</file>
+ <file>rc/window_grip_pressed@2x.png</file>
+ <file>rc/window_minimize.png</file>
+ <file>rc/window_minimize@2x.png</file>
+ <file>rc/window_minimize_disabled.png</file>
+ <file>rc/window_minimize_disabled@2x.png</file>
+ <file>rc/window_minimize_focus.png</file>
+ <file>rc/window_minimize_focus@2x.png</file>
+ <file>rc/window_minimize_pressed.png</file>
+ <file>rc/window_minimize_pressed@2x.png</file>
+ <file>rc/window_undock.png</file>
+ <file>rc/window_undock@2x.png</file>
+ <file>rc/window_undock_disabled.png</file>
+ <file>rc/window_undock_disabled@2x.png</file>
+ <file>rc/window_undock_focus.png</file>
+ <file>rc/window_undock_focus@2x.png</file>
+ <file>rc/window_undock_pressed.png</file>
+ <file>rc/window_undock_pressed@2x.png</file>
+ </qresource>
+ <qresource prefix="qdarkstyle_midnight_blue">
+ <file>style.qss</file>
+ </qresource>
+</RCC>
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
new file mode 100644
index 000000000..70e540b06
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
@@ -0,0 +1,2489 @@
+/* ---------------------------------------------------------------------------
+
+ Created by the qtsass compiler v0.1.1
+
+ The definitions are in the "qdarkstyle.qss._styles.scss" module
+
+ WARNING! All changes made in this file will be lost!
+
+--------------------------------------------------------------------------- */
+/* QDarkStyleSheet -----------------------------------------------------------
+
+This is the main style sheet, the palette has nine colors.
+
+It is based on three selecting colors, three greyish (background) colors
+plus three whitish (foreground) colors. Each set of widgets of the same
+type have a header like this:
+
+ ------------------
+ GroupName --------
+ ------------------
+
+And each widget is separated with a header like this:
+
+ QWidgetName ------
+
+This makes more easy to find and change some css field. The basic
+configuration is described bellow.
+
+ BACKGROUND -----------
+
+ Light (unpressed)
+ Normal (border, disabled, pressed, checked, toolbars, menus)
+ Dark (background)
+
+ FOREGROUND -----------
+
+ Light (texts/labels)
+ Normal (not used yet)
+ Dark (disabled texts)
+
+ SELECTION ------------
+
+ Light (selection/hover/active)
+ Normal (selected)
+ Dark (selected disabled)
+
+If a stranger configuration is required because of a bugfix or anything
+else, keep the comment on the line above so nobody changes it, including the
+issue number.
+
+*/
+/*
+
+See Qt documentation:
+
+ - https://doc.qt.io/qt-5/stylesheet.html
+ - https://doc.qt.io/qt-5/stylesheet-reference.html
+ - https://doc.qt.io/qt-5/stylesheet-examples.html
+
+--------------------------------------------------------------------------- */
+/* QWidget ----------------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QWidget {
+ background-color: #19232D;
+ border: 0px solid #32414B;
+ padding: 0px;
+ color: #F0F0F0;
+ selection-background-color: #1464A0;
+ selection-color: #F0F0F0;
+}
+
+QWidget:disabled {
+ background-color: #19232D;
+ color: #787878;
+ selection-background-color: #14506E;
+ selection-color: #787878;
+}
+
+QWidget::item:selected {
+ background-color: #1464A0;
+}
+
+QWidget::item:hover {
+ background-color: #148CD2;
+ color: #32414B;
+}
+
+/* QMainWindow ------------------------------------------------------------
+
+This adjusts the splitter in the dock widget, not qsplitter
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmainwindow
+
+--------------------------------------------------------------------------- */
+QMainWindow::separator {
+ background-color: #32414B;
+ border: 0px solid #19232D;
+ spacing: 0px;
+ padding: 2px;
+}
+
+QMainWindow::separator:hover {
+ background-color: #505F69;
+ border: 0px solid #148CD2;
+}
+
+QMainWindow::separator:horizontal {
+ width: 5px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ image: url(":/qss_icons/rc/toolbar_separator_vertical.png");
+}
+
+QMainWindow::separator:vertical {
+ height: 5px;
+ margin-left: 2px;
+ margin-right: 2px;
+ image: url(":/qss_icons/rc/toolbar_separator_horizontal.png");
+}
+
+/* QToolTip ---------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtooltip
+
+--------------------------------------------------------------------------- */
+QToolTip {
+ background-color: #148CD2;
+ border: 1px solid #19232D;
+ color: #19232D;
+ /* Remove padding, for fix combo box tooltip */
+ padding: 0px;
+ /* Remove opacity, fix #174 - may need to use RGBA */
+}
+
+/* QStatusBar -------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qstatusbar
+
+--------------------------------------------------------------------------- */
+QStatusBar {
+ background: #32414B;
+ /* Fixes #205, white vertical borders separating items */
+}
+
+QStatusBar::item {
+ border: none;
+}
+
+QStatusBar QToolTip {
+ background-color: #148CD2;
+ border: 1px solid #19232D;
+ color: #19232D;
+ /* Remove padding, for fix combo box tooltip */
+ padding: 0px;
+ /* Reducing transparency to read better */
+ opacity: 230;
+}
+
+QStatusBar QLabel {
+ /* Fixes Spyder #9120, #9121 */
+ background: transparent;
+ padding: 0px;
+}
+
+/* QCheckBox --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcheckbox
+
+--------------------------------------------------------------------------- */
+QCheckBox {
+ background-color: #19232D;
+ color: #F0F0F0;
+ spacing: 4px;
+ outline: none;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+QCheckBox:focus {
+ border: none;
+}
+
+QCheckBox QWidget:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+QCheckBox::indicator {
+ margin-left: 4px;
+ height: 16px;
+ width: 16px;
+}
+
+QCheckBox::indicator:unchecked {
+ image: url(":/qss_icons/rc/checkbox_unchecked.png");
+}
+
+QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus, QCheckBox::indicator:unchecked:pressed {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_unchecked_focus.png");
+}
+
+QCheckBox::indicator:unchecked:disabled {
+ image: url(":/qss_icons/rc/checkbox_unchecked_disabled.png");
+}
+
+QCheckBox::indicator:checked {
+ image: url(":/qss_icons/rc/checkbox_checked.png");
+}
+
+QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus, QCheckBox::indicator:checked:pressed {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_checked_focus.png");
+}
+
+QCheckBox::indicator:checked:disabled {
+ image: url(":/qss_icons/rc/checkbox_checked_disabled.png");
+}
+
+QCheckBox::indicator:indeterminate {
+ image: url(":/qss_icons/rc/checkbox_indeterminate.png");
+}
+
+QCheckBox::indicator:indeterminate:disabled {
+ image: url(":/qss_icons/rc/checkbox_indeterminate_disabled.png");
+}
+
+QCheckBox::indicator:indeterminate:focus, QCheckBox::indicator:indeterminate:hover, QCheckBox::indicator:indeterminate:pressed {
+ image: url(":/qss_icons/rc/checkbox_indeterminate_focus.png");
+}
+
+/* QGroupBox --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox
+
+--------------------------------------------------------------------------- */
+QGroupBox {
+ font-weight: bold;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ margin-top: 12px;
+ padding: 2px;
+}
+
+QGroupBox::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 3px;
+ padding-right: 5px;
+ padding-top: 2px;
+}
+
+QGroupBox::indicator {
+ margin-left: 2px;
+ height: 16px;
+ width: 16px;
+}
+
+QGroupBox::indicator:unchecked {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_unchecked.png");
+}
+
+QGroupBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:focus, QGroupBox::indicator:unchecked:pressed {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_unchecked_focus.png");
+}
+
+QGroupBox::indicator:unchecked:disabled {
+ image: url(":/qss_icons/rc/checkbox_unchecked_disabled.png");
+}
+
+QGroupBox::indicator:checked {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_checked.png");
+}
+
+QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:focus, QGroupBox::indicator:checked:pressed {
+ border: none;
+ image: url(":/qss_icons/rc/checkbox_checked_focus.png");
+}
+
+QGroupBox::indicator:checked:disabled {
+ image: url(":/qss_icons/rc/checkbox_checked_disabled.png");
+}
+
+/* QRadioButton -----------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qradiobutton
+
+--------------------------------------------------------------------------- */
+QRadioButton {
+ background-color: #19232D;
+ color: #F0F0F0;
+ spacing: 4px;
+ padding: 0px;
+ border: none;
+ outline: none;
+}
+
+QGroupBox QRadioButton {
+ padding-left: 0px;
+ padding-right: 7px;
+}
+
+QRadioButton:focus {
+ border: none;
+}
+
+QRadioButton:disabled {
+ background-color: #19232D;
+ color: #787878;
+ border: none;
+ outline: none;
+}
+
+QRadioButton QWidget {
+ background-color: #19232D;
+ color: #F0F0F0;
+ spacing: 0px;
+ padding: 0px;
+ outline: none;
+ border: none;
+}
+
+QRadioButton::indicator {
+ border: none;
+ outline: none;
+ height: 16px;
+ width: 16px;
+}
+
+QRadioButton::indicator:unchecked {
+ image: url(":/qss_icons/rc/radio_unchecked.png");
+}
+
+QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:focus, QRadioButton::indicator:unchecked:pressed {
+ border: none;
+ outline: none;
+ image: url(":/qss_icons/rc/radio_unchecked_focus.png");
+}
+
+QRadioButton::indicator:unchecked:disabled {
+ image: url(":/qss_icons/rc/radio_unchecked_disabled.png");
+}
+
+QRadioButton::indicator:checked {
+ border: none;
+ outline: none;
+ image: url(":/qss_icons/rc/radio_checked.png");
+}
+
+QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:focus, QRadioButton::indicator:checked:pressed {
+ border: none;
+ outline: none;
+ image: url(":/qss_icons/rc/radio_checked_focus.png");
+}
+
+QRadioButton::indicator:checked:disabled {
+ outline: none;
+ image: url(":/qss_icons/rc/radio_checked_disabled.png");
+}
+
+/* QMenuBar ---------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenubar
+
+--------------------------------------------------------------------------- */
+QMenuBar {
+ background-color: #32414B;
+ color: #F0F0F0;
+}
+
+QMenuBar::item {
+ background: transparent;
+}
+
+QMenuBar::item:selected {
+ background: transparent;
+ border: 0px solid #32414B;
+}
+
+QMenuBar::item:pressed {
+ border: 0px solid #32414B;
+ background-color: #148CD2;
+ color: #F0F0F0;
+ margin-bottom: 0px;
+ padding-bottom: 0px;
+}
+
+
+/* QMenu ------------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu
+
+--------------------------------------------------------------------------- */
+QMenu {
+ border: 0px solid #32414B;
+ color: #F0F0F0;
+ margin: 0px;
+}
+
+QMenu::separator {
+ height: 1px;
+ background-color: #505F69;
+ color: #F0F0F0;
+}
+
+QMenu::icon {
+ margin: 0px;
+ padding-left: 8px;
+}
+
+QMenu::item {
+ background-color: #32414B;
+ padding: 4px 24px 4px 24px;
+ /* Reserve space for selection border */
+ border: 1px transparent #32414B;
+}
+
+QMenu::item:selected {
+ color: #F0F0F0;
+}
+
+QMenu::indicator {
+ width: 12px;
+ height: 12px;
+ padding-left: 6px;
+ /* non-exclusive indicator = check box style indicator (see QActionGroup::setExclusive) */
+ /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
+}
+
+QMenu::indicator:non-exclusive:unchecked {
+ image: url(":/qss_icons/rc/checkbox_unchecked.png");
+}
+
+QMenu::indicator:non-exclusive:unchecked:selected {
+ image: url(":/qss_icons/rc/checkbox_unchecked_disabled.png");
+}
+
+QMenu::indicator:non-exclusive:checked {
+ image: url(":/qss_icons/rc/checkbox_checked.png");
+}
+
+QMenu::indicator:non-exclusive:checked:selected {
+ image: url(":/qss_icons/rc/checkbox_checked_disabled.png");
+}
+
+QMenu::indicator:exclusive:unchecked {
+ image: url(":/qss_icons/rc/radio_unchecked.png");
+}
+
+QMenu::indicator:exclusive:unchecked:selected {
+ image: url(":/qss_icons/rc/radio_unchecked_disabled.png");
+}
+
+QMenu::indicator:exclusive:checked {
+ image: url(":/qss_icons/rc/radio_checked.png");
+}
+
+QMenu::indicator:exclusive:checked:selected {
+ image: url(":/qss_icons/rc/radio_checked_disabled.png");
+}
+
+QMenu::right-arrow {
+ margin: 5px;
+ image: url(":/qss_icons/rc/arrow_right.png");
+ height: 12px;
+ width: 12px;
+}
+
+/* QAbstractItemView ------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox
+
+--------------------------------------------------------------------------- */
+QAbstractItemView {
+ alternate-background-color: #1f2933;
+ color: #F0F0F0;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+}
+
+QAbstractItemView QLineEdit {
+ padding: 2px;
+}
+
+/* QAbstractScrollArea ----------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qabstractscrollarea
+
+--------------------------------------------------------------------------- */
+QAbstractScrollArea {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ /* fix #159 */
+ min-height: 1.25em;
+ /* fix #159 */
+ color: #F0F0F0;
+}
+
+
+QAbstractScrollArea:disabled {
+ color: #787878;
+}
+
+/* QScrollArea ------------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QScrollArea QWidget QWidget:disabled {
+ background-color: #19232D;
+}
+
+/* QScrollBar -------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qscrollbar
+
+--------------------------------------------------------------------------- */
+QScrollBar:horizontal {
+ height: 16px;
+ margin: 2px 16px 2px 16px;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ background-color: #19232D;
+}
+
+QScrollBar:vertical {
+ background-color: #19232D;
+ width: 16px;
+ margin: 16px 2px 16px 2px;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+}
+
+QScrollBar::handle:horizontal {
+ background-color: #787878;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ min-width: 8px;
+}
+
+QScrollBar::handle:horizontal:hover {
+ background-color: #148CD2;
+ border: 1px solid #148CD2;
+ border-radius: 4px;
+ min-width: 8px;
+}
+
+QScrollBar::handle:horizontal:focus {
+ border: 1px solid #1464A0;
+}
+
+QScrollBar::handle:vertical {
+ background-color: #787878;
+ border: 1px solid #32414B;
+ min-height: 8px;
+ border-radius: 4px;
+}
+
+QScrollBar::handle:vertical:hover {
+ background-color: #148CD2;
+ border: 1px solid #148CD2;
+ border-radius: 4px;
+ min-height: 8px;
+}
+
+QScrollBar::handle:vertical:focus {
+ border: 1px solid #1464A0;
+}
+
+QScrollBar::add-line:horizontal {
+ margin: 0px 0px 0px 0px;
+ border-image: url(":/qss_icons/rc/arrow_right_disabled.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: right;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on {
+ border-image: url(":/qss_icons/rc/arrow_right.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: right;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:vertical {
+ margin: 3px 0px 3px 0px;
+ border-image: url(":/qss_icons/rc/arrow_down_disabled.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: bottom;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on {
+ border-image: url(":/qss_icons/rc/arrow_down.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: bottom;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:horizontal {
+ margin: 0px 3px 0px 3px;
+ border-image: url(":/qss_icons/rc/arrow_left_disabled.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: left;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on {
+ border-image: url(":/qss_icons/rc/arrow_left.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: left;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:vertical {
+ margin: 3px 0px 3px 0px;
+ border-image: url(":/qss_icons/rc/arrow_up_disabled.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: top;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on {
+ border-image: url(":/qss_icons/rc/arrow_up.png");
+ height: 12px;
+ width: 12px;
+ subcontrol-position: top;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {
+ background: none;
+}
+
+QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
+ background: none;
+}
+
+QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
+ background: none;
+}
+
+QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
+ background: none;
+}
+
+/* QTextEdit --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-specific-widgets
+
+--------------------------------------------------------------------------- */
+QTextEdit {
+ background-color: #19232D;
+ color: #F0F0F0;
+ border-radius: 4px;
+ border: 1px solid #32414B;
+}
+
+QTextEdit:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QTextEdit:focus {
+ border: 1px solid #1464A0;
+}
+
+QTextEdit:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+/* QPlainTextEdit ---------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QPlainTextEdit {
+ background-color: #19232D;
+ color: #F0F0F0;
+ border-radius: 4px;
+ border: 1px solid #32414B;
+}
+
+QPlainTextEdit:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QPlainTextEdit:focus {
+ border: 1px solid #1464A0;
+}
+
+QPlainTextEdit:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+/* QSizeGrip --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsizegrip
+
+--------------------------------------------------------------------------- */
+QSizeGrip {
+ background: transparent;
+ width: 12px;
+ height: 12px;
+ image: url(":/qss_icons/rc/window_grip.png");
+}
+
+/* QStackedWidget ---------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QStackedWidget {
+ padding: 2px;
+ border: 1px solid #32414B;
+ border: 1px solid #19232D;
+}
+
+/* QToolBar ---------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbar
+
+--------------------------------------------------------------------------- */
+QToolBar {
+ background-color: #32414B;
+ border-bottom: 1px solid #19232D;
+ padding: 2px;
+ font-weight: bold;
+ spacing: 2px;
+}
+
+QToolBar QToolButton {
+ background-color: #32414B;
+ border: 1px solid #32414B;
+}
+
+QToolBar QToolButton:hover {
+ border: 1px solid #148CD2;
+}
+
+QToolBar QToolButton:checked {
+ border: 1px solid #19232D;
+ background-color: #19232D;
+}
+
+QToolBar QToolButton:checked:hover {
+ border: 1px solid #148CD2;
+}
+
+QToolBar::handle:horizontal {
+ width: 16px;
+ image: url(":/qss_icons/rc/toolbar_move_horizontal.png");
+}
+
+QToolBar::handle:vertical {
+ height: 16px;
+ image: url(":/qss_icons/rc/toolbar_move_vertical.png");
+}
+
+QToolBar::separator:horizontal {
+ width: 16px;
+ image: url(":/qss_icons/rc/toolbar_separator_horizontal.png");
+}
+
+QToolBar::separator:vertical {
+ height: 16px;
+ image: url(":/qss_icons/rc/toolbar_separator_vertical.png");
+}
+
+QToolButton#qt_toolbar_ext_button {
+ background: #32414B;
+ border: 0px;
+ color: #F0F0F0;
+ image: url(":/qss_icons/rc/arrow_right.png");
+}
+
+/* QAbstractSpinBox -------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QAbstractSpinBox {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+ min-height: 19px;
+}
+
+QAbstractSpinBox:up-button {
+ background-color: #505F69;
+ subcontrol-origin: border;
+ subcontrol-position: top right;
+ border-left: 1px solid #32414B;
+ border-top: 1px solid #32414B;
+ border-right: 1px solid #32414B;
+ border-top-right-radius: 4px;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ margin: 0px;
+ width: 12px;
+ margin-bottom: 0px;
+}
+
+QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off {
+ image: url(":/qss_icons/rc/up_arrow.png");
+ height: 8px;
+ width: 8px;
+}
+
+QAbstractSpinBox::up-arrow:hover {
+ image: url(":/qss_icons/rc/arrow_up.png");
+}
+
+QAbstractSpinBox:down-button {
+ background-color: #505F69;
+ subcontrol-origin: border;
+ subcontrol-position: bottom right;
+ border-left: 1px solid #32414B;
+ border-right: 1px solid #32414B;
+ border-bottom: 1px solid #32414B;
+ border-top: 1px solid #32414B;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 4px;
+ margin: 0px;
+ width: 12px;
+ margin-top: 0px;
+}
+
+QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off {
+ image: url(":/qss_icons/rc/down_arrow.png");
+ height: 8px;
+ width: 8px;
+}
+
+QAbstractSpinBox::down-arrow:hover {
+ image: url(":/qss_icons/rc/arrow_down.png");
+}
+
+QAbstractSpinBox:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QAbstractSpinBox:focus {
+ border: 1px solid #1464A0;
+}
+
+QAbstractSpinBox:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+/* ------------------------------------------------------------------------ */
+/* DISPLAYS --------------------------------------------------------------- */
+/* ------------------------------------------------------------------------ */
+/* QLabel -----------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qframe
+
+--------------------------------------------------------------------------- */
+QLabel {
+ background: transparent;
+ border: 0px solid #32414B;
+ padding: 2px;
+ margin: 0px;
+ color: #F0F0F0;
+}
+
+QLabel:disabled {
+ border: 0px solid #32414B;
+ color: #787878;
+}
+
+/* QTextBrowser -----------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qabstractscrollarea
+
+--------------------------------------------------------------------------- */
+QTextBrowser {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+}
+
+QTextBrowser:disabled {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #787878;
+ border-radius: 4px;
+}
+
+QTextBrowser:hover, QTextBrowser:!hover, QTextBrowser:selected, QTextBrowser:pressed {
+ border: 1px solid #32414B;
+}
+
+/* QGraphicsView ----------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QGraphicsView {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+}
+
+QGraphicsView:disabled {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #787878;
+ border-radius: 4px;
+}
+
+QGraphicsView:hover, QGraphicsView:!hover, QGraphicsView:selected, QGraphicsView:pressed {
+ border: 1px solid #32414B;
+}
+
+/* QCalendarWidget --------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QCalendarWidget {
+ border: 1px solid #32414B;
+ border-radius: 4px;
+}
+
+QCalendarWidget:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+/* QLCDNumber -------------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QLCDNumber {
+ background-color: #19232D;
+ color: #F0F0F0;
+}
+
+QLCDNumber:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+/* QProgressBar -----------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qprogressbar
+
+--------------------------------------------------------------------------- */
+QProgressBar {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+ text-align: center;
+}
+
+QProgressBar:disabled {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #787878;
+ border-radius: 4px;
+ text-align: center;
+}
+
+QProgressBar::chunk {
+ background-color: #1464A0;
+ color: #19232D;
+ border-radius: 4px;
+}
+
+QProgressBar::chunk:disabled {
+ background-color: #14506E;
+ color: #787878;
+ border-radius: 4px;
+}
+
+/* ------------------------------------------------------------------------ */
+/* BUTTONS ---------------------------------------------------------------- */
+/* ------------------------------------------------------------------------ */
+/* QPushButton ------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qpushbutton
+
+--------------------------------------------------------------------------- */
+QPushButton {
+ background-color: #505F69;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+ padding: 3px 0px 3px 0px;
+ outline: none;
+ /* Issue #194 - Special case of QPushButton inside dialogs, for better UI */
+ min-width: 80px;
+ min-height: 13px;
+}
+
+QPushButton:disabled {
+ background-color: #32414B;
+ border: 1px solid #32414B;
+ color: #787878;
+ border-radius: 4px;
+ padding: 3px 0px 3px 0px;
+}
+
+QPushButton:checked {
+ background-color: #32414B;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ padding: 3px 0px 3px 0px;
+ outline: none;
+}
+
+QPushButton:checked:disabled {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #787878;
+ border-radius: 4px;
+ padding: 3px 0px 3px 0px;
+ outline: none;
+}
+
+QPushButton:checked:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+QPushButton::menu-indicator {
+ subcontrol-origin: padding;
+ subcontrol-position: bottom right;
+ bottom: 4px;
+}
+
+QPushButton:pressed {
+ background-color: #19232D;
+ border: 1px solid #19232D;
+}
+
+QPushButton:pressed:hover {
+ border: 1px solid #148CD2;
+}
+
+QPushButton:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QPushButton:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+QPushButton:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QPushButton:focus {
+ border: 1px solid #1464A0;
+}
+
+/* QToolButton ------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbutton
+
+--------------------------------------------------------------------------- */
+QToolButton {
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ margin: 0px;
+ padding: 2px;
+ /* The subcontrols below are used only in the DelayedPopup mode */
+ /* The subcontrols below are used only in the MenuButtonPopup mode */
+ /* The subcontrol below is used only in the InstantPopup or DelayedPopup mode */
+}
+
+QToolButton:checked {
+ background-color: transparent;
+ border: 1px solid #1464A0;
+}
+
+QToolButton:checked:disabled {
+ border: 1px solid #14506E;
+}
+
+QToolButton:pressed {
+ margin: 1px;
+ background-color: transparent;
+ border: 1px solid #1464A0;
+}
+
+QToolButton:disabled {
+ border: none;
+}
+
+QToolButton:hover {
+ border: 1px solid #148CD2;
+}
+
+QToolButton[popupMode="0"] {
+ /* Only for DelayedPopup */
+ padding-right: 2px;
+}
+
+QToolButton[popupMode="1"] {
+ /* Only for MenuButtonPopup */
+ padding-right: 20px;
+}
+
+QToolButton[popupMode="1"]::menu-button {
+ border: none;
+}
+
+QToolButton[popupMode="1"]::menu-button:hover {
+ border: none;
+ border-left: 1px solid #148CD2;
+ border-radius: 0;
+}
+
+QToolButton[popupMode="2"] {
+ /* Only for InstantPopup */
+ padding-right: 2px;
+}
+
+QToolButton::menu-button {
+ padding: 2px;
+ border-radius: 4px;
+ border: 1px solid #32414B;
+ width: 12px;
+ outline: none;
+}
+
+QToolButton::menu-button:hover {
+ border: 1px solid #148CD2;
+}
+
+QToolButton::menu-button:checked:hover {
+ border: 1px solid #148CD2;
+}
+
+QToolButton::menu-indicator {
+ image: url(":/qss_icons/rc/arrow_down.png");
+ height: 8px;
+ width: 8px;
+ top: 0;
+ /* Exclude a shift for better image */
+ left: -2px;
+ /* Shift it a bit */
+}
+
+QToolButton::menu-arrow {
+ image: url(":/qss_icons/rc/arrow_down.png");
+ height: 8px;
+ width: 8px;
+}
+
+QToolButton::menu-arrow:hover {
+ image: url(":/qss_icons/rc/arrow_down_focus.png");
+}
+
+/* QCommandLinkButton -----------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QCommandLinkButton {
+ background-color: transparent;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-radius: 4px;
+ padding: 0px;
+ margin: 0px;
+}
+
+QCommandLinkButton:disabled {
+ background-color: transparent;
+ color: #787878;
+}
+
+/* ------------------------------------------------------------------------ */
+/* INPUTS - NO FIELDS ----------------------------------------------------- */
+/* ------------------------------------------------------------------------ */
+/* QComboBox --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox
+
+--------------------------------------------------------------------------- */
+QComboBox {
+ background-color: #0f1922;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ selection-background-color: #1464A0;
+ padding: 0px 4px 0px 4px;
+ min-width: 60px;
+ min-height: 19px;
+}
+
+QComboBox QAbstractItemView {
+ border: 1px solid #32414B;
+ border-radius: 0;
+ background-color: #0f1922;
+ selection-background-color: #1464A0;
+}
+
+QComboBox QAbstractItemView:hover {
+ background-color: #19232D;
+ color: #F0F0F0;
+}
+
+QComboBox QAbstractItemView:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+QComboBox QAbstractItemView:alternate {
+ background: #19232D;
+}
+
+QComboBox:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+QComboBox:hover {
+ border: 1px solid #148CD2;
+}
+
+QComboBox:focus {
+ border: 1px solid #1464A0;
+}
+
+QComboBox:on {
+ selection-background-color: #1464A0;
+}
+
+QComboBox::indicator {
+ border: none;
+ border-radius: 0;
+ background-color: transparent;
+ selection-background-color: transparent;
+ color: transparent;
+ selection-color: transparent;
+ /* Needed to remove indicator - fix #132 */
+}
+
+QComboBox::indicator:alternate {
+ background: #19232D;
+}
+
+QComboBox::item:alternate {
+ background: #19232D;
+}
+
+QComboBox::item:checked {
+ font-weight: bold;
+}
+
+QComboBox::item:selected {
+ border: 0px solid transparent;
+}
+
+QComboBox::drop-down {
+ subcontrol-origin: padding;
+ subcontrol-position: top right;
+ width: 12px;
+ border-left: 1px solid #32414B;
+}
+
+QComboBox::down-arrow {
+ image: url(":/qss_icons/rc/down_arrow.png");
+ background-color: #505F69;
+ padding: 6px 2px;
+ border: 1px solid #32414B;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ height: 8px;
+ width: 8px;
+}
+
+QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QComboBox::down-arrow:focus {
+ image: url(":/qss_icons/rc/arrow_down.png");
+}
+
+/* QSlider ----------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qslider
+
+--------------------------------------------------------------------------- */
+QSlider:disabled {
+ background: #19232D;
+}
+
+QSlider:focus {
+ border: none;
+}
+
+QSlider::groove:horizontal {
+ background: #32414B;
+ border: 1px solid #32414B;
+ height: 4px;
+ margin: 0px;
+ border-radius: 4px;
+}
+
+QSlider::groove:vertical {
+ background: #32414B;
+ border: 1px solid #32414B;
+ width: 4px;
+ margin: 0px;
+ border-radius: 4px;
+}
+
+QSlider::add-page:vertical {
+ background: #1464A0;
+ border: 1px solid #32414B;
+ width: 4px;
+ margin: 0px;
+ border-radius: 4px;
+}
+
+QSlider::add-page:vertical :disabled {
+ background: #14506E;
+}
+
+QSlider::sub-page:horizontal {
+ background: #1464A0;
+ border: 1px solid #32414B;
+ height: 4px;
+ margin: 0px;
+ border-radius: 4px;
+}
+
+QSlider::sub-page:horizontal:disabled {
+ background: #14506E;
+}
+
+QSlider::handle:horizontal {
+ background: #787878;
+ border: 1px solid #32414B;
+ width: 8px;
+ height: 8px;
+ margin: -8px 0px;
+ border-radius: 4px;
+}
+
+QSlider::handle:horizontal:hover {
+ background: #148CD2;
+ border: 1px solid #148CD2;
+}
+
+QSlider::handle:horizontal:focus {
+ border: 1px solid #1464A0;
+}
+
+QSlider::handle:vertical {
+ background: #787878;
+ border: 1px solid #32414B;
+ width: 8px;
+ height: 8px;
+ margin: 0 -8px;
+ border-radius: 4px;
+}
+
+QSlider::handle:vertical:hover {
+ background: #148CD2;
+ border: 1px solid #148CD2;
+}
+
+QSlider::handle:vertical:focus {
+ border: 1px solid #1464A0;
+}
+
+/* QLineEdit --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlineedit
+
+--------------------------------------------------------------------------- */
+QLineEdit {
+ background-color: #19232D;
+ padding-top: 2px;
+ /* This QLineEdit fix 103, 111 */
+ padding-bottom: 2px;
+ /* This QLineEdit fix 103, 111 */
+ padding-left: 4px;
+ padding-right: 4px;
+ border-style: solid;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ color: #F0F0F0;
+}
+
+QLineEdit:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+QLineEdit:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QLineEdit:focus {
+ border: 1px solid #1464A0;
+}
+
+QLineEdit:selected {
+ background-color: #1464A0;
+ color: #32414B;
+}
+
+/* QTabWiget --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtabwidget-and-qtabbar
+
+--------------------------------------------------------------------------- */
+QTabWidget {
+ padding: 2px;
+ selection-background-color: #32414B;
+}
+
+QTabWidget QWidget {
+ /* Fixes #189 */
+ border-radius: 4px;
+}
+
+QTabWidget::pane {
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ margin: 0px;
+ /* Fixes double border inside pane with pyqt5 */
+ padding: 0px;
+}
+
+QTabWidget::pane:selected {
+ background-color: #32414B;
+ border: 1px solid #1464A0;
+}
+
+/* QTabBar ----------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtabwidget-and-qtabbar
+
+--------------------------------------------------------------------------- */
+QTabBar {
+ qproperty-drawBase: 0;
+ border-radius: 4px;
+ margin: 0px;
+ padding: 2px;
+ border: 0;
+ /* left: 5px; move to the right by 5px - removed for fix */
+}
+
+QTabBar::close-button {
+ border: 0;
+ margin: 2px;
+ padding: 2px;
+ image: url(":/qss_icons/rc/window_close.png");
+}
+
+QTabBar::close-button:hover {
+ image: url(":/qss_icons/rc/window_close_focus.png");
+}
+
+QTabBar::close-button:pressed {
+ image: url(":/qss_icons/rc/window_close_pressed.png");
+}
+
+/* QTabBar::tab - selected ------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtabwidget-and-qtabbar
+
+--------------------------------------------------------------------------- */
+QTabBar::tab {
+ /* !selected and disabled ----------------------------------------- */
+ /* selected ------------------------------------------------------- */
+}
+
+QTabBar::tab:top:selected:disabled {
+ border-bottom: 3px solid #14506E;
+ color: #787878;
+ background-color: #32414B;
+}
+
+QTabBar::tab:bottom:selected:disabled {
+ border-top: 3px solid #14506E;
+ color: #787878;
+ background-color: #32414B;
+}
+
+QTabBar::tab:left:selected:disabled {
+ border-right: 3px solid #14506E;
+ color: #787878;
+ background-color: #32414B;
+}
+
+QTabBar::tab:right:selected:disabled {
+ border-left: 3px solid #14506E;
+ color: #787878;
+ background-color: #32414B;
+}
+
+QTabBar::tab:top:!selected:disabled {
+ border-bottom: 3px solid #19232D;
+ color: #787878;
+ background-color: #19232D;
+}
+
+QTabBar::tab:bottom:!selected:disabled {
+ border-top: 3px solid #19232D;
+ color: #787878;
+ background-color: #19232D;
+}
+
+QTabBar::tab:left:!selected:disabled {
+ border-right: 3px solid #19232D;
+ color: #787878;
+ background-color: #19232D;
+}
+
+QTabBar::tab:right:!selected:disabled {
+ border-left: 3px solid #19232D;
+ color: #787878;
+ background-color: #19232D;
+}
+
+QTabBar::tab:top:!selected {
+ border-bottom: 2px solid #19232D;
+ margin-top: 2px;
+}
+
+QTabBar::tab:bottom:!selected {
+ border-top: 2px solid #19232D;
+ margin-bottom: 3px;
+}
+
+QTabBar::tab:left:!selected {
+ border-left: 2px solid #19232D;
+ margin-right: 2px;
+}
+
+QTabBar::tab:right:!selected {
+ border-right: 2px solid #19232D;
+ margin-left: 2px;
+}
+
+QTabBar::tab:top {
+ background-color: #32414B;
+ color: #F0F0F0;
+ min-width: 36px;
+ margin-left: 2px;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ border-bottom: 3px solid #32414B;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+
+QTabBar::tab:top:selected {
+ background-color: #505F69;
+ color: #F0F0F0;
+ border-bottom: 3px solid #1464A0;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+
+QTabBar::tab:top:!selected:hover {
+ border: 1px solid #148CD2;
+ border-bottom: 3px solid #148CD2;
+ /* Fixes spyder-ide/spyder#9766 */
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+QTabBar::tab:bottom {
+ color: #F0F0F0;
+ min-width: 36px;
+ border-top: 3px solid #32414B;
+ background-color: #32414B;
+ margin-left: 2px;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
+QTabBar::tab:bottom:selected {
+ color: #F0F0F0;
+ background-color: #505F69;
+ border-top: 3px solid #1464A0;
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
+QTabBar::tab:bottom:!selected:hover {
+ border: 1px solid #148CD2;
+ border-top: 3px solid #148CD2;
+ /* Fixes spyder-ide/spyder#9766 */
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+QTabBar::tab:left {
+ color: #F0F0F0;
+ background-color: #32414B;
+ margin-top: 2px;
+ padding-left: 2px;
+ padding-right: 2px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ min-height: 5px;
+}
+
+QTabBar::tab:left:selected {
+ color: #F0F0F0;
+ background-color: #505F69;
+ border-right: 3px solid #1464A0;
+}
+
+QTabBar::tab:left:!selected:hover {
+ border: 1px solid #148CD2;
+ border-right: 3px solid #148CD2;
+ padding: 0px;
+}
+
+QTabBar::tab:right {
+ color: #F0F0F0;
+ background-color: #32414B;
+ margin-top: 2px;
+ padding-left: 2px;
+ padding-right: 2px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ min-height: 5px;
+}
+
+QTabBar::tab:right:selected {
+ color: #F0F0F0;
+ background-color: #505F69;
+ border-left: 3px solid #1464A0;
+}
+
+QTabBar::tab:right:!selected:hover {
+ border: 1px solid #148CD2;
+ border-left: 3px solid #148CD2;
+ padding: 0px;
+}
+
+QTabBar QToolButton {
+ /* Fixes #136 */
+ background-color: #32414B;
+ height: 12px;
+ width: 12px;
+}
+
+QTabBar QToolButton:pressed {
+ background-color: #32414B;
+}
+
+QTabBar QToolButton:pressed:hover {
+ border: 1px solid #148CD2;
+}
+
+QTabBar QToolButton::left-arrow:enabled {
+ image: url(":/qss_icons/rc/arrow_left.png");
+}
+
+QTabBar QToolButton::left-arrow:disabled {
+ image: url(":/qss_icons/rc/arrow_left_disabled.png");
+}
+
+QTabBar QToolButton::right-arrow:enabled {
+ image: url(":/qss_icons/rc/arrow_right.png");
+}
+
+QTabBar QToolButton::right-arrow:disabled {
+ image: url(":/qss_icons/rc/arrow_right_disabled.png");
+}
+
+/* QDockWiget -------------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QDockWidget {
+ outline: 1px solid #32414B;
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ titlebar-close-icon: url(":/qss_icons/rc/window_close.png");
+ titlebar-normal-icon: url(":/qss_icons/rc/window_undock.png");
+}
+
+QDockWidget::title {
+ /* Better size for title bar */
+ padding: 6px;
+ spacing: 4px;
+ border: none;
+ background-color: #32414B;
+}
+
+QDockWidget::close-button {
+ background-color: #32414B;
+ border-radius: 4px;
+ border: none;
+}
+
+QDockWidget::close-button:hover {
+ image: url(":/qss_icons/rc/window_close_focus.png");
+}
+
+QDockWidget::close-button:pressed {
+ image: url(":/qss_icons/rc/window_close_pressed.png");
+}
+
+QDockWidget::float-button {
+ background-color: #32414B;
+ border-radius: 4px;
+ border: none;
+}
+
+QDockWidget::float-button:hover {
+ image: url(":/qss_icons/rc/window_undock_focus.png");
+}
+
+QDockWidget::float-button:pressed {
+ image: url(":/qss_icons/rc/window_undock_pressed.png");
+}
+
+/* QTreeView QListView QTableView -----------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtreeview
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlistview
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtableview
+
+--------------------------------------------------------------------------- */
+
+QTreeView:branch:has-children:!has-siblings:closed, QTreeView:branch:closed:has-children:has-siblings {
+ border-image: none;
+ image: url(":/qss_icons/rc/branch_closed.png");
+}
+
+QTreeView:branch:open:has-children:!has-siblings, QTreeView:branch:open:has-children:has-siblings {
+ border-image: none;
+ image: url(":/qss_icons/rc/branch_open.png");
+}
+
+QTreeView:branch:has-children:!has-siblings:closed:hover, QTreeView:branch:closed:has-children:has-siblings:hover {
+ image: url(":/qss_icons/rc/branch_closed_focus.png");
+}
+
+QTreeView:branch:open:has-children:!has-siblings:hover, QTreeView:branch:open:has-children:has-siblings:hover {
+ image: url(":/qss_icons/rc/branch_open_focus.png");
+}
+
+QTreeView::indicator:checked,
+QListView::indicator:checked {
+ image: url(":/qss_icons/rc/checkbox_checked.png");
+}
+
+QTreeView::indicator:checked:hover, QTreeView::indicator:checked:focus, QTreeView::indicator:checked:pressed,
+QListView::indicator:checked:hover,
+QListView::indicator:checked:focus,
+QListView::indicator:checked:pressed {
+ image: url(":/qss_icons/rc/checkbox_checked_focus.png");
+}
+
+QTreeView::indicator:unchecked,
+QListView::indicator:unchecked {
+ image: url(":/qss_icons/rc/checkbox_unchecked.png");
+}
+
+QTreeView::indicator:unchecked:hover, QTreeView::indicator:unchecked:focus, QTreeView::indicator:unchecked:pressed,
+QListView::indicator:unchecked:hover,
+QListView::indicator:unchecked:focus,
+QListView::indicator:unchecked:pressed {
+ image: url(":/qss_icons/rc/checkbox_unchecked_focus.png");
+}
+
+QTreeView::indicator:indeterminate,
+QListView::indicator:indeterminate {
+ image: url(":/qss_icons/rc/checkbox_indeterminate.png");
+}
+
+QTreeView::indicator:indeterminate:hover, QTreeView::indicator:indeterminate:focus, QTreeView::indicator:indeterminate:pressed,
+QListView::indicator:indeterminate:hover,
+QListView::indicator:indeterminate:focus,
+QListView::indicator:indeterminate:pressed {
+ image: url(":/qss_icons/rc/checkbox_indeterminate_focus.png");
+}
+
+QTreeView,
+QListView,
+QTableView,
+QColumnView {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ gridline-color: #32414B;
+ border-radius: 4px;
+}
+
+QTreeView:disabled,
+QListView:disabled,
+QTableView:disabled,
+QColumnView:disabled {
+ background-color: #19232D;
+ color: #787878;
+}
+
+QTreeView:selected,
+QListView:selected,
+QTableView:selected,
+QColumnView:selected {
+ background-color: #1464A0;
+ color: #32414B;
+}
+
+QTreeView:hover,
+QListView:hover,
+QTableView:hover,
+QColumnView:hover {
+ background-color: #19232D;
+ border: 1px solid #148CD2;
+}
+
+QTreeView::item:pressed,
+QListView::item:pressed,
+QTableView::item:pressed,
+QColumnView::item:pressed {
+ background-color: #1464A0;
+}
+
+QTreeView::item:selected:hover,
+QListView::item:selected:hover,
+QTableView::item:selected:hover,
+QColumnView::item:selected:hover {
+ background: #1464A0;
+ color: #19232D;
+}
+
+QTreeView::item:selected:active,
+QListView::item:selected:active,
+QTableView::item:selected:active,
+QColumnView::item:selected:active {
+ background-color: #1464A0;
+}
+
+QTreeView::item:!selected:hover,
+QListView::item:!selected:hover,
+QTableView::item:!selected:hover,
+QColumnView::item:!selected:hover {
+ outline: 0;
+ color: #148CD2;
+ background-color: #32414B;
+}
+
+QTableCornerButton::section {
+ background-color: #19232D;
+ border: 1px transparent #32414B;
+ border-radius: 0px;
+}
+
+/* QHeaderView ------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qheaderview
+
+--------------------------------------------------------------------------- */
+QHeaderView {
+ background-color: #19232D;
+ border: 0px transparent #19232D;
+ padding: 0px;
+ margin: 0px;
+ border-radius: 0px;
+}
+
+QHeaderView:disabled {
+ background-color: #19232D;
+ border: 1px transparent #19232D;
+ padding: 2px;
+}
+
+QHeaderView::section {
+ background-color: #19232D;
+ color: #F0F0F0;
+ padding: 2px;
+ border-radius: 0px;
+ text-align: left;
+}
+
+QHeaderView::section:checked {
+ color: #F0F0F0;
+ background-color: #1464A0;
+}
+
+QHeaderView::section:checked:disabled {
+ color: #787878;
+ background-color: #14506E;
+}
+
+QHeaderView::section::horizontal {
+ padding-left: 4px;
+ padding-right: 4px;
+ border-left: 1px solid #32414B;
+}
+
+QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one {
+ border-left: 1px solid #19232D;
+}
+
+QHeaderView::section::horizontal:disabled {
+ color: #787878;
+}
+
+QHeaderView::section::vertical {
+ padding-left: 4px;
+ padding-right: 4px;
+ border-top: 1px solid #32414B;
+}
+
+QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one {
+ border-top: 1px solid #32414B;
+}
+
+QHeaderView::section::vertical:disabled {
+ color: #787878;
+}
+
+QHeaderView::down-arrow {
+ /* Those settings (border/width/height/background-color) solve bug */
+ /* transparent arrow background and size */
+ background-color: #19232D;
+ border: none;
+ height: 12px;
+ width: 12px;
+ padding-left: 2px;
+ padding-right: 2px;
+ image: url(":/qss_icons/rc/arrow_down.png");
+}
+
+QHeaderView::up-arrow {
+ background-color: #19232D;
+ border: none;
+ height: 12px;
+ width: 12px;
+ padding-left: 2px;
+ padding-right: 2px;
+ image: url(":/qss_icons/rc/arrow_up.png");
+}
+
+/* QToolBox --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbox
+
+--------------------------------------------------------------------------- */
+QToolBox {
+ padding: 0px;
+ border: 0px;
+ border: 1px solid #32414B;
+}
+
+QToolBox:selected {
+ padding: 0px;
+ border: 2px solid #1464A0;
+}
+
+QToolBox::tab {
+ background-color: #19232D;
+ border: 1px solid #32414B;
+ color: #F0F0F0;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+QToolBox::tab:disabled {
+ color: #787878;
+}
+
+QToolBox::tab:selected {
+ background-color: #505F69;
+ border-bottom: 2px solid #1464A0;
+}
+
+QToolBox::tab:selected:disabled {
+ background-color: #32414B;
+ border-bottom: 2px solid #14506E;
+}
+
+QToolBox::tab:!selected {
+ background-color: #32414B;
+ border-bottom: 2px solid #32414B;
+}
+
+QToolBox::tab:!selected:disabled {
+ background-color: #19232D;
+}
+
+QToolBox::tab:hover {
+ border-color: #148CD2;
+ border-bottom: 2px solid #148CD2;
+}
+
+QToolBox QScrollArea QWidget QWidget {
+ padding: 0px;
+ border: 0px;
+ background-color: #19232D;
+}
+
+/* QFrame -----------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qframe
+https://doc.qt.io/qt-5/qframe.html#-prop
+https://doc.qt.io/qt-5/qframe.html#details
+https://stackoverflow.com/questions/14581498/qt-stylesheet-for-hline-vline-color
+
+--------------------------------------------------------------------------- */
+/* (dot) .QFrame fix #141, #126, #123 */
+.QFrame {
+ border-radius: 4px;
+ border: 1px solid #32414B;
+ /* No frame */
+ /* HLine */
+ /* HLine */
+}
+
+.QFrame[frameShape="0"] {
+ border-radius: 4px;
+ border: 1px transparent #32414B;
+}
+
+.QFrame[frameShape="4"] {
+ max-height: 2px;
+ border: none;
+ background-color: #32414B;
+}
+
+.QFrame[frameShape="5"] {
+ max-width: 2px;
+ border: none;
+ background-color: #32414B;
+}
+
+/* QSplitter --------------------------------------------------------------
+
+https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsplitter
+
+--------------------------------------------------------------------------- */
+QSplitter {
+ background-color: #32414B;
+ spacing: 0px;
+ padding: 0px;
+ margin: 0px;
+}
+
+QSplitter::handle {
+ background-color: #32414B;
+ border: 0px solid #19232D;
+ spacing: 0px;
+ padding: 1px;
+ margin: 0px;
+}
+
+QSplitter::handle:hover {
+ background-color: #787878;
+}
+
+QSplitter::handle:horizontal {
+ width: 5px;
+ image: url(":/qss_icons/rc/line_vertical.png");
+}
+
+QSplitter::handle:vertical {
+ height: 5px;
+ image: url(":/qss_icons/rc/line_horizontal.png");
+}
+
+/* QDateEdit, QDateTimeEdit -----------------------------------------------
+
+--------------------------------------------------------------------------- */
+QDateEdit, QDateTimeEdit {
+ selection-background-color: #1464A0;
+ border-style: solid;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ /* This fixes 103, 111 */
+ padding-top: 2px;
+ /* This fixes 103, 111 */
+ padding-bottom: 2px;
+ padding-left: 4px;
+ padding-right: 4px;
+ min-width: 10px;
+}
+
+QDateEdit:on, QDateTimeEdit:on {
+ selection-background-color: #1464A0;
+}
+
+QDateEdit::drop-down, QDateTimeEdit::drop-down {
+ subcontrol-origin: padding;
+ subcontrol-position: top right;
+ width: 12px;
+ border-left: 1px solid #32414B;
+}
+
+QDateEdit::down-arrow, QDateTimeEdit::down-arrow {
+ image: url(":/qss_icons/rc/arrow_down_disabled.png");
+ height: 8px;
+ width: 8px;
+}
+
+QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, QDateEdit::down-arrow:focus, QDateTimeEdit::down-arrow:on, QDateTimeEdit::down-arrow:hover, QDateTimeEdit::down-arrow:focus {
+ image: url(":/qss_icons/rc/arrow_down.png");
+}
+
+QDateEdit QAbstractItemView, QDateTimeEdit QAbstractItemView {
+ background-color: #19232D;
+ border-radius: 4px;
+ border: 1px solid #32414B;
+ selection-background-color: #1464A0;
+}
+
+/* QAbstractView ----------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+QAbstractView:hover {
+ border: 1px solid #148CD2;
+ color: #F0F0F0;
+}
+
+QAbstractView:selected {
+ background: #1464A0;
+ color: #32414B;
+}
+
+/* PlotWidget -------------------------------------------------------------
+
+--------------------------------------------------------------------------- */
+PlotWidget {
+ /* Fix cut labels in plots #134 */
+ padding: 0px;
+}
+
+
+QPushButton#TogglableStatusBarButton {
+ min-width: 0px;
+ color: #656565;
+ border: 1px solid transparent;
+ background-color: transparent;
+ padding: 0px 3px 0px 3px;
+ text-align: center;
+}
+
+QPushButton#TogglableStatusBarButton:checked {
+ color: #ffffff;
+}
+
+QPushButton#TogglableStatusBarButton:hover {
+ border: 1px solid #76797C;
+}
+
+QPushButton#RendererStatusBarButton {
+ min-width: 0px;
+ color: #656565;
+ border: 1px solid transparent;
+ background-color: transparent;
+ padding: 0px 3px 0px 3px;
+ text-align: center;
+}
+
+QPushButton#RendererStatusBarButton:hover {
+ border: 1px solid #76797C;
+}
+
+QPushButton#RendererStatusBarButton:checked {
+ color: #e85c00;
+}
+
+QPushButton#RendererStatusBarButton:!checked {
+ color: #00ccdd;
+}
+
+QPushButton#buttonRefreshDevices {
+ min-width: 19px;
+ min-height: 19px;
+ max-width: 19px;
+ max-height: 19px;
+ padding: 0px 0px;
+}
+
+QSpinBox#spinboxLStickRange,
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+ min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
+QGroupBox#motionGroup::indicator,
+QGroupBox#vibrationGroup::indicator {
+ margin-left: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox,
+QWidget#bottomPerGameInput QGroupBox#motionGroup,
+QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
+QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
+ padding: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox::title,
+QGroupBox#motionGroup::title,
+QGroupBox#vibrationGroup::title {
+ spacing: 2px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
+
+QListWidget#selectorList {
+ background-color: #0f1922;
+}
+
+QSpinBox,
+QLineEdit,
+QTreeView#hotkey_list,
+QScrollArea#scrollArea QTreeView {
+ background-color: #0f1922;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #0f1922;
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #19232d;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #76797c;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+ margin-top: 0px;
+}
+
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #32414b;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: -2px;
+ margin-right: -4px;
+ margin-bottom: 6px;
+}
+
+QCheckBox#checkboxPlayer1Connected,
+QCheckBox#checkboxPlayer2Connected,
+QCheckBox#checkboxPlayer3Connected,
+QCheckBox#checkboxPlayer4Connected,
+QCheckBox#checkboxPlayer5Connected,
+QCheckBox#checkboxPlayer6Connected,
+QCheckBox#checkboxPlayer7Connected,
+QCheckBox#checkboxPlayer8Connected {
+ spacing: 0px;
+}
+
+QWidget#connectedControllers QLabel {
+ padding: 0px;
+}
+
+QWidget#Player1LEDs,
+QWidget#Player2LEDs,
+QWidget#Player3LEDs,
+QWidget#Player4LEDs,
+QWidget#Player5LEDs,
+QWidget#Player6LEDs,
+QWidget#Player7LEDs,
+QWidget#Player8LEDs {
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox,
+QCheckBox#checkboxPlayer1Connected,
+QCheckBox#checkboxPlayer2Connected,
+QCheckBox#checkboxPlayer3Connected,
+QCheckBox#checkboxPlayer4Connected,
+QCheckBox#checkboxPlayer5Connected,
+QCheckBox#checkboxPlayer6Connected,
+QCheckBox#checkboxPlayer7Connected,
+QCheckBox#checkboxPlayer8Connected {
+ spacing: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
+QCheckBox#checkboxPlayer1Connected::indicator,
+QCheckBox#checkboxPlayer2Connected::indicator,
+QCheckBox#checkboxPlayer3Connected::indicator,
+QCheckBox#checkboxPlayer4Connected::indicator,
+QCheckBox#checkboxPlayer5Connected::indicator,
+QCheckBox#checkboxPlayer6Connected::indicator,
+QCheckBox#checkboxPlayer7Connected::indicator,
+QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 14px;
+ height: 14px;
+ margin-left: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
+QCheckBox#checkboxPlayer1Connected::indicator:checked,
+QCheckBox#checkboxPlayer2Connected::indicator:checked,
+QCheckBox#checkboxPlayer3Connected::indicator:checked,
+QCheckBox#checkboxPlayer4Connected::indicator:checked,
+QCheckBox#checkboxPlayer5Connected::indicator:checked,
+QCheckBox#checkboxPlayer6Connected::indicator:checked,
+QCheckBox#checkboxPlayer7Connected::indicator:checked,
+QCheckBox#checkboxPlayer8Connected::indicator:checked,
+QGroupBox#groupConnectedController::indicator:checked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: #39ff14;
+ image: none;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
+QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
+QGroupBox#groupConnectedController::indicator:unchecked {
+ border-radius: 2px;
+ border: 1px solid #929192;
+ background: #19232d;
+ image: none;
+}
+
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
+}
diff --git a/dist/yuzu.manifest b/dist/yuzu.manifest
index fd30b656f..038edff23 100644
--- a/dist/yuzu.manifest
+++ b/dist/yuzu.manifest
@@ -1,24 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
- <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
- <security>
- <requestedPrivileges>
- <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
- </requestedPrivileges>
- </security>
- </trustInfo>
- <application xmlns="urn:schemas-microsoft-com:asm.v3">
- <windowsSettings>
- <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
- <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
- </windowsSettings>
- </application>
- <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
- <application>
- <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
- <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
- <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
- <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
- </application>
- </compatibility>
-</assembly> \ No newline at end of file
+<assembly manifestVersion="1.0"
+ xmlns="urn:schemas-microsoft-com:asm.v1"
+ xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <asmv3:application>
+ <asmv3:windowsSettings>
+ <!-- Windows 7/8/8.1/10 -->
+ <dpiAware
+ xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ true/pm
+ </dpiAware>
+ <!-- Windows 10, version 1607 or later -->
+ <dpiAwareness
+ xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+ PerMonitorV2
+ </dpiAwareness>
+ <!-- Windows 10, version 1703 or later -->
+ <gdiScaling
+ xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
+ true
+ </gdiScaling>
+ <ws2:longPathAware
+ xmlns:ws3="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+ true
+ </ws2:longPathAware>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+ <compatibility
+ xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+ <trustInfo
+ xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <!--
+ UAC settings:
+ - app should run at same integrity level as calling process
+ - app does not need to manipulate windows belonging to
+ higher-integrity-level processes
+ -->
+ <requestedExecutionLevel
+ level="asInvoker"
+ uiAccess="false"
+ />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 61ad3487a..421b35890 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -1,16 +1,20 @@
# Definitions for all external bundled libraries
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")
+list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/externals/find-modules")
include(DownloadExternals)
+# xbyak
+if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
+ add_library(xbyak INTERFACE)
+ target_include_directories(xbyak SYSTEM INTERFACE ./xbyak/xbyak)
+ target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
+endif()
+
# Catch
add_library(catch-single-include INTERFACE)
target_include_directories(catch-single-include INTERFACE catch/single_include)
-# libfmt
-add_subdirectory(fmt)
-add_library(fmt::fmt ALIAS fmt)
-
# Dynarmic
if (ARCHITECTURE_x86_64)
set(DYNARMIC_TESTS OFF)
@@ -29,11 +33,6 @@ add_subdirectory(glad)
# inih
add_subdirectory(inih)
-# lz4
-set(LZ4_BUNDLED_MODE ON)
-add_subdirectory(lz4/contrib/cmake_unofficial EXCLUDE_FROM_ALL)
-target_include_directories(lz4_static INTERFACE ./lz4/lib)
-
# mbedtls
add_subdirectory(mbedtls EXCLUDE_FROM_ALL)
target_include_directories(mbedtls PUBLIC ./mbedtls/include)
@@ -46,17 +45,9 @@ target_include_directories(microprofile INTERFACE ./microprofile)
add_library(unicorn-headers INTERFACE)
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
-# Zstandard
-add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)
-target_include_directories(libzstd_static INTERFACE ./zstd/lib)
-
# SoundTouch
add_subdirectory(soundtouch)
-# Opus
-add_subdirectory(opus)
-target_include_directories(opus INTERFACE ./opus/include)
-
# Cubeb
if(ENABLE_CUBEB)
set(BUILD_TESTS OFF CACHE BOOL "")
@@ -74,28 +65,38 @@ if (ENABLE_VULKAN)
add_subdirectory(sirit)
endif()
-# zlib
-add_subdirectory(zlib EXCLUDE_FROM_ALL)
-set(ZLIB_LIBRARIES z)
-
# libzip
-add_subdirectory(libzip EXCLUDE_FROM_ALL)
+find_package(Libzip 1.5)
+if (NOT LIBZIP_FOUND)
+ message(STATUS "libzip 1.5 or newer not found, falling back to externals")
+ add_subdirectory(libzip EXCLUDE_FROM_ALL)
+endif()
if (ENABLE_WEB_SERVICE)
- # LibreSSL
- set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
- add_subdirectory(libressl EXCLUDE_FROM_ALL)
- target_include_directories(ssl INTERFACE ./libressl/include)
- target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
-
- # lurlparser
- add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
+ find_package(OpenSSL 1.1)
+ if (OPENSSL_FOUND)
+ set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
+ else()
+ # LibreSSL
+ set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
+ set(OPENSSLDIR "/etc/ssl/")
+ add_subdirectory(libressl EXCLUDE_FROM_ALL)
+ target_include_directories(ssl INTERFACE ./libressl/include)
+ target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
+ get_directory_property(OPENSSL_LIBRARIES
+ DIRECTORY libressl
+ DEFINITION OPENSSL_LIBS)
+ endif()
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
-
- # JSON
- add_library(json-headers INTERFACE)
- target_include_directories(json-headers INTERFACE ./json)
+ target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
+ target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
+ if (WIN32)
+ target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32)
+ endif()
endif()
+
+# Opus
+add_subdirectory(opus)
diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers
-Subproject 0e78ffd1dcfc3e9f14a966b9660dbc59bd967c5
+Subproject 8188e3fbbc105591064093440f88081fb957d4f
diff --git a/externals/boost b/externals/boost
deleted file mode 160000
-Subproject 5e8300b76a627f3a1ba215304e04ead33b5bc23
diff --git a/externals/catch b/externals/catch
deleted file mode 160000
-Subproject 15cf3caaceb21172ea42a24e595a2eb58c3ec96
diff --git a/externals/cubeb b/externals/cubeb
-Subproject 6f2420de8f155b10330cf973900ac7bdbfee589
+Subproject 616d773441b5355800ce64197a699e6cd6b3617
diff --git a/externals/dynarmic b/externals/dynarmic
-Subproject f6ae9e1c3311b747b7b91fd903c62bf40b3b9c8
+Subproject 0e1112b7df77ae55a62a51622940d5c8f9e8c84
diff --git a/externals/find-modules/FindCatch2.cmake b/externals/find-modules/FindCatch2.cmake
new file mode 100644
index 000000000..ce1d40bae
--- /dev/null
+++ b/externals/find-modules/FindCatch2.cmake
@@ -0,0 +1,49 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_Catch2 QUIET Catch2)
+
+find_path(Catch2_INCLUDE_DIR
+ NAMES catch.hpp
+ PATHS ${PC_Catch2_INCLUDE_DIRS} ${CONAN_CATCH2_ROOT}
+ PATH_SUFFIXES catch2
+)
+
+if(Catch2_INCLUDE_DIR)
+ file(STRINGS "${Catch2_INCLUDE_DIR}/catch.hpp" _Catch2_version_lines
+ REGEX "#define[ \t]+CATCH_VERSION_(MAJOR|MINOR|PATCH)")
+ string(REGEX REPLACE ".*CATCH_VERSION_MAJOR +\([0-9]+\).*" "\\1" _Catch2_version_major "${_Catch2_version_lines}")
+ string(REGEX REPLACE ".*CATCH_VERSION_MINOR +\([0-9]+\).*" "\\1" _Catch2_version_minor "${_Catch2_version_lines}")
+ string(REGEX REPLACE ".*CATCH_VERSION_PATCH +\([0-9]+\).*" "\\1" _Catch2_version_patch "${_Catch2_version_lines}")
+ set(Catch2_VERSION "${_Catch2_version_major}.${_Catch2_version_minor}.${_Catch2_version_patch}")
+ unset(_Catch2_version_major)
+ unset(_Catch2_version_minor)
+ unset(_Catch2_version_patch)
+ unset(_Catch2_version_lines)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Catch2
+ FOUND_VAR Catch2_FOUND
+ REQUIRED_VARS
+ Catch2_INCLUDE_DIR
+ Catch2_VERSION
+ VERSION_VAR Catch2_VERSION
+)
+
+if(Catch2_FOUND)
+ set(Catch2_INCLUDE_DIRS ${Catch2_INCLUDE_DIR})
+ set(Catch2_DEFINITIONS ${PC_Catch2_CFLAGS_OTHER})
+endif()
+
+if(Catch2_FOUND AND NOT TARGET Catch2::Catch2)
+ add_library(Catch2::Catch2 UNKNOWN IMPORTED)
+ set_target_properties(Catch2::Catch2 PROPERTIES
+ IMPORTED_LOCATION "${Catch2_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_Catch2_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${Catch2_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ Catch2_INCLUDE_DIR
+)
diff --git a/externals/find-modules/FindFFmpeg.cmake b/externals/find-modules/FindFFmpeg.cmake
new file mode 100644
index 000000000..77b331e00
--- /dev/null
+++ b/externals/find-modules/FindFFmpeg.cmake
@@ -0,0 +1,100 @@
+# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil)
+# Once done this will define
+#
+# FFMPEG_FOUND - system has ffmpeg or libav
+# FFMPEG_INCLUDE_DIR - the ffmpeg include directory
+# FFMPEG_LIBRARIES - Link these to use ffmpeg
+# FFMPEG_LIBAVCODEC
+# FFMPEG_LIBAVFORMAT
+# FFMPEG_LIBAVUTIL
+#
+# Copyright (c) 2008 Andreas Schneider <mail@cynapses.org>
+# Modified for other libraries by Lasse Kärkkäinen <tronic>
+# Modified for Hedgewars by Stepik777
+# Modified for FFmpeg-example Tuukka Pasanen 2018
+# Modified for yuzu toastUnlimted 2020
+#
+# Redistribution and use is allowed according to the terms of the New
+# BSD license.
+#
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(FFMPEG
+ FOUND_VAR FFMPEG_FOUND
+ REQUIRED_VARS
+ FFMPEG_LIBRARY
+ FFMPEG_INCLUDE_DIR
+ VERSION_VAR FFMPEG_VERSION
+)
+
+if(FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
+ # in cache already
+ set(FFMPEG_FOUND TRUE)
+else()
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ find_package(PkgConfig)
+ if(PKG_CONFIG_FOUND)
+ pkg_check_modules(_FFMPEG_AVCODEC libavcodec)
+ pkg_check_modules(_FFMPEG_AVUTIL libavutil)
+ pkg_check_modules(_FFMPEG_SWSCALE libswscale)
+ endif()
+
+ find_path(FFMPEG_AVCODEC_INCLUDE_DIR
+ NAMES libavcodec/avcodec.h
+ PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS}
+ /usr/include
+ /usr/local/include
+ /opt/local/include
+ /sw/include
+ PATH_SUFFIXES ffmpeg libav)
+
+ find_library(FFMPEG_LIBAVCODEC
+ NAMES avcodec
+ PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS}
+ /usr/lib
+ /usr/local/lib
+ /opt/local/lib
+ /sw/lib)
+
+ find_library(FFMPEG_LIBAVUTIL
+ NAMES avutil
+ PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS}
+ /usr/lib
+ /usr/local/lib
+ /opt/local/lib
+ /sw/lib)
+
+ find_library(FFMPEG_LIBSWSCALE
+ NAMES swscale
+ PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS}
+ /usr/lib
+ /usr/local/lib
+ /opt/local/lib
+ /sw/lib)
+
+ if(FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE)
+ set(FFMPEG_FOUND TRUE)
+ endif()
+
+ if(FFMPEG_FOUND)
+ set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR})
+ set(FFMPEG_LIBRARIES
+ ${FFMPEG_LIBAVCODEC}
+ ${FFMPEG_LIBAVUTIL}
+ ${FFMPEG_LIBSWSCALE})
+ endif()
+
+ if(FFMPEG_FOUND)
+ if(NOT FFMPEG_FIND_QUIETLY)
+ message(STATUS
+ "Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}")
+ endif()
+ else()
+ if(FFMPEG_FIND_REQUIRED)
+ message(FATAL_ERROR
+ "Could not find libavcodec or libavutil or libswscale")
+ endif()
+ endif()
+endif()
diff --git a/externals/find-modules/FindLibUSB.cmake b/externals/find-modules/FindLibUSB.cmake
new file mode 100644
index 000000000..dec0b98b0
--- /dev/null
+++ b/externals/find-modules/FindLibUSB.cmake
@@ -0,0 +1,43 @@
+# - Find libusb-1.0 library
+# This module defines
+# LIBUSB_INCLUDE_DIR, where to find bluetooth.h
+# LIBUSB_LIBRARIES, the libraries needed to use libusb-1.0.
+# LIBUSB_FOUND, If false, do not try to use libusb-1.0.
+#
+# Copyright (c) 2009, Michal Cihar, <michal@cihar.com>
+#
+# vim: expandtab sw=4 ts=4 sts=4:
+
+if(ANDROID)
+ set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
+ message(STATUS "libusb-1.0 not found.")
+elseif (NOT LIBUSB_FOUND)
+ pkg_check_modules (LIBUSB_PKG libusb-1.0)
+
+ find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h
+ PATHS
+ ${LIBUSB_PKG_INCLUDE_DIRS}
+ /usr/include/libusb-1.0
+ /usr/include
+ /usr/local/include/libusb-1.0
+ /usr/local/include
+ )
+
+ find_library(LIBUSB_LIBRARIES NAMES usb-1.0 usb
+ PATHS
+ ${LIBUSB_PKG_LIBRARY_DIRS}
+ /usr/lib
+ /usr/local/lib
+ )
+
+ if(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
+ set(LIBUSB_FOUND TRUE CACHE INTERNAL "libusb-1.0 found")
+ message(STATUS "Found libusb-1.0: ${LIBUSB_INCLUDE_DIR}, ${LIBUSB_LIBRARIES}")
+ else(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
+ set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
+ message(STATUS "libusb-1.0 not found.")
+ endif(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
+
+ mark_as_advanced(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
+endif ()
+
diff --git a/externals/find-modules/FindLibzip.cmake b/externals/find-modules/FindLibzip.cmake
new file mode 100644
index 000000000..f36b1687a
--- /dev/null
+++ b/externals/find-modules/FindLibzip.cmake
@@ -0,0 +1,72 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBZIP QUIET libzip)
+
+find_path(LIBZIP_INCLUDE_DIR
+ NAMES zip.h
+ PATHS ${PC_LIBZIP_INCLUDE_DIRS}
+ "$ENV{LIB_DIR}/include"
+ "$ENV{INCLUDE}"
+ /usr/local/include
+ /usr/include
+)
+find_path(LIBZIP_INCLUDE_DIR_ZIPCONF
+ NAMES zipconf.h
+ HINTS ${PC_LIBZIP_INCLUDE_DIRS}
+ "$ENV{LIB_DIR}/include"
+ "$ENV{LIB_DIR}/lib/libzip/include"
+ "$ENV{LIB}/lib/libzip/include"
+ /usr/local/lib/libzip/include
+ /usr/lib/libzip/include
+ /usr/local/include
+ /usr/include
+ "$ENV{INCLUDE}"
+)
+find_library(LIBZIP_LIBRARY
+ NAMES zip
+ PATHS ${PC_LIBZIP_LIBRARY_DIRS}
+ "$ENV{LIB_DIR}/lib" "$ENV{LIB}" /usr/local/lib /usr/lib
+)
+
+if (LIBZIP_INCLUDE_DIR_ZIPCONF)
+ FILE(READ "${LIBZIP_INCLUDE_DIR_ZIPCONF}/zipconf.h" _LIBZIP_VERSION_CONTENTS)
+ if (_LIBZIP_VERSION_CONTENTS)
+ STRING(REGEX REPLACE ".*#define LIBZIP_VERSION \"([0-9.]+)\".*" "\\1" LIBZIP_VERSION "${_LIBZIP_VERSION_CONTENTS}")
+ endif()
+ unset(_LIBZIP_VERSION_CONTENTS)
+endif()
+
+set(LIBZIP_VERSION ${LIBZIP_VERSION} CACHE STRING "Version number of libzip")
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Libzip
+ FOUND_VAR LIBZIP_FOUND
+ REQUIRED_VARS
+ LIBZIP_LIBRARY
+ LIBZIP_INCLUDE_DIR
+ LIBZIP_INCLUDE_DIR_ZIPCONF
+ LIBZIP_VERSION
+ VERSION_VAR LIBZIP_VERSION
+)
+
+if(LIBZIP_FOUND)
+ set(LIBZIP_LIBRARIES ${LIBZIP_LIBRARY})
+ set(LIBZIP_INCLUDE_DIRS ${LIBZIP_INCLUDE_DIR})
+ set(LIBZIP_DEFINITIONS ${PC_LIBZIP_CFLAGS_OTHER})
+endif()
+
+if(LIBZIP_FOUND AND NOT TARGET libzip::libzip)
+ add_library(libzip::libzip UNKNOWN IMPORTED)
+ set_target_properties(libzip::libzip PROPERTIES
+ IMPORTED_LOCATION "${LIBZIP_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_LIBZIP_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${LIBZIP_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ LIBZIP_INCLUDE_DIR
+ LIBZIP_INCLUDE_DIR_ZIPCONF
+ LIBZIP_LIBRARY
+ LIBZIP_VERSION
+)
diff --git a/externals/cmake-modules/FindUnicorn.cmake b/externals/find-modules/FindUnicorn.cmake
index a0f2a71f6..a0f2a71f6 100644
--- a/externals/cmake-modules/FindUnicorn.cmake
+++ b/externals/find-modules/FindUnicorn.cmake
diff --git a/externals/find-modules/Findfmt.cmake b/externals/find-modules/Findfmt.cmake
new file mode 100644
index 000000000..8ba51cbea
--- /dev/null
+++ b/externals/find-modules/Findfmt.cmake
@@ -0,0 +1,69 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_fmt QUIET fmt)
+
+find_path(fmt_INCLUDE_DIR
+ NAMES format.h
+ PATHS ${PC_fmt_INCLUDE_DIRS} ${CONAN_INCLUDE_DIRS_fmt}
+ PATH_SUFFIXES fmt
+)
+
+find_library(fmt_LIBRARY
+ NAMES fmt
+ PATHS ${PC_fmt_LIBRARY_DIRS} ${CONAN_LIB_DIRS_fmt}
+)
+
+if(fmt_INCLUDE_DIR)
+ set(_fmt_version_file "${fmt_INCLUDE_DIR}/core.h")
+ if(NOT EXISTS "${_fmt_version_file}")
+ set(_fmt_version_file "${fmt_INCLUDE_DIR}/format.h")
+ endif()
+ if(EXISTS "${_fmt_version_file}")
+ # parse "#define FMT_VERSION 60200" to 6.2.0
+ file(STRINGS "${_fmt_version_file}" fmt_VERSION_LINE
+ REGEX "^#define[ \t]+FMT_VERSION[ \t]+[0-9]+$")
+ string(REGEX REPLACE "^#define[ \t]+FMT_VERSION[ \t]+([0-9]+)$"
+ "\\1" fmt_VERSION "${fmt_VERSION_LINE}")
+ foreach(ver "fmt_VERSION_PATCH" "fmt_VERSION_MINOR" "fmt_VERSION_MAJOR")
+ math(EXPR ${ver} "${fmt_VERSION} % 100")
+ math(EXPR fmt_VERSION "(${fmt_VERSION} - ${${ver}}) / 100")
+ endforeach()
+ set(fmt_VERSION
+ "${fmt_VERSION_MAJOR}.${fmt_VERSION_MINOR}.${fmt_VERSION_PATCH}")
+ endif()
+ unset(_fmt_version_file)
+ unset(fmt_VERSION_LINE)
+ unset(fmt_VERSION_MAJOR)
+ unset(fmt_VERSION_MINOR)
+ unset(fmt_VERSION_PATCH)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(fmt
+ FOUND_VAR fmt_FOUND
+ REQUIRED_VARS
+ fmt_LIBRARY
+ fmt_INCLUDE_DIR
+ fmt_VERSION
+ VERSION_VAR fmt_VERSION
+)
+
+if(fmt_FOUND)
+ set(fmt_LIBRARIES ${fmt_LIBRARY})
+ set(fmt_INCLUDE_DIRS ${fmt_INCLUDE_DIR})
+ set(fmt_DEFINITIONS ${PC_fmt_CFLAGS_OTHER})
+endif()
+
+if(fmt_FOUND AND NOT TARGET fmt::fmt)
+ add_library(fmt::fmt UNKNOWN IMPORTED)
+ set_target_properties(fmt::fmt PROPERTIES
+ IMPORTED_LOCATION "${fmt_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_fmt_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${fmt_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ fmt_INCLUDE_DIR
+ fmt_LIBRARY
+)
diff --git a/externals/find-modules/Findlz4.cmake b/externals/find-modules/Findlz4.cmake
new file mode 100644
index 000000000..6279854c0
--- /dev/null
+++ b/externals/find-modules/Findlz4.cmake
@@ -0,0 +1,54 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_lz4 QUIET lz4)
+
+find_path(lz4_INCLUDE_DIR
+ NAMES lz4.h
+ PATHS ${PC_lz4_INCLUDE_DIRS}
+)
+find_library(lz4_LIBRARY
+ NAMES lz4
+ PATHS ${PC_lz4_LIBRARY_DIRS}
+)
+
+if(lz4_INCLUDE_DIR)
+ file(STRINGS "${lz4_INCLUDE_DIR}/lz4.h" _lz4_version_lines
+ REGEX "#define[ \t]+LZ4_VERSION_(MAJOR|MINOR|RELEASE)")
+ string(REGEX REPLACE ".*LZ4_VERSION_MAJOR *\([0-9]*\).*" "\\1" _lz4_version_major "${_lz4_version_lines}")
+ string(REGEX REPLACE ".*LZ4_VERSION_MINOR *\([0-9]*\).*" "\\1" _lz4_version_minor "${_lz4_version_lines}")
+ string(REGEX REPLACE ".*LZ4_VERSION_RELEASE *\([0-9]*\).*" "\\1" _lz4_version_release "${_lz4_version_lines}")
+ set(lz4_VERSION "${_lz4_version_major}.${_lz4_version_minor}.${_lz4_version_release}")
+ unset(_lz4_version_major)
+ unset(_lz4_version_minor)
+ unset(_lz4_version_release)
+ unset(_lz4_version_lines)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(lz4
+ FOUND_VAR lz4_FOUND
+ REQUIRED_VARS
+ lz4_LIBRARY
+ lz4_INCLUDE_DIR
+ VERSION_VAR lz4_VERSION
+)
+
+if(lz4_FOUND)
+ set(lz4_LIBRARIES ${lz4_LIBRARY})
+ set(lz4_INCLUDE_DIRS ${lz4_INCLUDE_DIR})
+ set(lz4_DEFINITIONS ${PC_lz4_CFLAGS_OTHER})
+endif()
+
+if(lz4_FOUND AND NOT TARGET lz4::lz4)
+ add_library(lz4::lz4 UNKNOWN IMPORTED)
+ set_target_properties(lz4::lz4 PROPERTIES
+ IMPORTED_LOCATION "${lz4_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_lz4_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${lz4_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ lz4_INCLUDE_DIR
+ lz4_LIBRARY
+)
diff --git a/externals/find-modules/Findnlohmann_json.cmake b/externals/find-modules/Findnlohmann_json.cmake
new file mode 100644
index 000000000..b0c5b3e4e
--- /dev/null
+++ b/externals/find-modules/Findnlohmann_json.cmake
@@ -0,0 +1,49 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_nlohmann_json QUIET nlohmann_json)
+
+find_path(nlohmann_json_INCLUDE_DIR
+ NAMES json.hpp
+ PATHS ${PC_nlohmann_json_INCLUDE_DIRS}
+ PATH_SUFFIXES nlohmann
+)
+
+if(nlohmann_json_INCLUDE_DIR)
+ file(STRINGS "${nlohmann_json_INCLUDE_DIR}/json.hpp" _nlohmann_json_version_lines
+ REGEX "#define[ \t]+NLOHMANN_JSON_VERSION_(MAJOR|MINOR|PATCH)")
+ string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_MAJOR +\([0-9]+\).*" "\\1" _nlohmann_json_version_major "${_nlohmann_json_version_lines}")
+ string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_MINOR +\([0-9]+\).*" "\\1" _nlohmann_json_version_minor "${_nlohmann_json_version_lines}")
+ string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_PATCH +\([0-9]+\).*" "\\1" _nlohmann_json_version_patch "${_nlohmann_json_version_lines}")
+ set(nlohmann_json_VERSION "${_nlohmann_json_version_major}.${_nlohmann_json_version_minor}.${_nlohmann_json_version_patch}")
+ unset(_nlohmann_json_version_major)
+ unset(_nlohmann_json_version_minor)
+ unset(_nlohmann_json_version_patch)
+ unset(_nlohmann_json_version_lines)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(nlohmann_json
+ FOUND_VAR nlohmann_json_FOUND
+ REQUIRED_VARS
+ nlohmann_json_INCLUDE_DIR
+ nlohmann_json_VERSION
+ VERSION_VAR nlohmann_json_VERSION
+)
+
+if(nlohmann_json_FOUND)
+ set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR})
+ set(nlohmann_json_DEFINITIONS ${PC_nlohmann_json_CFLAGS_OTHER})
+endif()
+
+if(nlohmann_json_FOUND AND NOT TARGET nlohmann_json::nlohmann_json)
+ add_library(nlohmann_json::nlohmann_json UNKNOWN IMPORTED)
+ set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
+ IMPORTED_LOCATION "${nlohmann_json_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_nlohmann_json_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${nlohmann_json_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ nlohmann_json_INCLUDE_DIR
+)
diff --git a/externals/find-modules/Findopus.cmake b/externals/find-modules/Findopus.cmake
new file mode 100644
index 000000000..de84bd995
--- /dev/null
+++ b/externals/find-modules/Findopus.cmake
@@ -0,0 +1,42 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_opus QUIET opus)
+
+find_path(opus_INCLUDE_DIR
+ NAMES opus.h
+ PATHS ${PC_opus_INCLUDE_DIRS}
+ PATH_SUFFIXES opus
+)
+find_library(opus_LIBRARY
+ NAMES opus
+ PATHS ${PC_opus_LIBRARY_DIRS}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(opus
+ FOUND_VAR opus_FOUND
+ REQUIRED_VARS
+ opus_LIBRARY
+ opus_INCLUDE_DIR
+ VERSION_VAR opus_VERSION
+)
+
+if(opus_FOUND)
+ set(Opus_LIBRARIES ${opus_LIBRARY})
+ set(Opus_INCLUDE_DIRS ${opus_INCLUDE_DIR})
+ set(Opus_DEFINITIONS ${PC_opus_CFLAGS_OTHER})
+endif()
+
+if(opus_FOUND AND NOT TARGET Opus::Opus)
+ add_library(Opus::Opus UNKNOWN IMPORTED)
+ set_target_properties(Opus::Opus PROPERTIES
+ IMPORTED_LOCATION "${opus_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_opus_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${opus_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ opus_INCLUDE_DIR
+ opus_LIBRARY
+)
diff --git a/externals/find-modules/Findzstd.cmake b/externals/find-modules/Findzstd.cmake
new file mode 100644
index 000000000..539abbafc
--- /dev/null
+++ b/externals/find-modules/Findzstd.cmake
@@ -0,0 +1,55 @@
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_zstd QUIET libzstd)
+
+find_path(zstd_INCLUDE_DIR
+ NAMES zstd.h
+ PATHS ${PC_zstd_INCLUDE_DIRS}
+)
+find_library(zstd_LIBRARY
+ NAMES zstd
+ PATHS ${PC_zstd_LIBRARY_DIRS}
+)
+
+if(zstd_INCLUDE_DIR)
+ file(STRINGS "${zstd_INCLUDE_DIR}/zstd.h" _zstd_version_lines
+ REGEX "#define[ \t]+ZSTD_VERSION_(MAJOR|MINOR|RELEASE)")
+ string(REGEX REPLACE ".*ZSTD_VERSION_MAJOR *\([0-9]*\).*" "\\1" _zstd_version_major "${_zstd_version_lines}")
+ string(REGEX REPLACE ".*ZSTD_VERSION_MINOR *\([0-9]*\).*" "\\1" _zstd_version_minor "${_zstd_version_lines}")
+ string(REGEX REPLACE ".*ZSTD_VERSION_RELEASE *\([0-9]*\).*" "\\1" _zstd_version_release "${_zstd_version_lines}")
+ set(zstd_VERSION "${_zstd_version_major}.${_zstd_version_minor}.${_zstd_version_release}")
+ unset(_zstd_version_major)
+ unset(_zstd_version_minor)
+ unset(_zstd_version_release)
+ unset(_zstd_version_lines)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(zstd
+ FOUND_VAR zstd_FOUND
+ REQUIRED_VARS
+ zstd_LIBRARY
+ zstd_INCLUDE_DIR
+ zstd_VERSION
+ VERSION_VAR zstd_VERSION
+)
+
+if(zstd_FOUND)
+ set(zstd_LIBRARIES ${zstd_LIBRARY})
+ set(zstd_INCLUDE_DIRS ${zstd_INCLUDE_DIR})
+ set(zstd_DEFINITIONS ${PC_zstd_CFLAGS_OTHER})
+endif()
+
+if(zstd_FOUND AND NOT TARGET zstd::zstd)
+ add_library(zstd::zstd UNKNOWN IMPORTED)
+ set_target_properties(zstd::zstd PROPERTIES
+ IMPORTED_LOCATION "${zstd_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
+ )
+endif()
+
+mark_as_advanced(
+ zstd_INCLUDE_DIR
+ zstd_LIBRARY
+)
diff --git a/externals/fmt b/externals/fmt
deleted file mode 160000
-Subproject 4b8f8fac96a7819f28f4be523ca10a2d5d8aaaf
diff --git a/externals/httplib/README.md b/externals/httplib/README.md
index 73037d297..1940e446c 100644
--- a/externals/httplib/README.md
+++ b/externals/httplib/README.md
@@ -1,4 +1,4 @@
-From https://github.com/yhirose/cpp-httplib/tree/fce8e6fefdab4ad48bc5b25c98e5ebfda4f3cf53
+From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701
MIT License
diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h
index e03842e6d..8982054e2 100644
--- a/externals/httplib/httplib.h
+++ b/externals/httplib/httplib.h
@@ -16,14 +16,18 @@
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
-#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND
-#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0
-#endif
-
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
#endif
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
+#endif
+
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
+#endif
+
#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
#endif
@@ -32,6 +36,26 @@
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
#endif
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
+#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
+#ifdef _WIN32
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
+#else
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
+#endif
+#endif
+
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif
@@ -41,16 +65,26 @@
#endif
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
-#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max())
+#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
+#endif
+
+#ifndef CPPHTTPLIB_TCP_NODELAY
+#define CPPHTTPLIB_TCP_NODELAY false
#endif
#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
#endif
+#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
+#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
+#endif
+
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
#define CPPHTTPLIB_THREAD_POOL_COUNT \
- (std::max(1u, std::thread::hardware_concurrency() - 1))
+ ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
+ ? std::thread::hardware_concurrency() - 1 \
+ : 0))
#endif
/*
@@ -92,6 +126,8 @@ using ssize_t = int;
#include <io.h>
#include <winsock2.h>
+
+#include <wincrypt.h>
#include <ws2tcpip.h>
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
@@ -100,6 +136,8 @@ using ssize_t = int;
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
+#pragma comment(lib, "crypt32.lib")
+#pragma comment(lib, "cryptui.lib")
#endif
#ifndef strcasecmp
@@ -118,6 +156,10 @@ using socket_t = SOCKET;
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
+#ifdef __linux__
+#include <resolv.h>
+#endif
+#include <netinet/tcp.h>
#ifdef CPPHTTPLIB_USE_POLL
#include <poll.h>
#endif
@@ -131,20 +173,25 @@ using socket_t = int;
#define INVALID_SOCKET (-1)
#endif //_WIN32
+#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
+#include <cctype>
+#include <climits>
#include <condition_variable>
#include <errno.h>
#include <fcntl.h>
#include <fstream>
#include <functional>
+#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <random>
#include <regex>
+#include <sstream>
#include <string>
#include <sys/stat.h>
#include <thread>
@@ -155,12 +202,17 @@ using socket_t = int;
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
+#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
+#include <openssl/applink.c>
+#endif
+
#include <iomanip>
+#include <iostream>
#include <sstream>
-// #if OPENSSL_VERSION_NUMBER < 0x1010100fL
-// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
-// #endif
+#if OPENSSL_VERSION_NUMBER < 0x1010100fL
+#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
+#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/crypto.h>
@@ -174,6 +226,11 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
#include <zlib.h>
#endif
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+#include <brotli/decode.h>
+#include <brotli/encode.h>
+#endif
+
/*
* Declaration
*/
@@ -181,6 +238,27 @@ namespace httplib {
namespace detail {
+/*
+ * Backport std::make_unique from C++14.
+ *
+ * NOTE: This code came up with the following stackoverflow post:
+ * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
+ *
+ */
+
+template <class T, class... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(Args &&... args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template <class T>
+typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(std::size_t n) {
+ typedef typename std::remove_extent<T>::type RT;
+ return std::unique_ptr<T>(new RT[n]);
+}
+
struct ci {
bool operator()(const std::string &s1, const std::string &s2) const {
return std::lexicographical_compare(
@@ -212,7 +290,8 @@ using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
class DataSink {
public:
- DataSink() = default;
+ DataSink() : os(&sb_), sb_(*this) {}
+
DataSink(const DataSink &) = delete;
DataSink &operator=(const DataSink &) = delete;
DataSink(DataSink &&) = delete;
@@ -221,10 +300,35 @@ public:
std::function<void(const char *data, size_t data_len)> write;
std::function<void()> done;
std::function<bool()> is_writable;
+ std::ostream os;
+
+private:
+ class data_sink_streambuf : public std::streambuf {
+ public:
+ explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
+
+ protected:
+ std::streamsize xsputn(const char *s, std::streamsize n) {
+ sink_.write(s, static_cast<size_t>(n));
+ return n;
+ }
+
+ private:
+ DataSink &sink_;
+ };
+
+ data_sink_streambuf sb_;
};
using ContentProvider =
- std::function<void(size_t offset, size_t length, DataSink &sink)>;
+ std::function<bool(size_t offset, size_t length, DataSink &sink)>;
+
+using ContentProviderWithoutLength =
+ std::function<bool(size_t offset, DataSink &sink)>;
+
+using ContentReceiverWithProgress =
+ std::function<bool(const char *data, size_t data_length, uint64_t offset,
+ uint64_t total_length)>;
using ContentReceiver =
std::function<bool(const char *data, size_t data_length)>;
@@ -238,18 +342,21 @@ public:
using MultipartReader = std::function<bool(MultipartContentHeader header,
ContentReceiver receiver)>;
- ContentReader(Reader reader, MultipartReader muitlpart_reader)
- : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
+ ContentReader(Reader reader, MultipartReader multipart_reader)
+ : reader_(std::move(reader)),
+ multipart_reader_(std::move(multipart_reader)) {}
bool operator()(MultipartContentHeader header,
ContentReceiver receiver) const {
- return muitlpart_reader_(header, receiver);
+ return multipart_reader_(std::move(header), std::move(receiver));
}
- bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
+ bool operator()(ContentReceiver receiver) const {
+ return reader_(std::move(receiver));
+ }
Reader reader_;
- MultipartReader muitlpart_reader_;
+ MultipartReader multipart_reader_;
};
using Range = std::pair<ssize_t, ssize_t>;
@@ -261,6 +368,9 @@ struct Request {
Headers headers;
std::string body;
+ std::string remote_addr;
+ int remote_port = -1;
+
// for server
std::string version;
std::string target;
@@ -272,7 +382,9 @@ struct Request {
// for client
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
ResponseHandler response_handler;
- ContentReceiver content_receiver;
+ ContentReceiverWithProgress content_receiver;
+ size_t content_length = 0;
+ ContentProvider content_provider;
Progress progress;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -281,6 +393,8 @@ struct Request {
bool has_header(const char *key) const;
std::string get_header_value(const char *key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const char *key, size_t id = 0) const;
size_t get_header_value_count(const char *key) const;
void set_header(const char *key, const char *val);
void set_header(const char *key, const std::string &val);
@@ -295,35 +409,40 @@ struct Request {
MultipartFormData get_file_value(const char *key) const;
// private members...
- size_t content_length;
- ContentProvider content_provider;
+ size_t authorization_count_ = 0;
};
struct Response {
std::string version;
int status = -1;
+ std::string reason;
Headers headers;
std::string body;
bool has_header(const char *key) const;
std::string get_header_value(const char *key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const char *key, size_t id = 0) const;
size_t get_header_value_count(const char *key) const;
void set_header(const char *key, const char *val);
void set_header(const char *key, const std::string &val);
- void set_redirect(const char *url);
+ void set_redirect(const char *url, int status = 302);
+ void set_redirect(const std::string &url, int status = 302);
void set_content(const char *s, size_t n, const char *content_type);
- void set_content(const std::string &s, const char *content_type);
+ void set_content(std::string s, const char *content_type);
void set_content_provider(
- size_t length,
- std::function<void(size_t offset, size_t length, DataSink &sink)>
- provider,
- std::function<void()> resource_releaser = [] {});
+ size_t length, const char *content_type, ContentProvider provider,
+ const std::function<void()> &resource_releaser = nullptr);
+
+ void set_content_provider(
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser = nullptr);
void set_chunked_content_provider(
- std::function<void(size_t offset, DataSink &sink)> provider,
- std::function<void()> resource_releaser = [] {});
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser = nullptr);
Response() = default;
Response(const Response &) = default;
@@ -331,15 +450,16 @@ struct Response {
Response(Response &&) = default;
Response &operator=(Response &&) = default;
~Response() {
- if (content_provider_resource_releaser) {
- content_provider_resource_releaser();
+ if (content_provider_resource_releaser_) {
+ content_provider_resource_releaser_();
}
}
// private members...
- size_t content_length = 0;
- ContentProvider content_provider;
- std::function<void()> content_provider_resource_releaser;
+ size_t content_length_ = 0;
+ ContentProvider content_provider_;
+ std::function<void()> content_provider_resource_releaser_;
+ bool is_chunked_content_provider = false;
};
class Stream {
@@ -349,22 +469,25 @@ public:
virtual bool is_readable() const = 0;
virtual bool is_writable() const = 0;
- virtual int read(char *ptr, size_t size) = 0;
- virtual int write(const char *ptr, size_t size) = 0;
- virtual std::string get_remote_addr() const = 0;
+ virtual ssize_t read(char *ptr, size_t size) = 0;
+ virtual ssize_t write(const char *ptr, size_t size) = 0;
+ virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
template <typename... Args>
- int write_format(const char *fmt, const Args &... args);
- int write(const char *ptr);
- int write(const std::string &s);
+ ssize_t write_format(const char *fmt, const Args &... args);
+ ssize_t write(const char *ptr);
+ ssize_t write(const std::string &s);
};
class TaskQueue {
public:
TaskQueue() = default;
virtual ~TaskQueue() = default;
+
virtual void enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
+
+ virtual void on_idle(){};
};
class ThreadPool : public TaskQueue {
@@ -381,7 +504,7 @@ public:
void enqueue(std::function<void()> fn) override {
std::unique_lock<std::mutex> lock(mutex_);
- jobs_.push_back(fn);
+ jobs_.push_back(std::move(fn));
cond_.notify_one();
}
@@ -439,6 +562,26 @@ private:
using Logger = std::function<void(const Request &, const Response &)>;
+using SocketOptions = std::function<void(socket_t sock)>;
+
+inline void default_socket_options(socket_t sock) {
+ int yes = 1;
+#ifdef _WIN32
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<char *>(&yes), sizeof(yes));
+#else
+#ifdef SO_REUSEPORT
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#else
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#endif
+#endif
+}
+
class Server {
public:
using Handler = std::function<void(const Request &, Response &)>;
@@ -461,23 +604,30 @@ public:
Server &Patch(const char *pattern, Handler handler);
Server &Patch(const char *pattern, HandlerWithContentReader handler);
Server &Delete(const char *pattern, Handler handler);
+ Server &Delete(const char *pattern, HandlerWithContentReader handler);
Server &Options(const char *pattern, Handler handler);
- [[deprecated]] bool set_base_dir(const char *dir,
- const char *mount_point = nullptr);
- bool set_mount_point(const char *mount_point, const char *dir);
+ bool set_base_dir(const char *dir, const char *mount_point = nullptr);
+ bool set_mount_point(const char *mount_point, const char *dir,
+ Headers headers = Headers());
bool remove_mount_point(const char *mount_point);
void set_file_extension_and_mimetype_mapping(const char *ext,
const char *mime);
void set_file_request_handler(Handler handler);
void set_error_handler(Handler handler);
+ void set_expect_100_continue_handler(Expect100ContinueHandler handler);
void set_logger(Logger logger);
- void set_expect_100_continue_handler(Expect100ContinueHandler handler);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
void set_keep_alive_max_count(size_t count);
- void set_read_timeout(time_t sec, time_t usec);
+ void set_keep_alive_timeout(time_t sec);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ void set_idle_interval(time_t sec, time_t usec = 0);
+
void set_payload_max_length(size_t length);
bool bind_to_port(const char *host, int port, int socket_flags = 0);
@@ -492,54 +642,66 @@ public:
std::function<TaskQueue *(void)> new_task_queue;
protected:
- bool process_request(Stream &strm, bool last_connection,
- bool &connection_close,
+ bool process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
const std::function<void(Request &)> &setup_request);
- size_t keep_alive_max_count_;
- time_t read_timeout_sec_;
- time_t read_timeout_usec_;
- size_t payload_max_length_;
+ std::atomic<socket_t> svr_sock_;
+ size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+ time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+ time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
+ time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
+ size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
private:
using Handlers = std::vector<std::pair<std::regex, Handler>>;
using HandlersForContentReader =
std::vector<std::pair<std::regex, HandlerWithContentReader>>;
- socket_t create_server_socket(const char *host, int port,
- int socket_flags) const;
+ socket_t create_server_socket(const char *host, int port, int socket_flags,
+ SocketOptions socket_options) const;
int bind_internal(const char *host, int port, int socket_flags);
bool listen_internal();
- bool routing(Request &req, Response &res, Stream &strm, bool last_connection);
+ bool routing(Request &req, Response &res, Stream &strm);
bool handle_file_request(Request &req, Response &res, bool head = false);
- bool dispatch_request(Request &req, Response &res, Handlers &handlers);
- bool dispatch_request_for_content_reader(Request &req, Response &res,
- ContentReader content_reader,
- HandlersForContentReader &handlers);
+ bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
+ bool
+ dispatch_request_for_content_reader(Request &req, Response &res,
+ ContentReader content_reader,
+ const HandlersForContentReader &handlers);
bool parse_request_line(const char *s, Request &req);
- bool write_response(Stream &strm, bool last_connection, const Request &req,
+ bool write_response(Stream &strm, bool close_connection, const Request &req,
Response &res);
bool write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type);
- bool read_content(Stream &strm, bool last_connection, Request &req,
- Response &res);
- bool read_content_with_content_receiver(
- Stream &strm, bool last_connection, Request &req, Response &res,
- ContentReceiver receiver, MultipartContentHeader multipart_header,
- ContentReceiver multipart_receiver);
- bool read_content_core(Stream &strm, bool last_connection, Request &req,
- Response &res, ContentReceiver receiver,
+ bool read_content(Stream &strm, Request &req, Response &res);
+ bool
+ read_content_with_content_receiver(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver);
+ bool read_content_core(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver);
virtual bool process_and_close_socket(socket_t sock);
+ struct MountPointEntry {
+ std::string mount_point;
+ std::string base_dir;
+ Headers headers;
+ };
+ std::vector<MountPointEntry> base_dirs_;
+
std::atomic<bool> is_running_;
- std::atomic<socket_t> svr_sock_;
- std::vector<std::pair<std::string, std::string>> base_dirs_;
std::map<std::string, std::string> file_extension_and_mimetype_map_;
Handler file_request_handler_;
Handlers get_handlers_;
@@ -550,293 +712,454 @@ private:
Handlers patch_handlers_;
HandlersForContentReader patch_handlers_for_content_reader_;
Handlers delete_handlers_;
+ HandlersForContentReader delete_handlers_for_content_reader_;
Handlers options_handlers_;
Handler error_handler_;
Logger logger_;
Expect100ContinueHandler expect_100_continue_handler_;
-};
-
-class Client {
-public:
- explicit Client(const std::string &host, int port = 80,
- const std::string &client_cert_path = std::string(),
- const std::string &client_key_path = std::string());
- virtual ~Client();
-
- virtual bool is_valid() const;
-
- std::shared_ptr<Response> Get(const char *path);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers);
-
- std::shared_ptr<Response> Get(const char *path, Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- Progress progress);
-
- std::shared_ptr<Response> Get(const char *path,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response>
- Get(const char *path, ContentReceiver content_receiver, Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ContentReceiver content_receiver,
- Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver,
- Progress progress);
-
- std::shared_ptr<Response> Head(const char *path);
-
- std::shared_ptr<Response> Head(const char *path, const Headers &headers);
-
- std::shared_ptr<Response> Post(const char *path, const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Params &params);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const Params &params);
-
- std::shared_ptr<Response> Post(const char *path,
- const MultipartFormDataItems &items);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items);
-
- std::shared_ptr<Response> Put(const char *path, const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Params &params);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- const Params &params);
-
- std::shared_ptr<Response> Patch(const char *path, const std::string &body,
- const char *content_type);
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = default_socket_options;
+};
- std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
+enum Error {
+ Success = 0,
+ Unknown,
+ Connection,
+ BindIPAddress,
+ Read,
+ Write,
+ ExceedRedirectCount,
+ Canceled,
+ SSLConnection,
+ SSLLoadingCerts,
+ SSLServerVerification,
+ UnsupportedMultipartBoundaryChars
+};
- std::shared_ptr<Response> Patch(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
+class Result {
+public:
+ Result(std::unique_ptr<Response> res, Error err)
+ : res_(std::move(res)), err_(err) {}
+ operator bool() const { return res_ != nullptr; }
+ bool operator==(std::nullptr_t) const { return res_ == nullptr; }
+ bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
+ const Response &value() const { return *res_; }
+ Response &value() { return *res_; }
+ const Response &operator*() const { return *res_; }
+ Response &operator*() { return *res_; }
+ const Response *operator->() const { return res_.get(); }
+ Response *operator->() { return res_.get(); }
+ Error error() const { return err_; }
- std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
+private:
+ std::unique_ptr<Response> res_;
+ Error err_;
+};
- std::shared_ptr<Response> Delete(const char *path);
+class ClientImpl {
+public:
+ explicit ClientImpl(const std::string &host);
- std::shared_ptr<Response> Delete(const char *path, const std::string &body,
- const char *content_type);
+ explicit ClientImpl(const std::string &host, int port);
- std::shared_ptr<Response> Delete(const char *path, const Headers &headers);
+ explicit ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
- std::shared_ptr<Response> Delete(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
+ virtual ~ClientImpl();
- std::shared_ptr<Response> Options(const char *path);
+ virtual bool is_valid() const;
- std::shared_ptr<Response> Options(const char *path, const Headers &headers);
+ Result Get(const char *path);
+ Result Get(const char *path, const Headers &headers);
+ Result Get(const char *path, Progress progress);
+ Result Get(const char *path, const Headers &headers, Progress progress);
+ Result Get(const char *path, ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+
+ Result Head(const char *path);
+ Result Head(const char *path, const Headers &headers);
+
+ Result Post(const char *path);
+ Result Post(const char *path, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Params &params);
+ Result Post(const char *path, const Headers &headers, const Params &params);
+ Result Post(const char *path, const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+
+ Result Put(const char *path);
+ Result Put(const char *path, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Params &params);
+ Result Put(const char *path, const Headers &headers, const Params &params);
+
+ Result Patch(const char *path, const std::string &body,
+ const char *content_type);
+ Result Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+ Result Patch(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Patch(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+
+ Result Delete(const char *path);
+ Result Delete(const char *path, const std::string &body,
+ const char *content_type);
+ Result Delete(const char *path, const Headers &headers);
+ Result Delete(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+
+ Result Options(const char *path);
+ Result Options(const char *path, const Headers &headers);
bool send(const Request &req, Response &res);
- bool send(const std::vector<Request> &requests,
- std::vector<Response> &responses);
+ size_t is_socket_open() const;
- void set_timeout_sec(time_t timeout_sec);
+ void stop();
- void set_read_timeout(time_t sec, time_t usec);
+ void set_default_headers(Headers headers);
- void set_keep_alive_max_count(size_t count);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
- void set_basic_auth(const char *username, const char *password);
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ void set_basic_auth(const char *username, const char *password);
+ void set_bearer_token_auth(const char *token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void set_digest_auth(const char *username, const char *password);
#endif
+ void set_keep_alive(bool on);
void set_follow_location(bool on);
void set_compress(bool on);
+ void set_decompress(bool on);
+
void set_interface(const char *intf);
void set_proxy(const char *host, int port);
-
void set_proxy_basic_auth(const char *username, const char *password);
-
+ void set_proxy_bearer_token_auth(const char *token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void set_proxy_digest_auth(const char *username, const char *password);
#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
void set_logger(Logger logger);
protected:
+ struct Socket {
+ socket_t sock = INVALID_SOCKET;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ SSL *ssl = nullptr;
+#endif
+
+ bool is_open() const { return sock != INVALID_SOCKET; }
+ };
+
+ virtual bool create_and_connect_socket(Socket &socket);
+
+ // All of:
+ // shutdown_ssl
+ // shutdown_socket
+ // close_socket
+ // should ONLY be called when socket_mutex_ is locked.
+ // Also, shutdown_ssl and close_socket should also NOT be called concurrently
+ // with a DIFFERENT thread sending requests using that socket.
+ virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
+ void shutdown_socket(Socket &socket);
+ void close_socket(Socket &socket);
+
+ // Similar to shutdown_ssl and close_socket, this should NOT be called
+ // concurrently with a DIFFERENT thread sending requests from the socket
+ void lock_socket_and_shutdown_and_close();
+
bool process_request(Stream &strm, const Request &req, Response &res,
- bool last_connection, bool &connection_close);
+ bool close_connection);
+
+ Error get_last_error() const;
+
+ void copy_settings(const ClientImpl &rhs);
+ // Error state
+ mutable std::atomic<Error> error_;
+
+ // Socket endoint information
const std::string host_;
const int port_;
const std::string host_and_port_;
+ // Current open socket
+ Socket socket_;
+ mutable std::mutex socket_mutex_;
+ std::recursive_mutex request_mutex_;
+
+ // These are all protected under socket_mutex
+ int socket_requests_in_flight_ = 0;
+ std::thread::id socket_requests_are_from_thread_ = std::thread::id();
+ bool socket_should_be_closed_when_request_is_done_ = false;
+
+ // Default headers
+ Headers default_headers_;
+
// Settings
std::string client_cert_path_;
std::string client_key_path_;
- time_t timeout_sec_ = 300;
+ time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
+ time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
-
- size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
std::string basic_auth_username_;
std::string basic_auth_password_;
+ std::string bearer_token_auth_token_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string digest_auth_username_;
std::string digest_auth_password_;
#endif
+ bool keep_alive_ = false;
bool follow_location_ = false;
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = nullptr;
+
bool compress_ = false;
+ bool decompress_ = true;
std::string interface_;
std::string proxy_host_;
- int proxy_port_;
+ int proxy_port_ = -1;
std::string proxy_basic_auth_username_;
std::string proxy_basic_auth_password_;
+ std::string proxy_bearer_token_auth_token_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string proxy_digest_auth_username_;
std::string proxy_digest_auth_password_;
#endif
- Logger logger_;
-
- void copy_settings(const Client &rhs) {
- client_cert_path_ = rhs.client_cert_path_;
- client_key_path_ = rhs.client_key_path_;
- timeout_sec_ = rhs.timeout_sec_;
- read_timeout_sec_ = rhs.read_timeout_sec_;
- read_timeout_usec_ = rhs.read_timeout_usec_;
- keep_alive_max_count_ = rhs.keep_alive_max_count_;
- basic_auth_username_ = rhs.basic_auth_username_;
- basic_auth_password_ = rhs.basic_auth_password_;
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- digest_auth_username_ = rhs.digest_auth_username_;
- digest_auth_password_ = rhs.digest_auth_password_;
-#endif
- follow_location_ = rhs.follow_location_;
- compress_ = rhs.compress_;
- interface_ = rhs.interface_;
- proxy_host_ = rhs.proxy_host_;
- proxy_port_ = rhs.proxy_port_;
- proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
- proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
- proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+ bool server_certificate_verification_ = true;
#endif
- logger_ = rhs.logger_;
- }
+
+ Logger logger_;
private:
socket_t create_client_socket() const;
bool read_response_line(Stream &strm, Response &res);
- bool write_request(Stream &strm, const Request &req, bool last_connection);
+ bool write_request(Stream &strm, const Request &req, bool close_connection);
bool redirect(const Request &req, Response &res);
bool handle_request(Stream &strm, const Request &req, Response &res,
- bool last_connection, bool &connection_close);
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- bool connect(socket_t sock, Response &res, bool &error);
-#endif
-
- std::shared_ptr<Response> send_with_content_provider(
+ bool close_connection);
+ std::unique_ptr<Response> send_with_content_provider(
const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length,
ContentProvider content_provider, const char *content_type);
- virtual bool process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback);
-
+ // socket is const because this function is called when socket_mutex_ is not locked
+ virtual bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback);
virtual bool is_ssl() const;
};
-inline void Get(std::vector<Request> &requests, const char *path,
- const Headers &headers) {
- Request req;
- req.method = "GET";
- req.path = path;
- req.headers = headers;
- requests.emplace_back(std::move(req));
-}
+class Client {
+public:
+ // Universal interface
+ explicit Client(const char *scheme_host_port);
+
+ explicit Client(const char *scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ // HTTP only interface
+ explicit Client(const std::string &host, int port);
+
+ explicit Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ ~Client();
+
+ bool is_valid() const;
+
+ Result Get(const char *path);
+ Result Get(const char *path, const Headers &headers);
+ Result Get(const char *path, Progress progress);
+ Result Get(const char *path, const Headers &headers, Progress progress);
+ Result Get(const char *path, ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+
+ Result Head(const char *path);
+ Result Head(const char *path, const Headers &headers);
+
+ Result Post(const char *path);
+ Result Post(const char *path, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Params &params);
+ Result Post(const char *path, const Headers &headers, const Params &params);
+ Result Post(const char *path, const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+ Result Put(const char *path);
+ Result Put(const char *path, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Params &params);
+ Result Put(const char *path, const Headers &headers, const Params &params);
+ Result Patch(const char *path, const std::string &body,
+ const char *content_type);
+ Result Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+ Result Patch(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Patch(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+
+ Result Delete(const char *path);
+ Result Delete(const char *path, const std::string &body,
+ const char *content_type);
+ Result Delete(const char *path, const Headers &headers);
+ Result Delete(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+
+ Result Options(const char *path);
+ Result Options(const char *path, const Headers &headers);
-inline void Get(std::vector<Request> &requests, const char *path) {
- Get(requests, path, Headers());
-}
+ bool send(const Request &req, Response &res);
-inline void Post(std::vector<Request> &requests, const char *path,
- const Headers &headers, const std::string &body,
- const char *content_type) {
- Request req;
- req.method = "POST";
- req.path = path;
- req.headers = headers;
- req.headers.emplace("Content-Type", content_type);
- req.body = body;
- requests.emplace_back(std::move(req));
-}
+ size_t is_socket_open() const;
-inline void Post(std::vector<Request> &requests, const char *path,
- const std::string &body, const char *content_type) {
- Post(requests, path, Headers(), body, content_type);
-}
+ void stop();
+
+ void set_default_headers(Headers headers);
+
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
+
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+
+ void set_basic_auth(const char *username, const char *password);
+ void set_bearer_token_auth(const char *token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_digest_auth(const char *username, const char *password);
+#endif
+
+ void set_keep_alive(bool on);
+ void set_follow_location(bool on);
+
+ void set_compress(bool on);
+
+ void set_decompress(bool on);
+
+ void set_interface(const char *intf);
+
+ void set_proxy(const char *host, int port);
+ void set_proxy_basic_auth(const char *username, const char *password);
+ void set_proxy_bearer_token_auth(const char *token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_proxy_digest_auth(const char *username, const char *password);
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
+ void set_logger(Logger logger);
+
+ // SSL
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_ca_cert_path(const char *ca_cert_file_path,
+ const char *ca_cert_dir_path = nullptr);
+
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
+
+ long get_openssl_verify_result() const;
+
+ SSL_CTX *ssl_context() const;
+#endif
+
+private:
+ std::unique_ptr<ClientImpl> cli_;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ bool is_ssl_ = false;
+#endif
+}; // namespace httplib
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLServer : public Server {
@@ -845,43 +1168,58 @@ public:
const char *client_ca_cert_file_path = nullptr,
const char *client_ca_cert_dir_path = nullptr);
- virtual ~SSLServer();
+ SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store = nullptr);
- virtual bool is_valid() const;
+ ~SSLServer() override;
+
+ bool is_valid() const override;
private:
- virtual bool process_and_close_socket(socket_t sock);
+ bool process_and_close_socket(socket_t sock) override;
SSL_CTX *ctx_;
std::mutex ctx_mutex_;
};
-class SSLClient : public Client {
+class SSLClient : public ClientImpl {
public:
- SSLClient(const std::string &host, int port = 443,
- const std::string &client_cert_path = std::string(),
- const std::string &client_key_path = std::string());
+ explicit SSLClient(const std::string &host);
- virtual ~SSLClient();
+ explicit SSLClient(const std::string &host, int port);
- virtual bool is_valid() const;
+ explicit SSLClient(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ explicit SSLClient(const std::string &host, int port, X509 *client_cert,
+ EVP_PKEY *client_key);
+
+ ~SSLClient() override;
- void set_ca_cert_path(const char *ca_ceert_file_path,
+ bool is_valid() const override;
+
+ void set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path = nullptr);
- void enable_server_certificate_verification(bool enabled);
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
long get_openssl_verify_result() const;
- SSL_CTX *ssl_context() const noexcept;
+ SSL_CTX *ssl_context() const;
private:
- virtual bool process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback);
- virtual bool is_ssl() const;
+ bool create_and_connect_socket(Socket &socket) override;
+ void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
+
+ bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) override;
+ bool is_ssl() const override;
+
+ bool connect_with_proxy(Socket &sock, Response &res, bool &success);
+ bool initialize_ssl(Socket &socket);
+
+ bool load_certs();
bool verify_host(X509 *server_cert) const;
bool verify_host_with_subject_alt_name(X509 *server_cert) const;
@@ -890,12 +1228,15 @@ private:
SSL_CTX *ctx_;
std::mutex ctx_mutex_;
+ std::once_flag initialize_cert_;
+
std::vector<std::string> host_components_;
std::string ca_cert_file_path_;
std::string ca_cert_dir_path_;
- bool server_certificate_verification_ = false;
long verify_result_ = 0;
+
+ friend class ClientImpl;
};
#endif
@@ -948,31 +1289,39 @@ inline std::string from_i_to_hex(size_t n) {
return ret;
}
+inline bool start_with(const std::string &a, const std::string &b) {
+ if (a.size() < b.size()) { return false; }
+ for (size_t i = 0; i < b.size(); i++) {
+ if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+ }
+ return true;
+}
+
inline size_t to_utf8(int code, char *buff) {
if (code < 0x0080) {
buff[0] = (code & 0x7F);
return 1;
} else if (code < 0x0800) {
- buff[0] = (0xC0 | ((code >> 6) & 0x1F));
- buff[1] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
+ buff[1] = static_cast<char>(0x80 | (code & 0x3F));
return 2;
} else if (code < 0xD800) {
- buff[0] = (0xE0 | ((code >> 12) & 0xF));
- buff[1] = (0x80 | ((code >> 6) & 0x3F));
- buff[2] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
return 3;
} else if (code < 0xE000) { // D800 - DFFF is invalid...
return 0;
} else if (code < 0x10000) {
- buff[0] = (0xE0 | ((code >> 12) & 0xF));
- buff[1] = (0x80 | ((code >> 6) & 0x3F));
- buff[2] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
return 3;
} else if (code < 0x110000) {
- buff[0] = (0xF0 | ((code >> 18) & 0x7));
- buff[1] = (0x80 | ((code >> 12) & 0x3F));
- buff[2] = (0x80 | ((code >> 6) & 0x3F));
- buff[3] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
+ buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[3] = static_cast<char>(0x80 | (code & 0x3F));
return 4;
}
@@ -992,8 +1341,8 @@ inline std::string base64_encode(const std::string &in) {
int val = 0;
int valb = -6;
- for (uint8_t c : in) {
- val = (val << 8) + c;
+ for (auto c : in) {
+ val = (val << 8) + static_cast<uint8_t>(c);
valb += 8;
while (valb >= 0) {
out.push_back(lookup[(val >> valb) & 0x3F]);
@@ -1057,13 +1406,81 @@ inline bool is_valid_path(const std::string &path) {
return true;
}
+inline std::string encode_url(const std::string &s) {
+ std::string result;
+
+ for (size_t i = 0; s[i]; i++) {
+ switch (s[i]) {
+ case ' ': result += "%20"; break;
+ case '+': result += "%2B"; break;
+ case '\r': result += "%0D"; break;
+ case '\n': result += "%0A"; break;
+ case '\'': result += "%27"; break;
+ case ',': result += "%2C"; break;
+ // case ':': result += "%3A"; break; // ok? probably...
+ case ';': result += "%3B"; break;
+ default:
+ auto c = static_cast<uint8_t>(s[i]);
+ if (c >= 0x80) {
+ result += '%';
+ char hex[4];
+ auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+ assert(len == 2);
+ result.append(hex, static_cast<size_t>(len));
+ } else {
+ result += s[i];
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+inline std::string decode_url(const std::string &s,
+ bool convert_plus_to_space) {
+ std::string result;
+
+ for (size_t i = 0; i < s.size(); i++) {
+ if (s[i] == '%' && i + 1 < s.size()) {
+ if (s[i + 1] == 'u') {
+ int val = 0;
+ if (from_hex_to_i(s, i + 2, 4, val)) {
+ // 4 digits Unicode codes
+ char buff[4];
+ size_t len = to_utf8(val, buff);
+ if (len > 0) { result.append(buff, len); }
+ i += 5; // 'u0000'
+ } else {
+ result += s[i];
+ }
+ } else {
+ int val = 0;
+ if (from_hex_to_i(s, i + 1, 2, val)) {
+ // 2 digits hex codes
+ result += static_cast<char>(val);
+ i += 2; // '00'
+ } else {
+ result += s[i];
+ }
+ }
+ } else if (convert_plus_to_space && s[i] == '+') {
+ result += ' ';
+ } else {
+ result += s[i];
+ }
+ }
+
+ return result;
+}
+
inline void read_file(const std::string &path, std::string &out) {
std::ifstream fs(path, std::ios_base::binary);
fs.seekg(0, std::ios_base::end);
auto size = fs.tellg();
fs.seekg(0);
out.resize(static_cast<size_t>(size));
- fs.read(&out[0], size);
+ fs.read(&out[0], static_cast<std::streamsize>(size));
}
inline std::string file_extension(const std::string &path) {
@@ -1073,19 +1490,41 @@ inline std::string file_extension(const std::string &path) {
return std::string();
}
+inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
+
+inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
+ size_t right) {
+ while (b + left < e && is_space_or_tab(b[left])) {
+ left++;
+ }
+ while (right > 0 && is_space_or_tab(b[right - 1])) {
+ right--;
+ }
+ return std::make_pair(left, right);
+}
+
+inline std::string trim_copy(const std::string &s) {
+ auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
+ return s.substr(r.first, r.second - r.first);
+}
+
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
- int i = 0;
- int beg = 0;
+ size_t i = 0;
+ size_t beg = 0;
- while (e ? (b + i != e) : (b[i] != '\0')) {
+ while (e ? (b + i < e) : (b[i] != '\0')) {
if (b[i] == d) {
- fn(&b[beg], &b[i]);
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
beg = i + 1;
}
i++;
}
- if (i) { fn(&b[beg], &b[i]); }
+ if (i) {
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+ }
}
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
@@ -1172,7 +1611,17 @@ inline int close_socket(socket_t sock) {
#endif
}
-inline int select_read(socket_t sock, time_t sec, time_t usec) {
+template <typename T> inline ssize_t handle_EINTR(T fn) {
+ ssize_t res = false;
+ while (true) {
+ res = fn();
+ if (res < 0 && errno == EINTR) { continue; }
+ break;
+ }
+ return res;
+}
+
+inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
struct pollfd pfd_read;
pfd_read.fd = sock;
@@ -1180,7 +1629,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return poll(&pfd_read, 1, timeout);
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
fd_set fds;
FD_ZERO(&fds);
@@ -1188,13 +1637,15 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
- return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ });
#endif
}
-inline int select_write(socket_t sock, time_t sec, time_t usec) {
+inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
struct pollfd pfd_read;
pfd_read.fd = sock;
@@ -1202,7 +1653,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return poll(&pfd_read, 1, timeout);
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
fd_set fds;
FD_ZERO(&fds);
@@ -1210,9 +1661,11 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
- return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+ });
#endif
}
@@ -1224,13 +1677,14 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- if (poll(&pfd_read, 1, timeout) > 0 &&
- pfd_read.revents & (POLLIN | POLLOUT)) {
+ auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+
+ if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
int error = 0;
socklen_t len = sizeof(error);
- return getsockopt(sock, SOL_SOCKET, SO_ERROR,
- reinterpret_cast<char *>(&error), &len) >= 0 &&
- !error;
+ auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+ reinterpret_cast<char *>(&error), &len);
+ return res >= 0 && !error;
}
return false;
#else
@@ -1243,10 +1697,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+ auto ret = handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
+ });
- if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
- (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
+ if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
int error = 0;
socklen_t len = sizeof(error);
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
@@ -1259,40 +1716,45 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
class SocketStream : public Stream {
public:
- SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec);
+ SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec);
~SocketStream() override;
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
private:
socket_t sock_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
};
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLSocketStream : public Stream {
public:
SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
- time_t read_timeout_usec);
- virtual ~SSLSocketStream();
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec);
+ ~SSLSocketStream() override;
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
private:
socket_t sock_;
SSL *ssl_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
};
#endif
@@ -1303,58 +1765,76 @@ public:
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
const std::string &get_buffer() const;
private:
std::string buffer;
- int position = 0;
+ size_t position = 0;
};
+inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
+ using namespace std::chrono;
+ auto start = steady_clock::now();
+ while (true) {
+ auto val = select_read(sock, 0, 10000);
+ if (val < 0) {
+ return false;
+ } else if (val == 0) {
+ auto current = steady_clock::now();
+ auto duration = duration_cast<milliseconds>(current - start);
+ auto timeout = keep_alive_timeout_sec * 1000;
+ if (duration.count() > timeout) { return false; }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ } else {
+ return true;
+ }
+ }
+}
+
template <typename T>
-inline bool process_socket(bool is_client_request, socket_t sock,
- size_t keep_alive_max_count, time_t read_timeout_sec,
- time_t read_timeout_usec, T callback) {
+inline bool
+process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, T callback) {
assert(keep_alive_max_count > 0);
-
auto ret = false;
-
- if (keep_alive_max_count > 1) {
- auto count = keep_alive_max_count;
- while (count > 0 &&
- (is_client_request ||
- select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
- CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
- SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
- auto last_connection = count == 1;
- auto connection_close = false;
-
- ret = callback(strm, last_connection, connection_close);
- if (!ret || connection_close) { break; }
-
- count--;
- }
- } else { // keep_alive_max_count is 0 or 1
- SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
- auto dummy_connection_close = false;
- ret = callback(strm, true, dummy_connection_close);
+ auto count = keep_alive_max_count;
+ while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
+ auto close_connection = count == 1;
+ auto connection_closed = false;
+ ret = callback(close_connection, connection_closed);
+ if (!ret || connection_closed) { break; }
+ count--;
}
-
return ret;
}
template <typename T>
-inline bool process_and_close_socket(bool is_client_request, socket_t sock,
- size_t keep_alive_max_count,
- time_t read_timeout_sec,
- time_t read_timeout_usec, T callback) {
- auto ret = process_socket(is_client_request, sock, keep_alive_max_count,
- read_timeout_sec, read_timeout_usec, callback);
- close_socket(sock);
- return ret;
+inline bool
+process_server_socket(socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ return process_server_socket_core(
+ sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
+
+template <typename T>
+inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
}
inline int shutdown_socket(socket_t sock) {
@@ -1365,18 +1845,10 @@ inline int shutdown_socket(socket_t sock) {
#endif
}
-template <typename Fn>
-socket_t create_socket(const char *host, int port, Fn fn,
- int socket_flags = 0) {
-#ifdef _WIN32
-#define SO_SYNCHRONOUS_NONALERT 0x20
-#define SO_OPENTYPE 0x7008
-
- int opt = SO_SYNCHRONOUS_NONALERT;
- setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt,
- sizeof(opt));
-#endif
-
+template <typename BindOrConnect>
+socket_t create_socket(const char *host, int port, int socket_flags,
+ bool tcp_nodelay, SocketOptions socket_options,
+ BindOrConnect bind_or_connect) {
// Get address info
struct addrinfo hints;
struct addrinfo *result;
@@ -1390,6 +1862,9 @@ socket_t create_socket(const char *host, int port, Fn fn,
auto service = std::to_string(port);
if (getaddrinfo(host, service.c_str(), &hints, &result)) {
+#ifdef __linux__
+ res_init();
+#endif
return INVALID_SOCKET;
}
@@ -1424,17 +1899,22 @@ socket_t create_socket(const char *host, int port, Fn fn,
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
#endif
- // Make 'reuse address' option available
- int yes = 1;
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
- sizeof(yes));
-#ifdef SO_REUSEPORT
- setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
- sizeof(yes));
-#endif
+ if (tcp_nodelay) {
+ int yes = 1;
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ }
+
+ if (socket_options) { socket_options(sock); }
+
+ if (rp->ai_family == AF_INET6) {
+ int no = 0;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
+ sizeof(no));
+ }
// bind or connect
- if (fn(sock, *rp)) {
+ if (bind_or_connect(sock, *rp)) {
freeaddrinfo(result);
return sock;
}
@@ -1479,7 +1959,7 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
auto ret = false;
for (auto rp = result; rp; rp = rp->ai_next) {
const auto &ai = *rp;
- if (!::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+ if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
ret = true;
break;
}
@@ -1489,8 +1969,12 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
return ret;
}
+#if !defined _WIN32 && !defined ANDROID
+#define USE_IF2IP
+#endif
+
+#ifdef USE_IF2IP
inline std::string if2ip(const std::string &ifn) {
-#ifndef _WIN32
struct ifaddrs *ifap;
getifaddrs(&ifap);
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
@@ -1506,51 +1990,83 @@ inline std::string if2ip(const std::string &ifn) {
}
}
freeifaddrs(ifap);
-#endif
return std::string();
}
+#endif
inline socket_t create_client_socket(const char *host, int port,
- time_t timeout_sec,
- const std::string &intf) {
- return create_socket(
- host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
+ bool tcp_nodelay,
+ SocketOptions socket_options,
+ time_t timeout_sec, time_t timeout_usec,
+ const std::string &intf, std::atomic<Error> &error) {
+ auto sock = create_socket(
+ host, port, 0, tcp_nodelay, std::move(socket_options),
+ [&](socket_t sock, struct addrinfo &ai) -> bool {
if (!intf.empty()) {
+#ifdef USE_IF2IP
auto ip = if2ip(intf);
if (ip.empty()) { ip = intf; }
- if (!bind_ip_address(sock, ip.c_str())) { return false; }
+ if (!bind_ip_address(sock, ip.c_str())) {
+ error = Error::BindIPAddress;
+ return false;
+ }
+#endif
}
set_nonblocking(sock, true);
- auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
+ auto ret =
+ ::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
+
if (ret < 0) {
if (is_connection_error() ||
- !wait_until_socket_is_ready(sock, timeout_sec, 0)) {
+ !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
close_socket(sock);
+ error = Error::Connection;
return false;
}
}
set_nonblocking(sock, false);
+ error = Error::Success;
return true;
});
-}
-inline std::string get_remote_addr(socket_t sock) {
- struct sockaddr_storage addr;
- socklen_t len = sizeof(addr);
+ if (sock != INVALID_SOCKET) {
+ error = Error::Success;
+ } else {
+ if (error == Error::Success) { error = Error::Connection; }
+ }
- if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
- std::array<char, NI_MAXHOST> ipstr{};
+ return sock;
+}
- if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
- ipstr.data(), static_cast<unsigned int>(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) {
- return ipstr.data();
- }
+inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
+ socklen_t addr_len, std::string &ip,
+ int &port) {
+ if (addr.ss_family == AF_INET) {
+ port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ port =
+ ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
}
- return std::string();
+ std::array<char, NI_MAXHOST> ipstr{};
+ if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
+ ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
+ 0, NI_NUMERICHOST)) {
+ ip = ipstr.data();
+ }
+}
+
+inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+
+ if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len)) {
+ get_remote_ip_and_port(addr, addr_len, ip, port);
+ }
}
inline const char *
@@ -1596,121 +2112,336 @@ find_content_type(const std::string &path,
inline const char *status_message(int status) {
switch (status) {
case 100: return "Continue";
+ case 101: return "Switching Protocol";
+ case 102: return "Processing";
+ case 103: return "Early Hints";
case 200: return "OK";
+ case 201: return "Created";
case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
case 204: return "No Content";
+ case 205: return "Reset Content";
case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 208: return "Already Reported";
+ case 226: return "IM Used";
+ case 300: return "Multiple Choice";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 306: return "unused";
+ case 307: return "Temporary Redirect";
+ case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
+ case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
case 413: return "Payload Too Large";
- case 414: return "Request-URI Too Long";
+ case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Range Not Satisfiable";
case 417: return "Expectation Failed";
+ case 418: return "I'm a teapot";
+ case 421: return "Misdirected Request";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 425: return "Too Early";
+ case 426: return "Upgrade Required";
+ case 428: return "Precondition Required";
+ case 429: return "Too Many Requests";
+ case 431: return "Request Header Fields Too Large";
+ case 451: return "Unavailable For Legal Reasons";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP Version Not Supported";
+ case 506: return "Variant Also Negotiates";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
default:
case 500: return "Internal Server Error";
}
}
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-inline bool can_compress(const std::string &content_type) {
- return !content_type.find("text/") || content_type == "image/svg+xml" ||
+inline bool can_compress_content_type(const std::string &content_type) {
+ return (!content_type.find("text/") && content_type != "text/event-stream") ||
+ content_type == "image/svg+xml" ||
content_type == "application/javascript" ||
content_type == "application/json" ||
content_type == "application/xml" ||
content_type == "application/xhtml+xml";
}
-inline bool compress(std::string &content) {
- z_stream strm;
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
+enum class EncodingType { None = 0, Gzip, Brotli };
- auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
- Z_DEFAULT_STRATEGY);
- if (ret != Z_OK) { return false; }
+inline EncodingType encoding_type(const Request &req, const Response &res) {
+ auto ret =
+ detail::can_compress_content_type(res.get_header_value("Content-Type"));
+ if (!ret) { return EncodingType::None; }
- strm.avail_in = content.size();
- strm.next_in =
- const_cast<Bytef *>(reinterpret_cast<const Bytef *>(content.data()));
+ const auto &s = req.get_header_value("Accept-Encoding");
+ (void)(s);
- std::string compressed;
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ // TODO: 'Accept-Encoding' has br, not br;q=0
+ ret = s.find("br") != std::string::npos;
+ if (ret) { return EncodingType::Brotli; }
+#endif
- std::array<char, 16384> buff{};
- do {
- strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef *>(buff.data());
- ret = deflate(&strm, Z_FINISH);
- assert(ret != Z_STREAM_ERROR);
- compressed.append(buff.data(), buff.size() - strm.avail_out);
- } while (strm.avail_out == 0);
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
+ ret = s.find("gzip") != std::string::npos;
+ if (ret) { return EncodingType::Gzip; }
+#endif
- assert(ret == Z_STREAM_END);
- assert(strm.avail_in == 0);
+ return EncodingType::None;
+}
- content.swap(compressed);
+class compressor {
+public:
+ virtual ~compressor(){};
- deflateEnd(&strm);
- return true;
-}
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) = 0;
+};
class decompressor {
public:
- decompressor() {
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
+ virtual ~decompressor() {}
+
+ virtual bool is_valid() const = 0;
+
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool decompress(const char *data, size_t data_length,
+ Callback callback) = 0;
+};
+
+class nocompressor : public compressor {
+public:
+ ~nocompressor(){};
+
+ bool compress(const char *data, size_t data_length, bool /*last*/,
+ Callback callback) override {
+ if (!data_length) { return true; }
+ return callback(data, data_length);
+ }
+};
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+class gzip_compressor : public compressor {
+public:
+ gzip_compressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
+
+ is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
+ Z_DEFAULT_STRATEGY) == Z_OK;
+ }
+
+ ~gzip_compressor() { deflateEnd(&strm_); }
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override {
+ assert(is_valid_);
+
+ auto flush = last ? Z_FINISH : Z_NO_FLUSH;
+
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+
+ int ret = Z_OK;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ do {
+ strm_.avail_out = buff.size();
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
+
+ ret = deflate(&strm_, flush);
+ assert(ret != Z_STREAM_ERROR);
+
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+ return false;
+ }
+ } while (strm_.avail_out == 0);
+
+ assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
+ assert(strm_.avail_in == 0);
+ return true;
+ }
+
+private:
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+
+class gzip_decompressor : public decompressor {
+public:
+ gzip_decompressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
// 15 is the value of wbits, which should be at the maximum possible value
- // to ensure that any gzip stream can be decoded. The offset of 16 specifies
- // that the stream to decompress will be formatted with a gzip wrapper.
- is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK;
+ // to ensure that any gzip stream can be decoded. The offset of 32 specifies
+ // that the stream type should be automatically detected either gzip or
+ // deflate.
+ is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
}
- ~decompressor() { inflateEnd(&strm); }
+ ~gzip_decompressor() { inflateEnd(&strm_); }
- bool is_valid() const { return is_valid_; }
+ bool is_valid() const override { return is_valid_; }
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override {
+ assert(is_valid_);
- template <typename T>
- bool decompress(const char *data, size_t data_length, T callback) {
int ret = Z_OK;
- strm.avail_in = data_length;
- strm.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
- std::array<char, 16384> buff{};
- do {
- strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef *>(buff.data());
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (strm_.avail_in > 0) {
+ strm_.avail_out = buff.size();
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
- ret = inflate(&strm, Z_NO_FLUSH);
+ ret = inflate(&strm_, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
- case Z_MEM_ERROR: inflateEnd(&strm); return false;
+ case Z_MEM_ERROR: inflateEnd(&strm_); return false;
}
- if (!callback(buff.data(), buff.size() - strm.avail_out)) {
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
- } while (strm.avail_out == 0);
+ }
return ret == Z_OK || ret == Z_STREAM_END;
}
private:
- bool is_valid_;
- z_stream strm;
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+class brotli_compressor : public compressor {
+public:
+ brotli_compressor() {
+ state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
+ }
+
+ ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); }
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override {
+ std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+
+ auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
+ auto available_in = data_length;
+ auto next_in = reinterpret_cast<const uint8_t *>(data);
+
+ for (;;) {
+ if (last) {
+ if (BrotliEncoderIsFinished(state_)) { break; }
+ } else {
+ if (!available_in) { break; }
+ }
+
+ auto available_out = buff.size();
+ auto next_out = buff.data();
+
+ if (!BrotliEncoderCompressStream(state_, operation, &available_in,
+ &next_in, &available_out, &next_out,
+ nullptr)) {
+ return false;
+ }
+
+ auto output_bytes = buff.size() - available_out;
+ if (output_bytes) {
+ callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
+ }
+ }
+
+ return true;
+ }
+
+private:
+ BrotliEncoderState *state_ = nullptr;
+};
+
+class brotli_decompressor : public decompressor {
+public:
+ brotli_decompressor() {
+ decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
+ decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+ : BROTLI_DECODER_RESULT_ERROR;
+ }
+
+ ~brotli_decompressor() {
+ if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
+ }
+
+ bool is_valid() const override { return decoder_s; }
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override {
+ if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_ERROR) {
+ return 0;
+ }
+
+ const uint8_t *next_in = (const uint8_t *)data;
+ size_t avail_in = data_length;
+ size_t total_out;
+
+ decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+ char *next_out = buff.data();
+ size_t avail_out = buff.size();
+
+ decoder_r = BrotliDecoderDecompressStream(
+ decoder_s, &avail_in, &next_in, &avail_out,
+ reinterpret_cast<uint8_t **>(&next_out), &total_out);
+
+ if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
+
+ if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
+ }
+
+ return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
+ }
+
+private:
+ BrotliDecoderResult decoder_r;
+ BrotliDecoderState *decoder_s = nullptr;
};
#endif
@@ -1720,21 +2451,60 @@ inline bool has_header(const Headers &headers, const char *key) {
inline const char *get_header_value(const Headers &headers, const char *key,
size_t id = 0, const char *def = nullptr) {
- auto it = headers.find(key);
- std::advance(it, id);
- if (it != headers.end()) { return it->second.c_str(); }
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second.c_str(); }
return def;
}
-inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
- int def = 0) {
- auto it = headers.find(key);
- if (it != headers.end()) {
+template <typename T>
+inline T get_header_value(const Headers & /*headers*/, const char * /*key*/,
+ size_t /*id*/ = 0, uint64_t /*def*/ = 0) {}
+
+template <>
+inline uint64_t get_header_value<uint64_t>(const Headers &headers,
+ const char *key, size_t id,
+ uint64_t def) {
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) {
return std::strtoull(it->second.data(), nullptr, 10);
}
return def;
}
+template <typename T>
+inline bool parse_header(const char *beg, const char *end, T fn) {
+ // Skip trailing spaces and tabs.
+ while (beg < end && is_space_or_tab(end[-1])) {
+ end--;
+ }
+
+ auto p = beg;
+ while (p < end && *p != ':') {
+ p++;
+ }
+
+ if (p == end) { return false; }
+
+ auto key_end = p;
+
+ if (*p++ != ':') { return false; }
+
+ while (p < end && is_space_or_tab(*p)) {
+ p++;
+ }
+
+ if (p < end) {
+ fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
+ return true;
+ }
+
+ return false;
+}
+
inline bool read_headers(Stream &strm, Headers &headers) {
const auto bufsiz = 2048;
char buf[bufsiz];
@@ -1751,42 +2521,31 @@ inline bool read_headers(Stream &strm, Headers &headers) {
continue; // Skip invalid line.
}
- // Skip trailing spaces and tabs.
+ // Exclude CRLF
auto end = line_reader.ptr() + line_reader.size() - 2;
- while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) {
- end--;
- }
- // Horizontal tab and ' ' are considered whitespace and are ignored when on
- // the left or right side of the header value:
- // - https://stackoverflow.com/questions/50179659/
- // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
- static const std::regex re(R"((.+?):[\t ]*(.+))");
-
- std::cmatch m;
- if (std::regex_match(line_reader.ptr(), end, m, re)) {
- auto key = std::string(m[1]);
- auto val = std::string(m[2]);
- headers.emplace(key, val);
- }
+ parse_header(line_reader.ptr(), end,
+ [&](std::string &&key, std::string &&val) {
+ headers.emplace(std::move(key), std::move(val));
+ });
}
return true;
}
inline bool read_content_with_length(Stream &strm, uint64_t len,
- Progress progress, ContentReceiver out) {
+ Progress progress,
+ ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
uint64_t r = 0;
while (r < len) {
auto read_len = static_cast<size_t>(len - r);
- auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return false; }
- if (!out(buf, n)) { return false; }
-
- r += n;
+ if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
+ r += static_cast<uint64_t>(n);
if (progress) {
if (!progress(r, len)) { return false; }
@@ -1801,14 +2560,16 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
uint64_t r = 0;
while (r < len) {
auto read_len = static_cast<size_t>(len - r);
- auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return; }
- r += n;
+ r += static_cast<uint64_t>(n);
}
}
-inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
+inline bool read_content_without_length(Stream &strm,
+ ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
+ uint64_t r = 0;
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
if (n < 0) {
@@ -1816,13 +2577,16 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
} else if (n == 0) {
return true;
}
- if (!out(buf, n)) { return false; }
+
+ if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
+ r += static_cast<uint64_t>(n);
}
return true;
}
-inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
+inline bool read_content_chunked(Stream &strm,
+ ContentReceiverWithProgress out) {
const auto bufsiz = 16;
char buf[bufsiz];
@@ -1830,9 +2594,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
if (!line_reader.getline()) { return false; }
- auto chunk_len = std::stoi(line_reader.ptr(), 0, 16);
+ unsigned long chunk_len;
+ while (true) {
+ char *end_ptr;
+
+ chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
+
+ if (end_ptr == line_reader.ptr()) { return false; }
+ if (chunk_len == ULONG_MAX) { return false; }
+
+ if (chunk_len == 0) { break; }
- while (chunk_len > 0) {
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
return false;
}
@@ -1842,8 +2614,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
if (!line_reader.getline()) { return false; }
-
- chunk_len = std::stoi(line_reader.ptr(), 0, 16);
}
if (chunk_len == 0) {
@@ -1860,62 +2630,91 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
"chunked");
}
-template <typename T>
-bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
- Progress progress, ContentReceiver receiver) {
-
- ContentReceiver out = [&](const char *buf, size_t n) {
- return receiver(buf, n);
- };
+template <typename T, typename U>
+bool prepare_content_receiver(T &x, int &status,
+ ContentReceiverWithProgress receiver,
+ bool decompress, U callback) {
+ if (decompress) {
+ std::string encoding = x.get_header_value("Content-Encoding");
+ std::unique_ptr<decompressor> decompressor;
+ if (encoding.find("gzip") != std::string::npos ||
+ encoding.find("deflate") != std::string::npos) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- decompressor decompressor;
-
- if (!decompressor.is_valid()) {
- status = 500;
- return false;
- }
-
- if (x.get_header_value("Content-Encoding") == "gzip") {
- out = [&](const char *buf, size_t n) {
- return decompressor.decompress(
- buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
- };
- }
+ decompressor = detail::make_unique<gzip_decompressor>();
#else
- if (x.get_header_value("Content-Encoding") == "gzip") {
- status = 415;
- return false;
- }
+ status = 415;
+ return false;
#endif
+ } else if (encoding.find("br") != std::string::npos) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ decompressor = detail::make_unique<brotli_decompressor>();
+#else
+ status = 415;
+ return false;
+#endif
+ }
- auto ret = true;
- auto exceed_payload_max_length = false;
-
- if (is_chunked_transfer_encoding(x.headers)) {
- ret = read_content_chunked(strm, out);
- } else if (!has_header(x.headers, "Content-Length")) {
- ret = read_content_without_length(strm, out);
- } else {
- auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
- if (len > payload_max_length) {
- exceed_payload_max_length = true;
- skip_content_with_length(strm, len);
- ret = false;
- } else if (len > 0) {
- ret = read_content_with_length(strm, len, progress, out);
+ if (decompressor) {
+ if (decompressor->is_valid()) {
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n,
+ uint64_t off, uint64_t len) {
+ return decompressor->decompress(buf, n,
+ [&](const char *buf, size_t n) {
+ return receiver(buf, n, off, len);
+ });
+ };
+ return callback(std::move(out));
+ } else {
+ status = 500;
+ return false;
+ }
}
}
- if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
+ uint64_t len) {
+ return receiver(buf, n, off, len);
+ };
+ return callback(std::move(out));
+}
- return ret;
+template <typename T>
+bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
+ Progress progress, ContentReceiverWithProgress receiver,
+ bool decompress) {
+ return prepare_content_receiver(
+ x, status, std::move(receiver), decompress,
+ [&](const ContentReceiverWithProgress &out) {
+ auto ret = true;
+ auto exceed_payload_max_length = false;
+
+ if (is_chunked_transfer_encoding(x.headers)) {
+ ret = read_content_chunked(strm, out);
+ } else if (!has_header(x.headers, "Content-Length")) {
+ ret = read_content_without_length(strm, out);
+ } else {
+ auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+ if (len > payload_max_length) {
+ exceed_payload_max_length = true;
+ skip_content_with_length(strm, len);
+ ret = false;
+ } else if (len > 0) {
+ ret = read_content_with_length(strm, len, std::move(progress), out);
+ }
+ }
+
+ if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ return ret;
+ });
}
template <typename T>
-inline int write_headers(Stream &strm, const T &info, const Headers &headers) {
- auto write_len = 0;
+inline ssize_t write_headers(Stream &strm, const T &info,
+ const Headers &headers) {
+ ssize_t write_len = 0;
for (const auto &x : info.headers) {
+ if (x.first == "EXCEPTION_WHAT") { continue; }
auto len =
strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
if (len < 0) { return len; }
@@ -1933,57 +2732,150 @@ inline int write_headers(Stream &strm, const T &info, const Headers &headers) {
return write_len;
}
+inline bool write_data(Stream &strm, const char *d, size_t l) {
+ size_t offset = 0;
+ while (offset < l) {
+ auto length = strm.write(d + offset, l - offset);
+ if (length < 0) { return false; }
+ offset += static_cast<size_t>(length);
+ }
+ return true;
+}
+
+template <typename T>
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
- size_t offset, size_t length) {
+ size_t offset, size_t length, T is_shutting_down) {
size_t begin_offset = offset;
size_t end_offset = offset + length;
- while (offset < end_offset) {
- ssize_t written_length = 0;
+ auto ok = true;
+ DataSink data_sink;
- DataSink data_sink;
- data_sink.write = [&](const char *d, size_t l) {
+ data_sink.write = [&](const char *d, size_t l) {
+ if (ok) {
offset += l;
- written_length = strm.write(d, l);
- };
- data_sink.done = [&](void) { written_length = -1; };
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ if (!write_data(strm, d, l)) { ok = false; }
+ }
+ };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
- content_provider(offset, end_offset - offset, data_sink);
- if (written_length < 0) { return written_length; }
+ while (offset < end_offset && !is_shutting_down()) {
+ if (!content_provider(offset, end_offset - offset, data_sink)) {
+ return -1;
+ }
+ if (!ok) { return -1; }
}
+
return static_cast<ssize_t>(offset - begin_offset);
}
template <typename T>
+inline ssize_t write_content_without_length(Stream &strm,
+ ContentProvider content_provider,
+ T is_shutting_down) {
+ size_t offset = 0;
+ auto data_available = true;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) {
+ if (ok) {
+ offset += l;
+ if (!write_data(strm, d, l)) { ok = false; }
+ }
+ };
+
+ data_sink.done = [&](void) { data_available = false; };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) { return -1; }
+ if (!ok) { return -1; }
+ }
+
+ return static_cast<ssize_t>(offset);
+}
+
+template <typename T, typename U>
inline ssize_t write_content_chunked(Stream &strm,
ContentProvider content_provider,
- T is_shutting_down) {
+ T is_shutting_down, U &compressor) {
size_t offset = 0;
auto data_available = true;
ssize_t total_written_length = 0;
- while (data_available && !is_shutting_down()) {
- ssize_t written_length = 0;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) {
+ if (!ok) { return; }
+
+ data_available = l > 0;
+ offset += l;
+
+ std::string payload;
+ if (!compressor.compress(d, l, false,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ ok = false;
+ return;
+ }
- DataSink data_sink;
- data_sink.write = [&](const char *d, size_t l) {
- data_available = l > 0;
- offset += l;
+ if (!payload.empty()) {
+ // Emit chunked response header and footer for each chunk
+ auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (write_data(strm, chunk.data(), chunk.size())) {
+ total_written_length += chunk.size();
+ } else {
+ ok = false;
+ return;
+ }
+ }
+ };
+
+ data_sink.done = [&](void) {
+ if (!ok) { return; }
+ data_available = false;
+
+ std::string payload;
+ if (!compressor.compress(nullptr, 0, true,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ ok = false;
+ return;
+ }
+
+ if (!payload.empty()) {
// Emit chunked response header and footer for each chunk
- auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
- written_length = strm.write(chunk);
- };
- data_sink.done = [&](void) {
- data_available = false;
- written_length = strm.write("0\r\n\r\n");
- };
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (write_data(strm, chunk.data(), chunk.size())) {
+ total_written_length += chunk.size();
+ } else {
+ ok = false;
+ return;
+ }
+ }
+
+ static const std::string done_marker("0\r\n\r\n");
+ if (write_data(strm, done_marker.data(), done_marker.size())) {
+ total_written_length += done_marker.size();
+ } else {
+ ok = false;
+ }
+ };
- content_provider(offset, 0, data_sink);
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
- if (written_length < 0) { return written_length; }
- total_written_length += written_length;
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) { return -1; }
+ if (!ok) { return -1; }
}
+
return total_written_length;
}
@@ -1994,6 +2886,12 @@ inline bool redirect(T &cli, const Request &req, Response &res,
new_req.path = path;
new_req.redirect_count -= 1;
+ if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
+ new_req.method = "GET";
+ new_req.body.clear();
+ new_req.headers.clear();
+ }
+
Response new_res;
auto ret = cli.send(new_req, new_res);
@@ -2001,75 +2899,20 @@ inline bool redirect(T &cli, const Request &req, Response &res,
return ret;
}
-inline std::string encode_url(const std::string &s) {
- std::string result;
-
- for (auto i = 0; s[i]; i++) {
- switch (s[i]) {
- case ' ': result += "%20"; break;
- case '+': result += "%2B"; break;
- case '\r': result += "%0D"; break;
- case '\n': result += "%0A"; break;
- case '\'': result += "%27"; break;
- case ',': result += "%2C"; break;
- // case ':': result += "%3A"; break; // ok? probably...
- case ';': result += "%3B"; break;
- default:
- auto c = static_cast<uint8_t>(s[i]);
- if (c >= 0x80) {
- result += '%';
- char hex[4];
- size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
- assert(len == 2);
- result.append(hex, len);
- } else {
- result += s[i];
- }
- break;
- }
- }
-
- return result;
-}
-
-inline std::string decode_url(const std::string &s) {
- std::string result;
+inline std::string params_to_query_str(const Params &params) {
+ std::string query;
- for (size_t i = 0; i < s.size(); i++) {
- if (s[i] == '%' && i + 1 < s.size()) {
- if (s[i + 1] == 'u') {
- int val = 0;
- if (from_hex_to_i(s, i + 2, 4, val)) {
- // 4 digits Unicode codes
- char buff[4];
- size_t len = to_utf8(val, buff);
- if (len > 0) { result.append(buff, len); }
- i += 5; // 'u0000'
- } else {
- result += s[i];
- }
- } else {
- int val = 0;
- if (from_hex_to_i(s, i + 1, 2, val)) {
- // 2 digits hex codes
- result += static_cast<char>(val);
- i += 2; // '00'
- } else {
- result += s[i];
- }
- }
- } else if (s[i] == '+') {
- result += ' ';
- } else {
- result += s[i];
- }
+ for (auto it = params.begin(); it != params.end(); ++it) {
+ if (it != params.begin()) { query += "&"; }
+ query += it->first;
+ query += "=";
+ query += encode_url(it->second);
}
-
- return result;
+ return query;
}
inline void parse_query_text(const std::string &s, Params &params) {
- split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
+ split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
std::string key;
std::string val;
split(b, e, '=', [&](const char *b2, const char *e2) {
@@ -2079,7 +2922,10 @@ inline void parse_query_text(const std::string &s, Params &params) {
val.assign(b2, e2);
}
});
- params.emplace(key, decode_url(val));
+
+ if (!key.empty()) {
+ params.emplace(decode_url(key, true), decode_url(val, true));
+ }
});
}
@@ -2087,17 +2933,20 @@ inline bool parse_multipart_boundary(const std::string &content_type,
std::string &boundary) {
auto pos = content_type.find("boundary=");
if (pos == std::string::npos) { return false; }
-
boundary = content_type.substr(pos + 9);
- return true;
+ if (boundary.length() >= 2 && boundary.front() == '"' &&
+ boundary.back() == '"') {
+ boundary = boundary.substr(1, boundary.size() - 2);
+ }
+ return !boundary.empty();
}
-inline bool parse_range_header(const std::string &s, Ranges &ranges) {
+inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
std::smatch m;
if (std::regex_match(s, m, re_first_range)) {
- auto pos = m.position(1);
- auto len = m.length(1);
+ auto pos = static_cast<size_t>(m.position(1));
+ auto len = static_cast<size_t>(m.length(1));
bool all_valid_ranges = true;
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
if (!all_valid_ranges) return;
@@ -2124,25 +2973,26 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
return all_valid_ranges;
}
return false;
-}
+} catch (...) { return false; }
class MultipartFormDataParser {
public:
- MultipartFormDataParser() {}
+ MultipartFormDataParser() = default;
- void set_boundary(const std::string &boundary) { boundary_ = boundary; }
+ void set_boundary(std::string &&boundary) { boundary_ = boundary; }
bool is_valid() const { return is_valid_; }
template <typename T, typename U>
- bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
- static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)",
- std::regex_constants::icase);
+ bool parse(const char *buf, size_t n, const T &content_callback,
+ const U &header_callback) {
static const std::regex re_content_disposition(
"^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
"\"(.*?)\")?\\s*$",
std::regex_constants::icase);
+ static const std::string dash_ = "--";
+ static const std::string crlf_ = "\r\n";
buf_.append(buf, n); // TODO: performance improvement
@@ -2152,10 +3002,7 @@ public:
auto pattern = dash_ + boundary_ + crlf_;
if (pattern.size() > buf_.size()) { return true; }
auto pos = buf_.find(pattern);
- if (pos != 0) {
- is_done_ = true;
- return false;
- }
+ if (pos != 0) { return false; }
buf_.erase(0, pattern.size());
off_ += pattern.size();
state_ = 1;
@@ -2173,7 +3020,6 @@ public:
if (pos == 0) {
if (!header_callback(file_)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
buf_.erase(0, crlf_.size());
@@ -2182,12 +3028,13 @@ public:
break;
}
- auto header = buf_.substr(0, pos);
- {
+ static const std::string header_name = "content-type:";
+ const auto header = buf_.substr(0, pos);
+ if (start_with(header, header_name)) {
+ file_.content_type = trim_copy(header.substr(header_name.size()));
+ } else {
std::smatch m;
- if (std::regex_match(header, m, re_content_type)) {
- file_.content_type = m[1];
- } else if (std::regex_match(header, m, re_content_disposition)) {
+ if (std::regex_match(header, m, re_content_disposition)) {
file_.name = m[1];
file_.filename = m[2];
}
@@ -2197,6 +3044,7 @@ public:
off_ += pos + crlf_.size();
pos = buf_.find(crlf_);
}
+ if (state_ != 3) { return true; }
break;
}
case 3: { // Body
@@ -2205,10 +3053,17 @@ public:
if (pattern.size() > buf_.size()) { return true; }
auto pos = buf_.find(pattern);
- if (pos == std::string::npos) { pos = buf_.size(); }
+ if (pos == std::string::npos) {
+ pos = buf_.size();
+ while (pos > 0) {
+ auto c = buf_[pos - 1];
+ if (c != '\r' && c != '\n' && c != '-') { break; }
+ pos--;
+ }
+ }
+
if (!content_callback(buf_.data(), pos)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2224,7 +3079,6 @@ public:
if (pos != std::string::npos) {
if (!content_callback(buf_.data(), pos)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2234,7 +3088,6 @@ public:
} else {
if (!content_callback(buf_.data(), pattern.size())) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2246,20 +3099,19 @@ public:
}
case 4: { // Boundary
if (crlf_.size() > buf_.size()) { return true; }
- if (buf_.find(crlf_) == 0) {
+ if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
buf_.erase(0, crlf_.size());
off_ += crlf_.size();
state_ = 1;
} else {
auto pattern = dash_ + crlf_;
if (pattern.size() > buf_.size()) { return true; }
- if (buf_.find(pattern) == 0) {
+ if (buf_.compare(0, pattern.size(), pattern) == 0) {
buf_.erase(0, pattern.size());
off_ += pattern.size();
is_valid_ = true;
state_ = 5;
} else {
- is_done_ = true;
return true;
}
}
@@ -2282,14 +3134,11 @@ private:
file_.content_type.clear();
}
- const std::string dash_ = "--";
- const std::string crlf_ = "\r\n";
std::string boundary_;
std::string buf_;
size_t state_ = 0;
- size_t is_valid_ = false;
- size_t is_done_ = false;
+ bool is_valid_ = false;
size_t off_ = 0;
MultipartFormData file_;
};
@@ -2308,8 +3157,13 @@ inline std::string make_multipart_data_boundary() {
static const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ // std::random_device might actually be deterministic on some
+ // platforms, but due to lack of support in the c++ standard library,
+ // doing better requires either some ugly hacks or breaking portability.
std::random_device seed_gen;
- std::mt19937 engine(seed_gen());
+ // Request 128 bits of entropy for initialization
+ std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
+ std::mt19937 engine(seed_sequence);
std::string result = "--cpp-httplib-multipart-data-";
@@ -2329,12 +3183,14 @@ get_range_offset_and_length(const Request &req, size_t content_length,
return std::make_pair(0, content_length);
}
+ auto slen = static_cast<ssize_t>(content_length);
+
if (r.first == -1) {
- r.first = content_length - r.second;
- r.second = content_length - 1;
+ r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
+ r.second = slen - 1;
}
- if (r.second == -1) { r.second = content_length - 1; }
+ if (r.second == -1) { r.second = slen - 1; }
return std::make_pair(r.first, r.second - r.first + 1);
}
@@ -2420,16 +3276,19 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
return data_length;
}
+template <typename T>
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
Response &res,
const std::string &boundary,
- const std::string &content_type) {
+ const std::string &content_type,
+ T is_shutting_down) {
return process_multipart_ranges_data(
req, res, boundary, content_type,
[&](const std::string &token) { strm.write(token); },
[&](const char *token) { strm.write(token); },
[&](size_t offset, size_t length) {
- return write_content(strm, res.content_provider, offset, length) >= 0;
+ return write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down) >= 0;
});
}
@@ -2438,20 +3297,31 @@ get_range_offset_and_length(const Request &req, const Response &res,
size_t index) {
auto r = req.ranges[index];
- if (r.second == -1) { r.second = res.content_length - 1; }
+ if (r.second == -1) {
+ r.second = static_cast<ssize_t>(res.content_length_) - 1;
+ }
return std::make_pair(r.first, r.second - r.first + 1);
}
inline bool expect_content(const Request &req) {
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
- req.method == "PRI") {
+ req.method == "PRI" || req.method == "DELETE") {
return true;
}
// TODO: check if Content-Length is set
return false;
}
+inline bool has_crlf(const char *s) {
+ auto p = s;
+ while (*p) {
+ if (*p == '\r' || *p == '\n') { return true; }
+ p++;
+ }
+ return false;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
template <typename CTX, typename Init, typename Update, typename Final>
inline std::string message_digest(const std::string &s, Init init,
@@ -2489,6 +3359,33 @@ inline std::string SHA_512(const std::string &s) {
#endif
#ifdef _WIN32
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
+inline bool load_system_certs_on_windows(X509_STORE *store) {
+ auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
+
+ if (!hStore) { return false; }
+
+ PCCERT_CONTEXT pContext = NULL;
+ while (pContext = CertEnumCertificatesInStore(hStore, pContext)) {
+ auto encoded_cert =
+ static_cast<const unsigned char *>(pContext->pbCertEncoded);
+
+ auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+ if (x509) {
+ X509_STORE_add_cert(store, x509);
+ X509_free(x509);
+ }
+ }
+
+ CertFreeCertificateContext(pContext);
+ CertCloseStore(hStore, 0);
+
+ return true;
+}
+#endif
+
class WSInit {
public:
WSInit() {
@@ -2502,31 +3399,6 @@ public:
static WSInit wsinit_;
#endif
-} // namespace detail
-
-// Header utilities
-inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
- std::string field = "bytes=";
- auto i = 0;
- for (auto r : ranges) {
- if (i != 0) { field += ", "; }
- if (r.first != -1) { field += std::to_string(r.first); }
- field += '-';
- if (r.second != -1) { field += std::to_string(r.second); }
- i++;
- }
- return std::make_pair("Range", field);
-}
-
-inline std::pair<std::string, std::string>
-make_basic_authentication_header(const std::string &username,
- const std::string &password,
- bool is_proxy = false) {
- auto field = "Basic " + detail::base64_encode(username + ":" + password);
- auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
- return std::make_pair(key, field);
-}
-
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline std::pair<std::string, std::string> make_digest_authentication_header(
const Request &req, const std::map<std::string, std::string> &auth,
@@ -2566,17 +3438,18 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
":" + qop + ":" + H(A2));
}
- auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
- "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
- "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
- "\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
+ auto field = "Digest username=\"" + username + "\", realm=\"" +
+ auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
+ "\", uri=\"" + req.path + "\", algorithm=" + algo +
+ ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
+ "\", response=\"" + response + "\"";
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
}
#endif
-inline bool parse_www_authenticate(const httplib::Response &res,
+inline bool parse_www_authenticate(const Response &res,
std::map<std::string, std::string> &auth,
bool is_proxy) {
auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
@@ -2593,9 +3466,13 @@ inline bool parse_www_authenticate(const httplib::Response &res,
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
for (auto i = beg; i != std::sregex_iterator(); ++i) {
auto m = *i;
- auto key = s.substr(m.position(1), m.length(1));
- auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2))
- : s.substr(m.position(3), m.length(3));
+ auto key = s.substr(static_cast<size_t>(m.position(1)),
+ static_cast<size_t>(m.length(1)));
+ auto val = m.length(2) > 0
+ ? s.substr(static_cast<size_t>(m.position(2)),
+ static_cast<size_t>(m.length(2)))
+ : s.substr(static_cast<size_t>(m.position(3)),
+ static_cast<size_t>(m.length(3)));
auth[key] = val;
}
return true;
@@ -2612,13 +3489,60 @@ inline std::string random_string(size_t length) {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
- return charset[rand() % max_index];
+ return charset[static_cast<size_t>(rand()) % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}
+class ContentProviderAdapter {
+public:
+ explicit ContentProviderAdapter(
+ ContentProviderWithoutLength &&content_provider)
+ : content_provider_(content_provider) {}
+
+ bool operator()(size_t offset, size_t, DataSink &sink) {
+ return content_provider_(offset, sink);
+ }
+
+private:
+ ContentProviderWithoutLength content_provider_;
+};
+
+} // namespace detail
+
+// Header utilities
+inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+ std::string field = "bytes=";
+ auto i = 0;
+ for (auto r : ranges) {
+ if (i != 0) { field += ", "; }
+ if (r.first != -1) { field += std::to_string(r.first); }
+ field += '-';
+ if (r.second != -1) { field += std::to_string(r.second); }
+ i++;
+ }
+ return std::make_pair("Range", std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+ const std::string &password,
+ bool is_proxy = false) {
+ auto field = "Basic " + detail::base64_encode(username + ":" + password);
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_bearer_token_authentication_header(const std::string &token,
+ bool is_proxy = false) {
+ auto field = "Bearer " + token;
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
// Request implementation
inline bool Request::has_header(const char *key) const {
return detail::has_header(headers, key);
@@ -2628,17 +3552,26 @@ inline std::string Request::get_header_value(const char *key, size_t id) const {
return detail::get_header_value(headers, key, id, "");
}
+template <typename T>
+inline T Request::get_header_value(const char *key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
inline size_t Request::get_header_value_count(const char *key) const {
auto r = headers.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline void Request::set_header(const char *key, const char *val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
}
inline void Request::set_header(const char *key, const std::string &val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+ headers.emplace(key, val);
+ }
}
inline bool Request::has_param(const char *key) const {
@@ -2646,15 +3579,16 @@ inline bool Request::has_param(const char *key) const {
}
inline std::string Request::get_param_value(const char *key, size_t id) const {
- auto it = params.find(key);
- std::advance(it, id);
- if (it != params.end()) { return it->second; }
+ auto rng = params.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second; }
return std::string();
}
inline size_t Request::get_param_value_count(const char *key) const {
auto r = params.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline bool Request::is_multipart_form_data() const {
@@ -2682,22 +3616,41 @@ inline std::string Response::get_header_value(const char *key,
return detail::get_header_value(headers, key, id, "");
}
+template <typename T>
+inline T Response::get_header_value(const char *key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
inline size_t Response::get_header_value_count(const char *key) const {
auto r = headers.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline void Response::set_header(const char *key, const char *val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
}
inline void Response::set_header(const char *key, const std::string &val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+ headers.emplace(key, val);
+ }
+}
+
+inline void Response::set_redirect(const char *url, int stat) {
+ if (!detail::has_crlf(url)) {
+ set_header("Location", url);
+ if (300 <= stat && stat < 400) {
+ this->status = stat;
+ } else {
+ this->status = 302;
+ }
+ }
}
-inline void Response::set_redirect(const char *url) {
- set_header("Location", url);
- status = 302;
+inline void Response::set_redirect(const std::string &url, int stat) {
+ set_redirect(url.c_str(), stat);
}
inline void Response::set_content(const char *s, size_t n,
@@ -2706,62 +3659,79 @@ inline void Response::set_content(const char *s, size_t n,
set_header("Content-Type", content_type);
}
-inline void Response::set_content(const std::string &s,
- const char *content_type) {
- body = s;
+inline void Response::set_content(std::string s, const char *content_type) {
+ body = std::move(s);
set_header("Content-Type", content_type);
}
-inline void Response::set_content_provider(
- size_t in_length,
- std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
- std::function<void()> resource_releaser) {
+inline void
+Response::set_content_provider(size_t in_length, const char *content_type,
+ ContentProvider provider,
+ const std::function<void()> &resource_releaser) {
assert(in_length > 0);
- content_length = in_length;
- content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
- provider(offset, length, sink);
- };
- content_provider_resource_releaser = resource_releaser;
+ set_header("Content-Type", content_type);
+ content_length_ = in_length;
+ content_provider_ = std::move(provider);
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = false;
+}
+
+inline void
+Response::set_content_provider(const char *content_type,
+ ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = false;
}
inline void Response::set_chunked_content_provider(
- std::function<void(size_t offset, DataSink &sink)> provider,
- std::function<void()> resource_releaser) {
- content_length = 0;
- content_provider = [provider](size_t offset, size_t, DataSink &sink) {
- provider(offset, sink);
- };
- content_provider_resource_releaser = resource_releaser;
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = true;
}
// Rstream implementation
-inline int Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); }
+inline ssize_t Stream::write(const char *ptr) {
+ return write(ptr, strlen(ptr));
+}
-inline int Stream::write(const std::string &s) {
+inline ssize_t Stream::write(const std::string &s) {
return write(s.data(), s.size());
}
template <typename... Args>
-inline int Stream::write_format(const char *fmt, const Args &... args) {
- std::array<char, 2048> buf;
+inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
+ const auto bufsiz = 2048;
+ std::array<char, bufsiz> buf;
#if defined(_MSC_VER) && _MSC_VER < 1900
- auto n = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...);
+ auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...);
#else
- auto n = snprintf(buf.data(), buf.size() - 1, fmt, args...);
+ auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
#endif
- if (n <= 0) { return n; }
+ if (sn <= 0) { return sn; }
- if (n >= static_cast<int>(buf.size()) - 1) {
+ auto n = static_cast<size_t>(sn);
+
+ if (n >= buf.size() - 1) {
std::vector<char> glowable_buf(buf.size());
- while (n >= static_cast<int>(glowable_buf.size() - 1)) {
+ while (n >= glowable_buf.size() - 1) {
glowable_buf.resize(glowable_buf.size() * 2);
#if defined(_MSC_VER) && _MSC_VER < 1900
- n = _snprintf_s(&glowable_buf[0], glowable_buf.size(),
- glowable_buf.size() - 1, fmt, args...);
+ n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(),
+ glowable_buf.size() - 1, fmt,
+ args...));
#else
- n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...);
+ n = static_cast<size_t>(
+ snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
#endif
}
return write(&glowable_buf[0], n);
@@ -2774,32 +3744,53 @@ namespace detail {
// Socket stream implementation
inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec)
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
: sock_(sock), read_timeout_sec_(read_timeout_sec),
- read_timeout_usec_(read_timeout_usec) {}
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec) {}
inline SocketStream::~SocketStream() {}
inline bool SocketStream::is_readable() const {
- return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
}
inline bool SocketStream::is_writable() const {
- return detail::select_write(sock_, 0, 0) > 0;
+ return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
}
-inline int SocketStream::read(char *ptr, size_t size) {
- if (is_readable()) { return recv(sock_, ptr, static_cast<int>(size), 0); }
- return -1;
+inline ssize_t SocketStream::read(char *ptr, size_t size) {
+ if (!is_readable()) { return -1; }
+
+#ifdef _WIN32
+ if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
+ return -1;
+ }
+ return recv(sock_, ptr, static_cast<int>(size), 0);
+#else
+ return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
+#endif
}
-inline int SocketStream::write(const char *ptr, size_t size) {
- if (is_writable()) { return send(sock_, ptr, static_cast<int>(size), 0); }
- return -1;
+inline ssize_t SocketStream::write(const char *ptr, size_t size) {
+ if (!is_writable()) { return -1; }
+
+#ifdef _WIN32
+ if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
+ return -1;
+ }
+ return send(sock_, ptr, static_cast<int>(size), 0);
+#else
+ return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
+#endif
}
-inline std::string SocketStream::get_remote_addr() const {
- return detail::get_remote_addr(sock_);
+inline void SocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ return detail::get_remote_ip_and_port(sock_, ip, port);
}
// Buffer stream implementation
@@ -2807,22 +3798,23 @@ inline bool BufferStream::is_readable() const { return true; }
inline bool BufferStream::is_writable() const { return true; }
-inline int BufferStream::read(char *ptr, size_t size) {
-#if defined(_MSC_VER) && _MSC_VER < 1900
- int len_read = static_cast<int>(buffer._Copy_s(ptr, size, size, position));
+inline ssize_t BufferStream::read(char *ptr, size_t size) {
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ auto len_read = buffer._Copy_s(ptr, size, size, position);
#else
- int len_read = static_cast<int>(buffer.copy(ptr, size, position));
+ auto len_read = buffer.copy(ptr, size, position);
#endif
- position += len_read;
- return len_read;
+ position += static_cast<size_t>(len_read);
+ return static_cast<ssize_t>(len_read);
}
-inline int BufferStream::write(const char *ptr, size_t size) {
+inline ssize_t BufferStream::write(const char *ptr, size_t size) {
buffer.append(ptr, size);
- return static_cast<int>(size);
+ return static_cast<ssize_t>(size);
}
-inline std::string BufferStream::get_remote_addr() const { return ""; }
+inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
+ int & /*port*/) const {}
inline const std::string &BufferStream::get_buffer() const { return buffer; }
@@ -2830,67 +3822,77 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; }
// HTTP server implementation
inline Server::Server()
- : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
- read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
- read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
- payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false),
- svr_sock_(INVALID_SOCKET) {
+ : new_task_queue(
+ [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
+ svr_sock_(INVALID_SOCKET), is_running_(false) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
- new_task_queue = [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); };
}
inline Server::~Server() {}
inline Server &Server::Get(const char *pattern, Handler handler) {
- get_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ get_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern, Handler handler) {
- post_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ post_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern,
HandlerWithContentReader handler) {
post_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern, Handler handler) {
- put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ put_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern,
HandlerWithContentReader handler) {
put_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern, Handler handler) {
- patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ patch_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern,
HandlerWithContentReader handler) {
patch_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Delete(const char *pattern, Handler handler) {
- delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ delete_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+inline Server &Server::Delete(const char *pattern,
+ HandlerWithContentReader handler) {
+ delete_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Options(const char *pattern, Handler handler) {
- options_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ options_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -2898,11 +3900,12 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
return set_mount_point(mount_point, dir);
}
-inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
+inline bool Server::set_mount_point(const char *mount_point, const char *dir,
+ Headers headers) {
if (detail::is_dir(dir)) {
std::string mnt = mount_point ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
- base_dirs_.emplace_back(mnt, dir);
+ base_dirs_.push_back({mnt, dir, std::move(headers)});
return true;
}
}
@@ -2911,7 +3914,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
inline bool Server::remove_mount_point(const char *mount_point) {
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
- if (it->first == mount_point) {
+ if (it->mount_point == mount_point) {
base_dirs_.erase(it);
return true;
}
@@ -2932,6 +3935,12 @@ inline void Server::set_error_handler(Handler handler) {
error_handler_ = std::move(handler);
}
+inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
+inline void Server::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+}
+
inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
inline void
@@ -2943,11 +3952,25 @@ inline void Server::set_keep_alive_max_count(size_t count) {
keep_alive_max_count_ = count;
}
+inline void Server::set_keep_alive_timeout(time_t sec) {
+ keep_alive_timeout_sec_ = sec;
+}
+
inline void Server::set_read_timeout(time_t sec, time_t usec) {
read_timeout_sec_ = sec;
read_timeout_usec_ = usec;
}
+inline void Server::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
+}
+
+inline void Server::set_idle_interval(time_t sec, time_t usec) {
+ idle_interval_sec_ = sec;
+ idle_interval_usec_ = usec;
+}
+
inline void Server::set_payload_max_length(size_t length) {
payload_max_length_ = length;
}
@@ -2987,7 +4010,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
req.version = std::string(m[5]);
req.method = std::string(m[1]);
req.target = std::string(m[2]);
- req.path = detail::decode_url(m[3]);
+ req.path = detail::decode_url(m[3], false);
// Parse query text
auto len = std::distance(m[4].first, m[4].second);
@@ -2999,7 +4022,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
return false;
}
-inline bool Server::write_response(Stream &strm, bool last_connection,
+inline bool Server::write_response(Stream &strm, bool close_connection,
const Request &req, Response &res) {
assert(res.status != -1);
@@ -3014,16 +4037,17 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
}
// Headers
- if (last_connection || req.get_header_value("Connection") == "close") {
+ if (close_connection || req.get_header_value("Connection") == "close") {
res.set_header("Connection", "close");
- }
-
- if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") {
- res.set_header("Connection", "Keep-Alive");
+ } else {
+ std::stringstream ss;
+ ss << "timeout=" << keep_alive_timeout_sec_
+ << ", max=" << keep_alive_max_count_;
+ res.set_header("Keep-Alive", ss.str());
}
if (!res.has_header("Content-Type") &&
- (!res.body.empty() || res.content_length > 0)) {
+ (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
res.set_header("Content-Type", "text/plain");
}
@@ -3047,18 +4071,20 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
"multipart/byteranges; boundary=" + boundary);
}
+ auto type = detail::encoding_type(req, res);
+
if (res.body.empty()) {
- if (res.content_length > 0) {
+ if (res.content_length_ > 0) {
size_t length = 0;
if (req.ranges.empty()) {
- length = res.content_length;
+ length = res.content_length_;
} else if (req.ranges.size() == 1) {
auto offsets =
- detail::get_range_offset_and_length(req, res.content_length, 0);
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
auto offset = offsets.first;
length = offsets.second;
auto content_range = detail::make_content_range_header_field(
- offset, length, res.content_length);
+ offset, length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
length = detail::get_multipart_ranges_data_length(req, res, boundary,
@@ -3066,8 +4092,15 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
}
res.set_header("Content-Length", std::to_string(length));
} else {
- if (res.content_provider) {
- res.set_header("Transfer-Encoding", "chunked");
+ if (res.content_provider_) {
+ if (res.is_chunked_content_provider) {
+ res.set_header("Transfer-Encoding", "chunked");
+ if (type == detail::EncodingType::Gzip) {
+ res.set_header("Content-Encoding", "gzip");
+ } else if (type == detail::EncodingType::Brotli) {
+ res.set_header("Content-Encoding", "br");
+ }
+ }
} else {
res.set_header("Content-Length", "0");
}
@@ -3089,16 +4122,35 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
detail::make_multipart_ranges_data(req, res, boundary, content_type);
}
+ if (type != detail::EncodingType::None) {
+ std::unique_ptr<detail::compressor> compressor;
+
+ if (type == detail::EncodingType::Gzip) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
- const auto &encodings = req.get_header_value("Accept-Encoding");
- if (encodings.find("gzip") != std::string::npos &&
- detail::can_compress(res.get_header_value("Content-Type"))) {
- if (detail::compress(res.body)) {
+ compressor = detail::make_unique<detail::gzip_compressor>();
res.set_header("Content-Encoding", "gzip");
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+ res.set_header("Content-Encoding", "brotli");
+#endif
+ }
+
+ if (compressor) {
+ std::string compressed;
+
+ if (!compressor->compress(res.body.data(), res.body.size(), true,
+ [&](const char *data, size_t data_len) {
+ compressed.append(data, data_len);
+ return true;
+ })) {
+ return false;
+ }
+
+ res.body.swap(compressed);
}
}
-#endif
auto length = std::to_string(res.body.size());
res.set_header("Content-Length", length);
@@ -3111,13 +4163,14 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
strm.write(data.data(), data.size());
// Body
+ auto ret = true;
if (req.method != "HEAD") {
if (!res.body.empty()) {
- if (!strm.write(res.body)) { return false; }
- } else if (res.content_provider) {
+ if (!strm.write(res.body)) { ret = false; }
+ } else if (res.content_provider_) {
if (!write_content_with_provider(strm, req, res, boundary,
content_type)) {
- return false;
+ ret = false;
}
}
}
@@ -3125,119 +4178,158 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
// Log
if (logger_) { logger_(req, res); }
- return true;
+ return ret;
}
inline bool
Server::write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type) {
- if (res.content_length) {
+ auto is_shutting_down = [this]() {
+ return this->svr_sock_ == INVALID_SOCKET;
+ };
+
+ if (res.content_length_ > 0) {
if (req.ranges.empty()) {
- if (detail::write_content(strm, res.content_provider, 0,
- res.content_length) < 0) {
+ if (detail::write_content(strm, res.content_provider_, 0,
+ res.content_length_, is_shutting_down) < 0) {
return false;
}
} else if (req.ranges.size() == 1) {
auto offsets =
- detail::get_range_offset_and_length(req, res.content_length, 0);
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
auto offset = offsets.first;
auto length = offsets.second;
- if (detail::write_content(strm, res.content_provider, offset, length) <
- 0) {
+ if (detail::write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down) < 0) {
return false;
}
} else {
- if (!detail::write_multipart_ranges_data(strm, req, res, boundary,
- content_type)) {
+ if (!detail::write_multipart_ranges_data(
+ strm, req, res, boundary, content_type, is_shutting_down)) {
return false;
}
}
} else {
- auto is_shutting_down = [this]() {
- return this->svr_sock_ == INVALID_SOCKET;
- };
- if (detail::write_content_chunked(strm, res.content_provider,
- is_shutting_down) < 0) {
- return false;
+ if (res.is_chunked_content_provider) {
+ auto type = detail::encoding_type(req, res);
+
+ std::unique_ptr<detail::compressor> compressor;
+ if (type == detail::EncodingType::Gzip) {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ compressor = detail::make_unique<detail::gzip_compressor>();
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+#endif
+ } else {
+ compressor = detail::make_unique<detail::nocompressor>();
+ }
+ assert(compressor != nullptr);
+
+ if (detail::write_content_chunked(strm, res.content_provider_,
+ is_shutting_down, *compressor) < 0) {
+ return false;
+ }
+ } else {
+ if (detail::write_content_without_length(strm, res.content_provider_,
+ is_shutting_down) < 0) {
+ return false;
+ }
}
}
return true;
}
-inline bool Server::read_content(Stream &strm, bool last_connection,
- Request &req, Response &res) {
+inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
MultipartFormDataMap::iterator cur;
- auto ret = read_content_core(
- strm, last_connection, req, res,
- // Regular
- [&](const char *buf, size_t n) {
- if (req.body.size() + n > req.body.max_size()) { return false; }
- req.body.append(buf, n);
- return true;
- },
- // Multipart
- [&](const MultipartFormData &file) {
- cur = req.files.emplace(file.name, file);
- return true;
- },
- [&](const char *buf, size_t n) {
- auto &content = cur->second.content;
- if (content.size() + n > content.max_size()) { return false; }
- content.append(buf, n);
- return true;
- });
-
- const auto &content_type = req.get_header_value("Content-Type");
- if (!content_type.find("application/x-www-form-urlencoded")) {
- detail::parse_query_text(req.body, req.params);
+ if (read_content_core(
+ strm, req, res,
+ // Regular
+ [&](const char *buf, size_t n) {
+ if (req.body.size() + n > req.body.max_size()) { return false; }
+ req.body.append(buf, n);
+ return true;
+ },
+ // Multipart
+ [&](const MultipartFormData &file) {
+ cur = req.files.emplace(file.name, file);
+ return true;
+ },
+ [&](const char *buf, size_t n) {
+ auto &content = cur->second.content;
+ if (content.size() + n > content.max_size()) { return false; }
+ content.append(buf, n);
+ return true;
+ })) {
+ const auto &content_type = req.get_header_value("Content-Type");
+ if (!content_type.find("application/x-www-form-urlencoded")) {
+ detail::parse_query_text(req.body, req.params);
+ }
+ return true;
}
-
- return ret;
+ return false;
}
inline bool Server::read_content_with_content_receiver(
- Stream &strm, bool last_connection, Request &req, Response &res,
- ContentReceiver receiver, MultipartContentHeader multipart_header,
+ Stream &strm, Request &req, Response &res, ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver) {
- return read_content_core(strm, last_connection, req, res, receiver,
- multipart_header, multipart_receiver);
+ return read_content_core(strm, req, res, std::move(receiver),
+ std::move(multipart_header),
+ std::move(multipart_receiver));
}
-inline bool Server::read_content_core(Stream &strm, bool last_connection,
- Request &req, Response &res,
+inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver) {
detail::MultipartFormDataParser multipart_form_data_parser;
- ContentReceiver out;
+ ContentReceiverWithProgress out;
if (req.is_multipart_form_data()) {
const auto &content_type = req.get_header_value("Content-Type");
std::string boundary;
if (!detail::parse_multipart_boundary(content_type, boundary)) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return false;
}
- multipart_form_data_parser.set_boundary(boundary);
- out = [&](const char *buf, size_t n) {
+ multipart_form_data_parser.set_boundary(std::move(boundary));
+ out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
+ /* For debug
+ size_t pos = 0;
+ while (pos < n) {
+ auto read_size = std::min<size_t>(1, n - pos);
+ auto ret = multipart_form_data_parser.parse(
+ buf + pos, read_size, multipart_receiver, mulitpart_header);
+ if (!ret) { return false; }
+ pos += read_size;
+ }
+ return true;
+ */
return multipart_form_data_parser.parse(buf, n, multipart_receiver,
mulitpart_header);
};
} else {
- out = receiver;
+ out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) { return receiver(buf, n); };
}
- if (!detail::read_content(strm, req, payload_max_length_, res.status,
- Progress(), out)) {
- return write_response(strm, last_connection, req, res);
+ if (req.method == "DELETE" && !req.has_header("Content-Length")) {
+ return true;
+ }
+
+ if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
+ out, true)) {
+ return false;
}
if (req.is_multipart_form_data()) {
if (!multipart_form_data_parser.is_valid()) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return false;
}
}
@@ -3246,15 +4338,12 @@ inline bool Server::read_content_core(Stream &strm, bool last_connection,
inline bool Server::handle_file_request(Request &req, Response &res,
bool head) {
- for (const auto &kv : base_dirs_) {
- const auto &mount_point = kv.first;
- const auto &base_dir = kv.second;
-
+ for (const auto &entry : base_dirs_) {
// Prefix match
- if (!req.path.find(mount_point)) {
- std::string sub_path = "/" + req.path.substr(mount_point.size());
+ if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
+ std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
if (detail::is_valid_path(sub_path)) {
- auto path = base_dir + sub_path;
+ auto path = entry.base_dir + sub_path;
if (path.back() == '/') { path += "index.html"; }
if (detail::is_file(path)) {
@@ -3262,6 +4351,9 @@ inline bool Server::handle_file_request(Request &req, Response &res,
auto type =
detail::find_content_type(path, file_extension_and_mimetype_map_);
if (type) { res.set_header("Content-Type", type); }
+ for (const auto &kv : entry.headers) {
+ res.set_header(kv.first.c_str(), kv.second);
+ }
res.status = 200;
if (!head && file_request_handler_) {
file_request_handler_(req, res);
@@ -3274,40 +4366,39 @@ inline bool Server::handle_file_request(Request &req, Response &res,
return false;
}
-inline socket_t Server::create_server_socket(const char *host, int port,
- int socket_flags) const {
+inline socket_t
+Server::create_server_socket(const char *host, int port, int socket_flags,
+ SocketOptions socket_options) const {
return detail::create_socket(
- host, port,
+ host, port, socket_flags, tcp_nodelay_, std::move(socket_options),
[](socket_t sock, struct addrinfo &ai) -> bool {
- if (::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+ if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
}
if (::listen(sock, 5)) { // Listen through 5 channels
return false;
}
return true;
- },
- socket_flags);
+ });
}
inline int Server::bind_internal(const char *host, int port, int socket_flags) {
if (!is_valid()) { return -1; }
- svr_sock_ = create_server_socket(host, port, socket_flags);
+ svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
if (svr_sock_ == INVALID_SOCKET) { return -1; }
if (port == 0) {
- struct sockaddr_storage address;
- socklen_t len = sizeof(address);
- if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
- &len) == -1) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+ if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len) == -1) {
return -1;
}
- if (address.ss_family == AF_INET) {
- return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
- } else if (address.ss_family == AF_INET6) {
- return ntohs(
- reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
+ if (addr.ss_family == AF_INET) {
+ return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
} else {
return -1;
}
@@ -3323,18 +4414,19 @@ inline bool Server::listen_internal() {
{
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
- for (;;) {
- if (svr_sock_ == INVALID_SOCKET) {
- // The server socket was closed by 'stop' method.
- break;
- }
-
- auto val = detail::select_read(svr_sock_, 0, 100000);
-
- if (val == 0) { // Timeout
- continue;
+ while (svr_sock_ != INVALID_SOCKET) {
+#ifndef _WIN32
+ if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
+#endif
+ auto val = detail::select_read(svr_sock_, idle_interval_sec_,
+ idle_interval_usec_);
+ if (val == 0) { // Timeout
+ task_queue->on_idle();
+ continue;
+ }
+#ifndef _WIN32
}
-
+#endif
socket_t sock = accept(svr_sock_, nullptr, nullptr);
if (sock == INVALID_SOCKET) {
@@ -3353,7 +4445,11 @@ inline bool Server::listen_internal() {
break;
}
+#if __cplusplus > 201703L
+ task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
+#else
task_queue->enqueue([=]() { process_and_close_socket(sock); });
+#endif
}
task_queue->shutdown();
@@ -3363,8 +4459,7 @@ inline bool Server::listen_internal() {
return ret;
}
-inline bool Server::routing(Request &req, Response &res, Stream &strm,
- bool last_connection) {
+inline bool Server::routing(Request &req, Response &res, Stream &strm) {
// File handler
bool is_head_request = req.method == "HEAD";
if ((req.method == "GET" || is_head_request) &&
@@ -3378,33 +4473,43 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
ContentReader reader(
[&](ContentReceiver receiver) {
return read_content_with_content_receiver(
- strm, last_connection, req, res, receiver, nullptr, nullptr);
+ strm, req, res, std::move(receiver), nullptr, nullptr);
},
[&](MultipartContentHeader header, ContentReceiver receiver) {
- return read_content_with_content_receiver(
- strm, last_connection, req, res, nullptr, header, receiver);
+ return read_content_with_content_receiver(strm, req, res, nullptr,
+ std::move(header),
+ std::move(receiver));
});
if (req.method == "POST") {
if (dispatch_request_for_content_reader(
- req, res, reader, post_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ post_handlers_for_content_reader_)) {
return true;
}
} else if (req.method == "PUT") {
if (dispatch_request_for_content_reader(
- req, res, reader, put_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ put_handlers_for_content_reader_)) {
return true;
}
} else if (req.method == "PATCH") {
if (dispatch_request_for_content_reader(
- req, res, reader, patch_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ patch_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "DELETE") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ delete_handlers_for_content_reader_)) {
return true;
}
}
}
// Read content into `req.body`
- if (!read_content(strm, last_connection, req, res)) { return false; }
+ if (!read_content(strm, req, res)) { return false; }
}
// Regular handler
@@ -3427,22 +4532,30 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
}
inline bool Server::dispatch_request(Request &req, Response &res,
- Handlers &handlers) {
- for (const auto &x : handlers) {
- const auto &pattern = x.first;
- const auto &handler = x.second;
-
- if (std::regex_match(req.path, req.matches, pattern)) {
- handler(req, res);
- return true;
+ const Handlers &handlers) {
+ try {
+ for (const auto &x : handlers) {
+ const auto &pattern = x.first;
+ const auto &handler = x.second;
+
+ if (std::regex_match(req.path, req.matches, pattern)) {
+ handler(req, res);
+ return true;
+ }
}
+ } catch (const std::exception &ex) {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", ex.what());
+ } catch (...) {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", "UNKNOWN");
}
return false;
}
inline bool Server::dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
- HandlersForContentReader &handlers) {
+ const HandlersForContentReader &handlers) {
for (const auto &x : handlers) {
const auto &pattern = x.first;
const auto &handler = x.second;
@@ -3456,8 +4569,8 @@ inline bool Server::dispatch_request_for_content_reader(
}
inline bool
-Server::process_request(Stream &strm, bool last_connection,
- bool &connection_close,
+Server::process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
const std::function<void(Request &)> &setup_request) {
std::array<char, 2048> buf{};
@@ -3476,31 +4589,34 @@ Server::process_request(Stream &strm, bool last_connection,
Headers dummy;
detail::read_headers(strm, dummy);
res.status = 414;
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
// Request line and headers
if (!parse_request_line(line_reader.ptr(), req) ||
!detail::read_headers(strm, req.headers)) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
if (req.get_header_value("Connection") == "close") {
- connection_close = true;
+ connection_closed = true;
}
if (req.version == "HTTP/1.0" &&
req.get_header_value("Connection") != "Keep-Alive") {
- connection_close = true;
+ connection_closed = true;
}
- req.set_header("REMOTE_ADDR", strm.get_remote_addr());
+ strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
+ req.set_header("REMOTE_ADDR", req.remote_addr);
+ req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
- // TODO: error
+ res.status = 416;
+ return write_response(strm, close_connection, req, res);
}
}
@@ -3517,136 +4633,278 @@ Server::process_request(Stream &strm, bool last_connection,
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
detail::status_message(status));
break;
- default: return write_response(strm, last_connection, req, res);
+ default: return write_response(strm, close_connection, req, res);
}
}
// Rounting
- if (routing(req, res, strm, last_connection)) {
+ if (routing(req, res, strm)) {
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
} else {
if (res.status == -1) { res.status = 404; }
}
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
inline bool Server::is_valid() const { return true; }
inline bool Server::process_and_close_socket(socket_t sock) {
- return detail::process_and_close_socket(
- false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
- [this](Stream &strm, bool last_connection, bool &connection_close) {
- return process_request(strm, last_connection, connection_close,
+ auto ret = detail::process_server_socket(
+ sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
+ read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
+ [this](Stream &strm, bool close_connection, bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
nullptr);
});
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
}
// HTTP client implementation
-inline Client::Client(const std::string &host, int port,
- const std::string &client_cert_path,
- const std::string &client_key_path)
- : host_(host), port_(port),
+inline ClientImpl::ClientImpl(const std::string &host)
+ : ClientImpl(host, 80, std::string(), std::string()) {}
+
+inline ClientImpl::ClientImpl(const std::string &host, int port)
+ : ClientImpl(host, port, std::string(), std::string()) {}
+
+inline ClientImpl::ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : error_(Error::Success), host_(host), port_(port),
host_and_port_(host_ + ":" + std::to_string(port_)),
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
-inline Client::~Client() {}
+inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
+
+inline bool ClientImpl::is_valid() const { return true; }
-inline bool Client::is_valid() const { return true; }
+inline Error ClientImpl::get_last_error() const { return error_; }
-inline socket_t Client::create_client_socket() const {
- if (!proxy_host_.empty()) {
- return detail::create_client_socket(proxy_host_.c_str(), proxy_port_,
- timeout_sec_, interface_);
+inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
+ client_cert_path_ = rhs.client_cert_path_;
+ client_key_path_ = rhs.client_key_path_;
+ connection_timeout_sec_ = rhs.connection_timeout_sec_;
+ read_timeout_sec_ = rhs.read_timeout_sec_;
+ read_timeout_usec_ = rhs.read_timeout_usec_;
+ write_timeout_sec_ = rhs.write_timeout_sec_;
+ write_timeout_usec_ = rhs.write_timeout_usec_;
+ basic_auth_username_ = rhs.basic_auth_username_;
+ basic_auth_password_ = rhs.basic_auth_password_;
+ bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ digest_auth_username_ = rhs.digest_auth_username_;
+ digest_auth_password_ = rhs.digest_auth_password_;
+#endif
+ keep_alive_ = rhs.keep_alive_;
+ follow_location_ = rhs.follow_location_;
+ tcp_nodelay_ = rhs.tcp_nodelay_;
+ socket_options_ = rhs.socket_options_;
+ compress_ = rhs.compress_;
+ decompress_ = rhs.decompress_;
+ interface_ = rhs.interface_;
+ proxy_host_ = rhs.proxy_host_;
+ proxy_port_ = rhs.proxy_port_;
+ proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
+ proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
+ proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
+ proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ server_certificate_verification_ = rhs.server_certificate_verification_;
+#endif
+ logger_ = rhs.logger_;
+}
+
+inline socket_t ClientImpl::create_client_socket() const {
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ return detail::create_client_socket(
+ proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_,
+ connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
}
- return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
- interface_);
+ return detail::create_client_socket(
+ host_.c_str(), port_, tcp_nodelay_, socket_options_,
+ connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
+}
+
+inline bool ClientImpl::create_and_connect_socket(Socket &socket) {
+ auto sock = create_client_socket();
+ if (sock == INVALID_SOCKET) { return false; }
+ socket.sock = sock;
+ return true;
+}
+
+inline void ClientImpl::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+ (void)socket;
+ (void)shutdown_gracefully;
+ //If there are any requests in flight from threads other than us, then it's
+ //a thread-unsafe race because individual ssl* objects are not thread-safe.
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+}
+
+inline void ClientImpl::shutdown_socket(Socket &socket) {
+ if (socket.sock == INVALID_SOCKET)
+ return;
+ detail::shutdown_socket(socket.sock);
+}
+
+inline void ClientImpl::close_socket(Socket &socket) {
+ // If there are requests in flight in another thread, usually closing
+ // the socket will be fine and they will simply receive an error when
+ // using the closed socket, but it is still a bug since rarely the OS
+ // may reassign the socket id to be used for a new socket, and then
+ // suddenly they will be operating on a live socket that is different
+ // than the one they intended!
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+ // It is also a bug if this happens while SSL is still active
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ assert(socket.ssl == nullptr);
+#endif
+ if (socket.sock == INVALID_SOCKET)
+ return;
+ detail::close_socket(socket.sock);
+ socket.sock = INVALID_SOCKET;
}
-inline bool Client::read_response_line(Stream &strm, Response &res) {
+inline void ClientImpl::lock_socket_and_shutdown_and_close() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+}
+
+inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
std::array<char, 2048> buf;
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
if (!line_reader.getline()) { return false; }
- const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
+ const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
std::cmatch m;
- if (std::regex_match(line_reader.ptr(), m, re)) {
+ if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
+ res.version = std::string(m[1]);
+ res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
+
+ // Ignore '100 Continue'
+ while (res.status == 100) {
+ if (!line_reader.getline()) { return false; } // CRLF
+ if (!line_reader.getline()) { return false; } // next response line
+
+ if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
res.version = std::string(m[1]);
res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
}
return true;
}
-inline bool Client::send(const Request &req, Response &res) {
- auto sock = create_client_socket();
- if (sock == INVALID_SOCKET) { return false; }
+inline bool ClientImpl::send(const Request &req, Response &res) {
+ std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
+
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ // Set this to false immediately - if it ever gets set to true by the end of the
+ // request, we know another thread instructed us to close the socket.
+ socket_should_be_closed_when_request_is_done_ = false;
+
+ auto is_alive = false;
+ if (socket_.is_open()) {
+ is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
+ if (!is_alive) {
+ // Attempt to avoid sigpipe by shutting down nongracefully if it seems like
+ // the other side has already closed the connection
+ // Also, there cannot be any requests in flight from other threads since we locked
+ // request_mutex_, so safe to close everything immediately
+ const bool shutdown_gracefully = false;
+ shutdown_ssl(socket_, shutdown_gracefully);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+ }
+
+ if (!is_alive) {
+ if (!create_and_connect_socket(socket_)) { return false; }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (is_ssl() && !proxy_host_.empty()) {
- bool error;
- if (!connect(sock, res, error)) { return error; }
- }
+ // TODO: refactoring
+ if (is_ssl()) {
+ auto &scli = static_cast<SSLClient &>(*this);
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ bool success = false;
+ if (!scli.connect_with_proxy(socket_, res, success)) {
+ return success;
+ }
+ }
+
+ if (!scli.initialize_ssl(socket_)) { return false; }
+ }
#endif
+ }
- return process_and_close_socket(
- sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
- return handle_request(strm, req, res, last_connection,
- connection_close);
- });
-}
+ // Mark the current socket as being in use so that it cannot be closed by anyone
+ // else while this request is ongoing, even though we will be releasing the mutex.
+ if (socket_requests_in_flight_ > 1) {
+ assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
+ }
+ socket_requests_in_flight_ += 1;
+ socket_requests_are_from_thread_ = std::this_thread::get_id();
+ }
-inline bool Client::send(const std::vector<Request> &requests,
- std::vector<Response> &responses) {
- size_t i = 0;
- while (i < requests.size()) {
- auto sock = create_client_socket();
- if (sock == INVALID_SOCKET) { return false; }
+ auto close_connection = !keep_alive_;
+ auto ret = process_socket(socket_, [&](Stream &strm) {
+ return handle_request(strm, req, res, close_connection);
+ });
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (is_ssl() && !proxy_host_.empty()) {
- Response res;
- bool error;
- if (!connect(sock, res, error)) { return false; }
+ //Briefly lock mutex in order to mark that a request is no longer ongoing
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ socket_requests_in_flight_ -= 1;
+ if (socket_requests_in_flight_ <= 0) {
+ assert(socket_requests_in_flight_ == 0);
+ socket_requests_are_from_thread_ = std::thread::id();
}
-#endif
- if (!process_and_close_socket(sock, requests.size() - i,
- [&](Stream &strm, bool last_connection,
- bool &connection_close) -> bool {
- auto &req = requests[i++];
- auto res = Response();
- auto ret = handle_request(strm, req, res,
- last_connection,
- connection_close);
- if (ret) {
- responses.emplace_back(std::move(res));
- }
- return ret;
- })) {
- return false;
+ if (socket_should_be_closed_when_request_is_done_ ||
+ close_connection ||
+ !ret ) {
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
}
}
- return true;
+ if (!ret) {
+ if (error_ == Error::Success) { error_ = Error::Unknown; }
+ }
+
+ return ret;
}
-inline bool Client::handle_request(Stream &strm, const Request &req,
- Response &res, bool last_connection,
- bool &connection_close) {
- if (req.path.empty()) { return false; }
+inline bool ClientImpl::handle_request(Stream &strm, const Request &req,
+ Response &res, bool close_connection) {
+ if (req.path.empty()) {
+ error_ = Error::Connection;
+ return false;
+ }
bool ret;
- if (!is_ssl() && !proxy_host_.empty()) {
+ if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
auto req2 = req;
req2.path = "http://" + host_and_port_ + req.path;
- ret = process_request(strm, req2, res, last_connection, connection_close);
+ ret = process_request(strm, req2, res, close_connection);
} else {
- ret = process_request(strm, req, res, last_connection, connection_close);
+ ret = process_request(strm, req, res, close_connection);
}
if (!ret) { return false; }
@@ -3656,7 +4914,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (res.status == 401 || res.status == 407) {
+ if ((res.status == 401 || res.status == 407) &&
+ req.authorization_count_ < 5) {
auto is_proxy = res.status == 407;
const auto &username =
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
@@ -3665,12 +4924,14 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
if (!username.empty() && !password.empty()) {
std::map<std::string, std::string> auth;
- if (parse_www_authenticate(res, auth, is_proxy)) {
+ if (detail::parse_www_authenticate(res, auth, is_proxy)) {
Request new_req = req;
- auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
+ new_req.authorization_count_ += 1;
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
new_req.headers.erase(key);
- new_req.headers.insert(make_digest_authentication_header(
- req, auth, 1, random_string(10), username, password, is_proxy));
+ new_req.headers.insert(detail::make_digest_authentication_header(
+ req, auth, new_req.authorization_count_, detail::random_string(10),
+ username, password, is_proxy));
Response new_res;
@@ -3684,102 +4945,64 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
return ret;
}
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline bool Client::connect(socket_t sock, Response &res, bool &error) {
- error = true;
- Response res2;
-
- if (!detail::process_socket(
- true, sock, 1, read_timeout_sec_, read_timeout_usec_,
- [&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
- Request req2;
- req2.method = "CONNECT";
- req2.path = host_and_port_;
- return process_request(strm, req2, res2, false, connection_close);
- })) {
- detail::close_socket(sock);
- error = false;
+inline bool ClientImpl::redirect(const Request &req, Response &res) {
+ if (req.redirect_count == 0) {
+ error_ = Error::ExceedRedirectCount;
return false;
}
- if (res2.status == 407) {
- if (!proxy_digest_auth_username_.empty() &&
- !proxy_digest_auth_password_.empty()) {
- std::map<std::string, std::string> auth;
- if (parse_www_authenticate(res2, auth, true)) {
- Response res3;
- if (!detail::process_socket(
- true, sock, 1, read_timeout_sec_, read_timeout_usec_,
- [&](Stream &strm, bool /*last_connection*/,
- bool &connection_close) {
- Request req3;
- req3.method = "CONNECT";
- req3.path = host_and_port_;
- req3.headers.insert(make_digest_authentication_header(
- req3, auth, 1, random_string(10),
- proxy_digest_auth_username_, proxy_digest_auth_password_,
- true));
- return process_request(strm, req3, res3, false,
- connection_close);
- })) {
- detail::close_socket(sock);
- error = false;
- return false;
- }
- }
- } else {
- res = res2;
- return false;
- }
- }
-
- return true;
-}
-#endif
-
-inline bool Client::redirect(const Request &req, Response &res) {
- if (req.redirect_count == 0) { return false; }
-
- auto location = res.get_header_value("location");
+ auto location = detail::decode_url(res.get_header_value("location"), true);
if (location.empty()) { return false; }
const static std::regex re(
- R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+ R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
std::smatch m;
- if (!regex_match(location, m, re)) { return false; }
+ if (!std::regex_match(location, m, re)) { return false; }
auto scheme = is_ssl() ? "https" : "http";
auto next_scheme = m[1].str();
auto next_host = m[2].str();
- auto next_path = m[3].str();
- if (next_scheme.empty()) { next_scheme = scheme; }
+ auto port_str = m[3].str();
+ auto next_path = m[4].str();
+
+ auto next_port = port_;
+ if (!port_str.empty()) {
+ next_port = std::stoi(port_str);
+ } else if (!next_scheme.empty()) {
+ next_port = next_scheme == "https" ? 443 : 80;
+ }
+
if (next_scheme.empty()) { next_scheme = scheme; }
if (next_host.empty()) { next_host = host_; }
if (next_path.empty()) { next_path = "/"; }
- if (next_scheme == scheme && next_host == host_) {
+ if (next_scheme == scheme && next_host == host_ && next_port == port_) {
return detail::redirect(*this, req, res, next_path);
} else {
if (next_scheme == "https") {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- SSLClient cli(next_host.c_str());
+ SSLClient cli(next_host.c_str(), next_port);
cli.copy_settings(*this);
- return detail::redirect(cli, req, res, next_path);
+ auto ret = detail::redirect(cli, req, res, next_path);
+ if (!ret) { error_ = cli.get_last_error(); }
+ return ret;
#else
return false;
#endif
} else {
- Client cli(next_host.c_str());
+ ClientImpl cli(next_host.c_str(), next_port);
cli.copy_settings(*this);
- return detail::redirect(cli, req, res, next_path);
+ auto ret = detail::redirect(cli, req, res, next_path);
+ if (!ret) { error_ = cli.get_last_error(); }
+ return ret;
}
}
}
-inline bool Client::write_request(Stream &strm, const Request &req,
- bool last_connection) {
+inline bool ClientImpl::write_request(Stream &strm, const Request &req,
+ bool close_connection) {
detail::BufferStream bstrm;
// Request line
@@ -3789,7 +5012,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
// Additonal headers
Headers headers;
- if (last_connection) { headers.emplace("Connection", "close"); }
+ if (close_connection) { headers.emplace("Connection", "close"); }
if (!req.has_header("Host")) {
if (is_ssl()) {
@@ -3810,7 +5033,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); }
if (!req.has_header("User-Agent")) {
- headers.emplace("User-Agent", "cpp-httplib/0.5");
+ headers.emplace("User-Agent", "cpp-httplib/0.7");
}
if (req.body.empty()) {
@@ -3818,7 +5041,10 @@ inline bool Client::write_request(Stream &strm, const Request &req,
auto length = std::to_string(req.content_length);
headers.emplace("Content-Length", length);
} else {
- headers.emplace("Content-Length", "0");
+ if (req.method == "POST" || req.method == "PUT" ||
+ req.method == "PATCH") {
+ headers.emplace("Content-Length", "0");
+ }
}
} else {
if (!req.has_header("Content-Type")) {
@@ -3831,7 +5057,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
}
}
- if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) {
+ if (!basic_auth_password_.empty()) {
headers.insert(make_basic_authentication_header(
basic_auth_username_, basic_auth_password_, false));
}
@@ -3842,11 +5068,24 @@ inline bool Client::write_request(Stream &strm, const Request &req,
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
}
+ if (!bearer_token_auth_token_.empty()) {
+ headers.insert(make_bearer_token_authentication_header(
+ bearer_token_auth_token_, false));
+ }
+
+ if (!proxy_bearer_token_auth_token_.empty()) {
+ headers.insert(make_bearer_token_authentication_header(
+ proxy_bearer_token_auth_token_, true));
+ }
+
detail::write_headers(bstrm, req, headers);
// Flush buffer
auto &data = bstrm.get_buffer();
- strm.write(data.data(), data.size());
+ if (!detail::write_data(strm, data.data(), data.size())) {
+ error_ = Error::Write;
+ return false;
+ }
// Body
if (req.body.empty()) {
@@ -3854,282 +5093,370 @@ inline bool Client::write_request(Stream &strm, const Request &req,
size_t offset = 0;
size_t end_offset = req.content_length;
+ bool ok = true;
+
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
- auto written_length = strm.write(d, l);
- offset += written_length;
+ if (ok) {
+ if (detail::write_data(strm, d, l)) {
+ offset += l;
+ } else {
+ ok = false;
+ }
+ }
};
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset) {
- req.content_provider(offset, end_offset - offset, data_sink);
+ if (!req.content_provider(offset, end_offset - offset, data_sink)) {
+ error_ = Error::Canceled;
+ return false;
+ }
+ if (!ok) {
+ error_ = Error::Write;
+ return false;
+ }
}
}
} else {
- strm.write(req.body);
+ return detail::write_data(strm, req.body.data(), req.body.size());
}
return true;
}
-inline std::shared_ptr<Response> Client::send_with_content_provider(
+inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length,
ContentProvider content_provider, const char *content_type) {
+
Request req;
req.method = method;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- req.headers.emplace("Content-Type", content_type);
+ if (content_type) { req.headers.emplace("Content-Type", content_type); }
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (compress_) {
+ detail::gzip_compressor compressor;
+
if (content_provider) {
+ auto ok = true;
size_t offset = 0;
DataSink data_sink;
data_sink.write = [&](const char *data, size_t data_len) {
- req.body.append(data, data_len);
- offset += data_len;
+ if (ok) {
+ auto last = offset + data_len == content_length;
+
+ auto ret = compressor.compress(
+ data, data_len, last, [&](const char *data, size_t data_len) {
+ req.body.append(data, data_len);
+ return true;
+ });
+
+ if (ret) {
+ offset += data_len;
+ } else {
+ ok = false;
+ }
+ }
};
- data_sink.is_writable = [&](void) { return true; };
+ data_sink.is_writable = [&](void) { return ok && true; };
- while (offset < content_length) {
- content_provider(offset, content_length - offset, data_sink);
+ while (ok && offset < content_length) {
+ if (!content_provider(offset, content_length - offset, data_sink)) {
+ error_ = Error::Canceled;
+ return nullptr;
+ }
}
} else {
- req.body = body;
+ if (!compressor.compress(body.data(), body.size(), true,
+ [&](const char *data, size_t data_len) {
+ req.body.append(data, data_len);
+ return true;
+ })) {
+ return nullptr;
+ }
}
- if (!detail::compress(req.body)) { return nullptr; }
req.headers.emplace("Content-Encoding", "gzip");
} else
#endif
{
if (content_provider) {
req.content_length = content_length;
- req.content_provider = content_provider;
+ req.content_provider = std::move(content_provider);
} else {
req.body = body;
}
}
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
- return send(req, *res) ? res : nullptr;
+ return send(req, *res) ? std::move(res) : nullptr;
}
-inline bool Client::process_request(Stream &strm, const Request &req,
- Response &res, bool last_connection,
- bool &connection_close) {
+inline bool ClientImpl::process_request(Stream &strm, const Request &req,
+ Response &res, bool close_connection) {
// Send request
- if (!write_request(strm, req, last_connection)) { return false; }
+ if (!write_request(strm, req, close_connection)) { return false; }
// Receive response and headers
if (!read_response_line(strm, res) ||
!detail::read_headers(strm, res.headers)) {
+ error_ = Error::Read;
return false;
}
- if (res.get_header_value("Connection") == "close" ||
- res.version == "HTTP/1.0") {
- connection_close = true;
- }
-
if (req.response_handler) {
- if (!req.response_handler(res)) { return false; }
+ if (!req.response_handler(res)) {
+ error_ = Error::Canceled;
+ return false;
+ }
}
// Body
if (req.method != "HEAD" && req.method != "CONNECT") {
- ContentReceiver out = [&](const char *buf, size_t n) {
- if (res.body.size() + n > res.body.max_size()) { return false; }
- res.body.append(buf, n);
- return true;
+ auto out =
+ req.content_receiver
+ ? static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t off, uint64_t len) {
+ auto ret = req.content_receiver(buf, n, off, len);
+ if (!ret) { error_ = Error::Canceled; }
+ return ret;
+ })
+ : static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) {
+ if (res.body.size() + n > res.body.max_size()) {
+ return false;
+ }
+ res.body.append(buf, n);
+ return true;
+ });
+
+ auto progress = [&](uint64_t current, uint64_t total) {
+ if (!req.progress) { return true; }
+ auto ret = req.progress(current, total);
+ if (!ret) { error_ = Error::Canceled; }
+ return ret;
};
- if (req.content_receiver) {
- out = [&](const char *buf, size_t n) {
- return req.content_receiver(buf, n);
- };
- }
-
int dummy_status;
- if (!detail::read_content(strm, res, std::numeric_limits<size_t>::max(),
- dummy_status, req.progress, out)) {
+ if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
+ dummy_status, std::move(progress), std::move(out),
+ decompress_)) {
+ if (error_ != Error::Canceled) { error_ = Error::Read; }
return false;
}
}
+ if (res.get_header_value("Connection") == "close" ||
+ (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
+ // TODO this requires a not-entirely-obvious chain of calls to be correct
+ // for this to be safe. Maybe a code refactor (such as moving this out to
+ // the send function and getting rid of the recursiveness of the mutex)
+ // could make this more obvious.
+
+ // This is safe to call because process_request is only called by handle_request
+ // which is only called by send, which locks the request mutex during the process.
+ // It would be a bug to call it from a different thread since it's a thread-safety
+ // issue to do these things to the socket if another thread is using the socket.
+ lock_socket_and_shutdown_and_close();
+ }
+
// Log
if (logger_) { logger_(req, res); }
return true;
}
-inline bool Client::process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback) {
- request_count = std::min(request_count, keep_alive_max_count_);
- return detail::process_and_close_socket(true, sock, request_count,
- read_timeout_sec_, read_timeout_usec_,
- callback);
+inline bool
+ClientImpl::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ return detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_, std::move(callback));
}
-inline bool Client::is_ssl() const { return false; }
+inline bool ClientImpl::is_ssl() const { return false; }
-inline std::shared_ptr<Response> Client::Get(const char *path) {
+inline Result ClientImpl::Get(const char *path) {
return Get(path, Headers(), Progress());
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, Progress progress) {
return Get(path, Headers(), std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers) {
return Get(path, headers, Progress());
}
-inline std::shared_ptr<Response>
-Client::Get(const char *path, const Headers &headers, Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ Progress progress) {
Request req;
req.method = "GET";
req.path = path;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.progress = std::move(progress);
- auto res = std::make_shared<Response>();
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- ContentReceiver content_receiver) {
- return Get(path, Headers(), nullptr, std::move(content_receiver), Progress());
+inline Result ClientImpl::Get(const char *path,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path,
+ ContentReceiver content_receiver,
+ Progress progress) {
return Get(path, Headers(), nullptr, std::move(content_receiver),
std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ContentReceiver content_receiver) {
- return Get(path, headers, nullptr, std::move(content_receiver), Progress());
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver,
+ Progress progress) {
return Get(path, headers, nullptr, std::move(content_receiver),
std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver) {
- return Get(path, headers, std::move(response_handler), content_receiver,
- Progress());
+inline Result ClientImpl::Get(const char *path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+inline Result ClientImpl::Get(const char *path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
Request req;
req.method = "GET";
req.path = path;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.response_handler = std::move(response_handler);
- req.content_receiver = std::move(content_receiver);
+ req.content_receiver =
+ [content_receiver](const char *data, size_t data_length,
+ uint64_t /*offset*/, uint64_t /*total_length*/) {
+ return content_receiver(data, data_length);
+ };
req.progress = std::move(progress);
- auto res = std::make_shared<Response>();
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Head(const char *path) {
+inline Result ClientImpl::Head(const char *path) {
return Head(path, Headers());
}
-inline std::shared_ptr<Response> Client::Head(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Head(const char *path, const Headers &headers) {
Request req;
req.method = "HEAD";
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
+}
- return send(req, *res) ? res : nullptr;
+inline Result ClientImpl::Post(const char *path) {
+ return Post(path, std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Post(const char *path, const std::string &body,
+ const char *content_type) {
return Post(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("POST", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr,
+ content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const Params &params) {
+inline Result ClientImpl::Post(const char *path, const Params &params) {
return Post(path, Headers(), params);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Post(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Post(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Post(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("POST", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "POST", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers, const Params &params) {
- std::string query;
- for (auto it = params.begin(); it != params.end(); ++it) {
- if (it != params.begin()) { query += "&"; }
- query += it->first;
- query += "=";
- query += detail::encode_url(it->second);
- }
-
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const Params &params) {
+ auto query = detail::params_to_query_str(params);
return Post(path, headers, query, "application/x-www-form-urlencoded");
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const MultipartFormDataItems &items) {
+inline Result ClientImpl::Post(const char *path,
+ const MultipartFormDataItems &items) {
return Post(path, Headers(), items);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items) {
- auto boundary = detail::make_multipart_data_boundary();
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return Post(path, headers, items, detail::make_multipart_data_boundary());
+}
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ for (size_t i = 0; i < boundary.size(); i++) {
+ char c = boundary[i];
+ if (!std::isalnum(c) && c != '-' && c != '_') {
+ error_ = Error::UnsupportedMultipartBoundaryChars;
+ return Result{nullptr, error_};
+ }
+ }
std::string body;
@@ -4153,182 +5480,240 @@ Client::Post(const char *path, const Headers &headers,
return Post(path, headers, body, content_type.c_str());
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Put(const char *path) {
+ return Put(path, std::string(), nullptr);
+}
+
+inline Result ClientImpl::Put(const char *path, const std::string &body,
+ const char *content_type) {
return Put(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr,
+ content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Put(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Put(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Put(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Put(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("PUT", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "PUT", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const Params &params) {
+inline Result ClientImpl::Put(const char *path, const Params &params) {
return Put(path, Headers(), params);
}
-inline std::shared_ptr<Response>
-Client::Put(const char *path, const Headers &headers, const Params &params) {
- std::string query;
- for (auto it = params.begin(); it != params.end(); ++it) {
- if (it != params.begin()) { query += "&"; }
- query += it->first;
- query += "=";
- query += detail::encode_url(it->second);
- }
-
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ const Params &params) {
+ auto query = detail::params_to_query_str(params);
return Put(path, headers, query, "application/x-www-form-urlencoded");
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Patch(const char *path, const std::string &body,
+ const char *content_type) {
return Patch(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("PATCH", path, headers, body, 0,
+ nullptr, content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Patch(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Patch(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Patch(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Patch(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("PATCH", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "PATCH", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Delete(const char *path) {
+inline Result ClientImpl::Delete(const char *path) {
return Delete(path, Headers(), std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Delete(const char *path, const std::string &body,
+ const char *content_type) {
return Delete(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Delete(const char *path, const Headers &headers) {
return Delete(path, headers, std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Delete(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
Request req;
req.method = "DELETE";
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
if (content_type) { req.headers.emplace("Content-Type", content_type); }
req.body = body;
- auto res = std::make_shared<Response>();
-
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Options(const char *path) {
+inline Result ClientImpl::Options(const char *path) {
return Options(path, Headers());
}
-inline std::shared_ptr<Response> Client::Options(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Options(const char *path, const Headers &headers) {
Request req;
req.method = "OPTIONS";
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- req.headers = headers;
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
+}
- return send(req, *res) ? res : nullptr;
+inline size_t ClientImpl::is_socket_open() const {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ return socket_.is_open();
}
-inline void Client::set_timeout_sec(time_t timeout_sec) {
- timeout_sec_ = timeout_sec;
+inline void ClientImpl::stop() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ // There is no guarantee that this doesn't get overwritten later, but set it so that
+ // there is a good chance that any threads stopping as a result pick up this error.
+ error_ = Error::Canceled;
+
+ // If there is anything ongoing right now, the ONLY thread-safe thing we can do
+ // is to shutdown_socket, so that threads using this socket suddenly discover
+ // they can't read/write any more and error out.
+ // Everything else (closing the socket, shutting ssl down) is unsafe because these
+ // actions are not thread-safe.
+ if (socket_requests_in_flight_ > 0) {
+ shutdown_socket(socket_);
+ // Aside from that, we set a flag for the socket to be closed when we're done.
+ socket_should_be_closed_when_request_is_done_ = true;
+ return;
+ }
+
+ //Otherwise, sitll holding the mutex, we can shut everything down ourselves
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
}
-inline void Client::set_read_timeout(time_t sec, time_t usec) {
+inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
+ connection_timeout_sec_ = sec;
+ connection_timeout_usec_ = usec;
+}
+
+inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
read_timeout_sec_ = sec;
read_timeout_usec_ = usec;
}
-inline void Client::set_keep_alive_max_count(size_t count) {
- keep_alive_max_count_ = count;
+inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
}
-inline void Client::set_basic_auth(const char *username, const char *password) {
+inline void ClientImpl::set_basic_auth(const char *username,
+ const char *password) {
basic_auth_username_ = username;
basic_auth_password_ = password;
}
+inline void ClientImpl::set_bearer_token_auth(const char *token) {
+ bearer_token_auth_token_ = token;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_digest_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_digest_auth(const char *username,
+ const char *password) {
digest_auth_username_ = username;
digest_auth_password_ = password;
}
#endif
-inline void Client::set_follow_location(bool on) { follow_location_ = on; }
+inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
-inline void Client::set_compress(bool on) { compress_ = on; }
+inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
-inline void Client::set_interface(const char *intf) { interface_ = intf; }
+inline void ClientImpl::set_default_headers(Headers headers) {
+ default_headers_ = std::move(headers);
+}
-inline void Client::set_proxy(const char *host, int port) {
+inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
+inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+}
+
+inline void ClientImpl::set_compress(bool on) { compress_ = on; }
+
+inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
+
+inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; }
+
+inline void ClientImpl::set_proxy(const char *host, int port) {
proxy_host_ = host;
proxy_port_ = port;
}
-inline void Client::set_proxy_basic_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_proxy_basic_auth(const char *username,
+ const char *password) {
proxy_basic_auth_username_ = username;
proxy_basic_auth_password_ = password;
}
+inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) {
+ proxy_bearer_token_auth_token_ = token;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_proxy_digest_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_proxy_digest_auth(const char *username,
+ const char *password) {
proxy_digest_auth_username_ = username;
proxy_digest_auth_password_ = password;
}
#endif
-inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); }
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
+ server_certificate_verification_ = enabled;
+}
+#endif
+
+inline void ClientImpl::set_logger(Logger logger) {
+ logger_ = std::move(logger);
+}
/*
* SSL Implementation
@@ -4336,72 +5721,69 @@ inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
namespace detail {
-template <typename U, typename V, typename T>
-inline bool process_and_close_socket_ssl(
- bool is_client_request, socket_t sock, size_t keep_alive_max_count,
- time_t read_timeout_sec, time_t read_timeout_usec, SSL_CTX *ctx,
- std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup, T callback) {
- assert(keep_alive_max_count > 0);
-
+template <typename U, typename V>
+inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
+ U SSL_connect_or_accept, V setup) {
SSL *ssl = nullptr;
{
std::lock_guard<std::mutex> guard(ctx_mutex);
ssl = SSL_new(ctx);
}
- if (!ssl) {
- close_socket(sock);
- return false;
- }
+ if (ssl) {
+ auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
+ SSL_set_bio(ssl, bio, bio);
- auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
- SSL_set_bio(ssl, bio, bio);
-
- if (!setup(ssl)) {
- SSL_shutdown(ssl);
- {
- std::lock_guard<std::mutex> guard(ctx_mutex);
- SSL_free(ssl);
+ if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
+ SSL_shutdown(ssl);
+ {
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+ }
+ return nullptr;
}
-
- close_socket(sock);
- return false;
}
- auto ret = false;
+ return ssl;
+}
- if (SSL_connect_or_accept(ssl) == 1) {
- if (keep_alive_max_count > 1) {
- auto count = keep_alive_max_count;
- while (count > 0 &&
- (is_client_request ||
- detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
- CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
- SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
- auto last_connection = count == 1;
- auto connection_close = false;
-
- ret = callback(ssl, strm, last_connection, connection_close);
- if (!ret || connection_close) { break; }
-
- count--;
- }
- } else {
- SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
- auto dummy_connection_close = false;
- ret = callback(ssl, strm, true, dummy_connection_close);
- }
+inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
+ bool shutdown_gracefully) {
+ // sometimes we may want to skip this to try to avoid SIGPIPE if we know
+ // the remote has closed the network connection
+ // Note that it is not always possible to avoid SIGPIPE, this is merely a best-efforts.
+ if (shutdown_gracefully) {
+ SSL_shutdown(ssl);
}
- SSL_shutdown(ssl);
- {
- std::lock_guard<std::mutex> guard(ctx_mutex);
- SSL_free(ssl);
- }
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+}
- close_socket(sock);
+template <typename T>
+inline bool
+process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec,
+ time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ T callback) {
+ return process_server_socket_core(
+ sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
- return ret;
+template <typename T>
+inline bool
+process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -4420,11 +5802,11 @@ public:
private:
static void locking_callback(int mode, int type, const char * /*file*/,
int /*line*/) {
- auto &locks = *openSSL_locks_;
+ auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
if (mode & CRYPTO_LOCK) {
- locks[type].lock();
+ lk.lock();
} else {
- locks[type].unlock();
+ lk.unlock();
}
}
};
@@ -4458,9 +5840,15 @@ private:
// SSL socket stream implementation
inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
time_t read_timeout_sec,
- time_t read_timeout_usec)
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
- read_timeout_usec_(read_timeout_usec) {}
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec) {
+ SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
+}
inline SSLSocketStream::~SSLSocketStream() {}
@@ -4469,24 +5857,44 @@ inline bool SSLSocketStream::is_readable() const {
}
inline bool SSLSocketStream::is_writable() const {
- return detail::select_write(sock_, 0, 0) > 0;
+ return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
+ 0;
}
-inline int SSLSocketStream::read(char *ptr, size_t size) {
- if (SSL_pending(ssl_) > 0 ||
- select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0) {
+inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
+ if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret < 0) {
+ auto err = SSL_get_error(ssl_, ret);
+ while (err == SSL_ERROR_WANT_READ) {
+ if (SSL_pending(ssl_) > 0) {
+ return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret >= 0) {
+ return ret;
+ }
+ err = SSL_get_error(ssl_, ret);
+ } else {
+ return -1;
+ }
+ }
+ }
+ return ret;
}
return -1;
}
-inline int SSLSocketStream::write(const char *ptr, size_t size) {
+inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
if (is_writable()) { return SSL_write(ssl_, ptr, static_cast<int>(size)); }
return -1;
}
-inline std::string SSLSocketStream::get_remote_addr() const {
- return detail::get_remote_addr(sock_);
+inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ detail::get_remote_ip_and_port(sock_, ip, port);
}
static SSLInit sslinit_;
@@ -4532,6 +5940,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
}
}
+inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store) {
+ ctx_ = SSL_CTX_new(SSLv23_server_method());
+
+ if (ctx_) {
+ SSL_CTX_set_options(ctx_,
+ SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ } else if (client_ca_cert_store) {
+
+ SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+
+ SSL_CTX_set_verify(
+ ctx_,
+ SSL_VERIFY_PEER |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
+ nullptr);
+ }
+ }
+}
+
inline SSLServer::~SSLServer() {
if (ctx_) { SSL_CTX_free(ctx_); }
}
@@ -4539,21 +5974,42 @@ inline SSLServer::~SSLServer() {
inline bool SSLServer::is_valid() const { return ctx_; }
inline bool SSLServer::process_and_close_socket(socket_t sock) {
- return detail::process_and_close_socket_ssl(
- false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
- ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; },
- [this](SSL *ssl, Stream &strm, bool last_connection,
- bool &connection_close) {
- return process_request(strm, last_connection, connection_close,
- [&](Request &req) { req.ssl = ssl; });
- });
+ auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept,
+ [](SSL * /*ssl*/) { return true; });
+
+ if (ssl) {
+ auto ret = detail::process_server_socket_ssl(
+ ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_,
+ [this, ssl](Stream &strm, bool close_connection,
+ bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
+ [&](Request &req) { req.ssl = ssl; });
+ });
+
+ detail::ssl_delete(ctx_mutex_, ssl, ret);
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
+ }
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return false;
}
// SSL HTTP client implementation
+inline SSLClient::SSLClient(const std::string &host)
+ : SSLClient(host, 443, std::string(), std::string()) {}
+
+inline SSLClient::SSLClient(const std::string &host, int port)
+ : SSLClient(host, port, std::string(), std::string()) {}
+
inline SSLClient::SSLClient(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
- : Client(host, port, client_cert_path, client_key_path) {
+ : ClientImpl(host, port, client_cert_path, client_key_path) {
ctx_ = SSL_CTX_new(SSLv23_client_method());
detail::split(&host_[0], &host_[host_.size()], '.',
@@ -4571,8 +6027,30 @@ inline SSLClient::SSLClient(const std::string &host, int port,
}
}
+inline SSLClient::SSLClient(const std::string &host, int port,
+ X509 *client_cert, EVP_PKEY *client_key)
+ : ClientImpl(host, port) {
+ ctx_ = SSL_CTX_new(SSLv23_client_method());
+
+ detail::split(&host_[0], &host_[host_.size()], '.',
+ [&](const char *b, const char *e) {
+ host_components_.emplace_back(std::string(b, e));
+ });
+ if (client_cert != nullptr && client_key != nullptr) {
+ if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
inline SSLClient::~SSLClient() {
if (ctx_) { SSL_CTX_free(ctx_); }
+ // Make sure to shut down SSL since shutdown_ssl will resolve to the
+ // base function rather than the derived function once we get to the
+ // base class destructor, and won't free the SSL (causing a leak).
+ SSLClient::shutdown_ssl(socket_, true);
}
inline bool SSLClient::is_valid() const { return ctx_; }
@@ -4583,67 +6061,188 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
}
-inline void SSLClient::enable_server_certificate_verification(bool enabled) {
- server_certificate_verification_ = enabled;
+inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (ca_cert_store) {
+ if (ctx_) {
+ if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
+ // Free memory allocated for old cert and use new store `ca_cert_store`
+ SSL_CTX_set_cert_store(ctx_, ca_cert_store);
+ }
+ } else {
+ X509_STORE_free(ca_cert_store);
+ }
+ }
}
inline long SSLClient::get_openssl_verify_result() const {
return verify_result_;
}
-inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
+inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
-inline bool SSLClient::process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback) {
+inline bool SSLClient::create_and_connect_socket(Socket &socket) {
+ return is_valid() && ClientImpl::create_and_connect_socket(socket);
+}
- request_count = std::min(request_count, keep_alive_max_count_);
+// Assumes that socket_mutex_ is locked and that there are no requests in flight
+inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
+ bool &success) {
+ success = true;
+ Response res2;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req2;
+ req2.method = "CONNECT";
+ req2.path = host_and_port_;
+ return process_request(strm, req2, res2, false);
+ })) {
+ // Thread-safe to close everything because we are assuming there are no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+
+ if (res2.status == 407) {
+ if (!proxy_digest_auth_username_.empty() &&
+ !proxy_digest_auth_password_.empty()) {
+ std::map<std::string, std::string> auth;
+ if (detail::parse_www_authenticate(res2, auth, true)) {
+ Response res3;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req3;
+ req3.method = "CONNECT";
+ req3.path = host_and_port_;
+ req3.headers.insert(detail::make_digest_authentication_header(
+ req3, auth, 1, detail::random_string(10),
+ proxy_digest_auth_username_, proxy_digest_auth_password_,
+ true));
+ return process_request(strm, req3, res3, false);
+ })) {
+ // Thread-safe to close everything because we are assuming there are no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+ }
+ } else {
+ res = res2;
+ return false;
+ }
+ }
- return is_valid() &&
- detail::process_and_close_socket_ssl(
- true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
- ctx_, ctx_mutex_,
- [&](SSL *ssl) {
- if (ca_cert_file_path_.empty()) {
- SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
- } else {
- if (!SSL_CTX_load_verify_locations(
- ctx_, ca_cert_file_path_.c_str(), nullptr)) {
- return false;
- }
- SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
- }
+ return true;
+}
- if (SSL_connect(ssl) != 1) { return false; }
+inline bool SSLClient::load_certs() {
+ bool ret = true;
- if (server_certificate_verification_) {
- verify_result_ = SSL_get_verify_result(ssl);
+ std::call_once(initialize_cert_, [&]() {
+ std::lock_guard<std::mutex> guard(ctx_mutex_);
+ if (!ca_cert_file_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
+ nullptr)) {
+ ret = false;
+ }
+ } else if (!ca_cert_dir_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
+ ca_cert_dir_path_.c_str())) {
+ ret = false;
+ }
+ } else {
+#ifdef _WIN32
+ detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
+#else
+ SSL_CTX_set_default_verify_paths(ctx_);
+#endif
+ }
+ });
- if (verify_result_ != X509_V_OK) { return false; }
+ return ret;
+}
- auto server_cert = SSL_get_peer_certificate(ssl);
+inline bool SSLClient::initialize_ssl(Socket &socket) {
+ auto ssl = detail::ssl_new(
+ socket.sock, ctx_, ctx_mutex_,
+ [&](SSL *ssl) {
+ if (server_certificate_verification_) {
+ if (!load_certs()) {
+ error_ = Error::SSLLoadingCerts;
+ return false;
+ }
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
+ }
- if (server_cert == nullptr) { return false; }
+ if (SSL_connect(ssl) != 1) {
+ error_ = Error::SSLConnection;
+ return false;
+ }
- if (!verify_host(server_cert)) {
- X509_free(server_cert);
- return false;
- }
- X509_free(server_cert);
- }
+ if (server_certificate_verification_) {
+ verify_result_ = SSL_get_verify_result(ssl);
- return true;
- },
- [&](SSL *ssl) {
- SSL_set_tlsext_host_name(ssl, host_.c_str());
- return true;
- },
- [&](SSL * /*ssl*/, Stream &strm, bool last_connection,
- bool &connection_close) {
- return callback(strm, last_connection, connection_close);
- });
+ if (verify_result_ != X509_V_OK) {
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+
+ auto server_cert = SSL_get_peer_certificate(ssl);
+
+ if (server_cert == nullptr) {
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+
+ if (!verify_host(server_cert)) {
+ X509_free(server_cert);
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+ X509_free(server_cert);
+ }
+
+ return true;
+ },
+ [&](SSL *ssl) {
+ SSL_set_tlsext_host_name(ssl, host_.c_str());
+ return true;
+ });
+
+ if (ssl) {
+ socket.ssl = ssl;
+ return true;
+ }
+
+ shutdown_socket(socket);
+ close_socket(socket);
+ return false;
+}
+
+inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+ if (socket.sock == INVALID_SOCKET) {
+ assert(socket.ssl == nullptr);
+ return;
+ }
+ if (socket.ssl) {
+ detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
+ socket.ssl = nullptr;
+ }
+ assert(socket.ssl == nullptr);
+}
+
+inline bool
+SSLClient::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ assert(socket.ssl);
+ return detail::process_client_socket_ssl(
+ socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, std::move(callback));
}
inline bool SSLClient::is_ssl() const { return true; }
@@ -4703,7 +6302,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
auto count = sk_GENERAL_NAME_num(alt_names);
- for (auto i = 0; i < count && !dsn_matched; i++) {
+ for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
auto val = sk_GENERAL_NAME_value(alt_names, i);
if (val->type == type) {
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
@@ -4728,7 +6327,6 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
}
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
-
return ret;
}
@@ -4740,7 +6338,9 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
name, sizeof(name));
- if (name_len != -1) { return check_host_name(name, name_len); }
+ if (name_len != -1) {
+ return check_host_name(name, static_cast<size_t>(name_len));
+ }
}
return false;
@@ -4775,6 +6375,338 @@ inline bool SSLClient::check_host_name(const char *pattern,
}
#endif
+// Universal client implementation
+inline Client::Client(const char *scheme_host_port)
+ : Client(scheme_host_port, std::string(), std::string()) {}
+
+inline Client::Client(const char *scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path) {
+ const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
+
+ std::cmatch m;
+ if (std::regex_match(scheme_host_port, m, re)) {
+ auto scheme = m[1].str();
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
+#else
+ if (!scheme.empty() && scheme != "http") {
+#endif
+ std::string msg = "'" + scheme + "' scheme is not supported.";
+ throw std::invalid_argument(msg);
+ return;
+ }
+
+ auto is_ssl = scheme == "https";
+
+ auto host = m[2].str();
+
+ auto port_str = m[3].str();
+ auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
+
+ if (is_ssl) {
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ cli_ = detail::make_unique<SSLClient>(host.c_str(), port,
+ client_cert_path, client_key_path);
+ is_ssl_ = is_ssl;
+#endif
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(host.c_str(), port,
+ client_cert_path, client_key_path);
+ }
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
+ client_cert_path, client_key_path);
+ }
+}
+
+inline Client::Client(const std::string &host, int port)
+ : cli_(detail::make_unique<ClientImpl>(host, port)) {}
+
+inline Client::Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
+ client_key_path)) {}
+
+inline Client::~Client() {}
+
+inline bool Client::is_valid() const {
+ return cli_ != nullptr && cli_->is_valid();
+}
+
+inline Result Client::Get(const char *path) { return cli_->Get(path); }
+inline Result Client::Get(const char *path, const Headers &headers) {
+ return cli_->Get(path, headers);
+}
+inline Result Client::Get(const char *path, Progress progress) {
+ return cli_->Get(path, std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ Progress progress) {
+ return cli_->Get(path, headers, std::move(progress));
+}
+inline Result Client::Get(const char *path, ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, ContentReceiver content_receiver,
+ Progress progress) {
+ return cli_->Get(path, std::move(content_receiver), std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(content_receiver),
+ std::move(progress));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+
+inline Result Client::Head(const char *path) { return cli_->Head(path); }
+inline Result Client::Head(const char *path, const Headers &headers) {
+ return cli_->Head(path, headers);
+}
+
+inline Result Client::Post(const char *path) { return cli_->Post(path); }
+inline Result Client::Post(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Post(path, body, content_type);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Post(path, headers, body, content_type);
+}
+inline Result Client::Post(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Post(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Post(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Post(const char *path, const Params &params) {
+ return cli_->Post(path, params);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const Params &params) {
+ return cli_->Post(path, headers, params);
+}
+inline Result Client::Post(const char *path,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, items);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, headers, items);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ return cli_->Post(path, headers, items, boundary);
+}
+inline Result Client::Put(const char *path) { return cli_->Put(path); }
+inline Result Client::Put(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Put(path, body, content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Put(path, headers, body, content_type);
+}
+inline Result Client::Put(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Put(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Put(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Put(const char *path, const Params &params) {
+ return cli_->Put(path, params);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ const Params &params) {
+ return cli_->Put(path, headers, params);
+}
+inline Result Client::Patch(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Patch(path, body, content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Patch(path, headers, body, content_type);
+}
+inline Result Client::Patch(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Patch(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Patch(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Delete(const char *path) { return cli_->Delete(path); }
+inline Result Client::Delete(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Delete(path, body, content_type);
+}
+inline Result Client::Delete(const char *path, const Headers &headers) {
+ return cli_->Delete(path, headers);
+}
+inline Result Client::Delete(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ return cli_->Delete(path, headers, body, content_type);
+}
+inline Result Client::Options(const char *path) { return cli_->Options(path); }
+inline Result Client::Options(const char *path, const Headers &headers) {
+ return cli_->Options(path, headers);
+}
+
+inline bool Client::send(const Request &req, Response &res) {
+ return cli_->send(req, res);
+}
+
+inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
+
+inline void Client::stop() { cli_->stop(); }
+
+inline void Client::set_default_headers(Headers headers) {
+ cli_->set_default_headers(std::move(headers));
+}
+
+inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
+inline void Client::set_socket_options(SocketOptions socket_options) {
+ cli_->set_socket_options(std::move(socket_options));
+}
+
+inline void Client::set_connection_timeout(time_t sec, time_t usec) {
+ cli_->set_connection_timeout(sec, usec);
+}
+inline void Client::set_read_timeout(time_t sec, time_t usec) {
+ cli_->set_read_timeout(sec, usec);
+}
+inline void Client::set_write_timeout(time_t sec, time_t usec) {
+ cli_->set_write_timeout(sec, usec);
+}
+
+inline void Client::set_basic_auth(const char *username, const char *password) {
+ cli_->set_basic_auth(username, password);
+}
+inline void Client::set_bearer_token_auth(const char *token) {
+ cli_->set_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_digest_auth(const char *username,
+ const char *password) {
+ cli_->set_digest_auth(username, password);
+}
+#endif
+
+inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
+inline void Client::set_follow_location(bool on) {
+ cli_->set_follow_location(on);
+}
+
+inline void Client::set_compress(bool on) { cli_->set_compress(on); }
+
+inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
+
+inline void Client::set_interface(const char *intf) {
+ cli_->set_interface(intf);
+}
+
+inline void Client::set_proxy(const char *host, int port) {
+ cli_->set_proxy(host, port);
+}
+inline void Client::set_proxy_basic_auth(const char *username,
+ const char *password) {
+ cli_->set_proxy_basic_auth(username, password);
+}
+inline void Client::set_proxy_bearer_token_auth(const char *token) {
+ cli_->set_proxy_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_proxy_digest_auth(const char *username,
+ const char *password) {
+ cli_->set_proxy_digest_auth(username, password);
+}
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::enable_server_certificate_verification(bool enabled) {
+ cli_->enable_server_certificate_verification(enabled);
+}
+#endif
+
+inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
+ const char *ca_cert_dir_path) {
+ if (is_ssl_) {
+ static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
+ ca_cert_dir_path);
+ }
+}
+
+inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (is_ssl_) {
+ static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
+ }
+}
+
+inline long Client::get_openssl_verify_result() const {
+ if (is_ssl_) {
+ return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
+ }
+ return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
+}
+
+inline SSL_CTX *Client::ssl_context() const {
+ if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
+ return nullptr;
+}
+#endif
+
// ----------------------------------------------------------------------------
} // namespace httplib
diff --git a/externals/inih/inih b/externals/inih/inih
-Subproject 603729dec89aaca42d7bd08f08bc333165b7d5d
+Subproject 1e80a47dffbda813604f0913e2ad68c7054c14e
diff --git a/externals/json/README.md b/externals/json/README.md
deleted file mode 100644
index 2db68ffde..000000000
--- a/externals/json/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-JSON for Modern C++
-===================
-
-v3.1.2
-
-This is a mirror providing the single required header file.
-
-The original repository can be found at:
-https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe
diff --git a/externals/json/json.hpp b/externals/json/json.hpp
deleted file mode 100644
index 6b6655af8..000000000
--- a/externals/json/json.hpp
+++ /dev/null
@@ -1,17300 +0,0 @@
-/*
- __ _____ _____ _____
- __| | __| | | | JSON for Modern C++
-| | |__ | | | | | | version 3.1.2
-|_____|_____|_____|_|___| https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-#ifndef NLOHMANN_JSON_HPP
-#define NLOHMANN_JSON_HPP
-
-#define NLOHMANN_JSON_VERSION_MAJOR 3
-#define NLOHMANN_JSON_VERSION_MINOR 1
-#define NLOHMANN_JSON_VERSION_PATCH 2
-
-#include <algorithm> // all_of, find, for_each
-#include <cassert> // assert
-#include <ciso646> // and, not, or
-#include <cstddef> // nullptr_t, ptrdiff_t, size_t
-#include <functional> // hash, less
-#include <initializer_list> // initializer_list
-#include <iosfwd> // istream, ostream
-#include <iterator> // iterator_traits, random_access_iterator_tag
-#include <numeric> // accumulate
-#include <string> // string, stoi, to_string
-#include <utility> // declval, forward, move, pair, swap
-
-// #include <nlohmann/json_fwd.hpp>
-#ifndef NLOHMANN_JSON_FWD_HPP
-#define NLOHMANN_JSON_FWD_HPP
-
-#include <cstdint> // int64_t, uint64_t
-#include <map> // map
-#include <memory> // allocator
-#include <string> // string
-#include <vector> // vector
-
-/*!
-@brief namespace for Niels Lohmann
-@see https://github.com/nlohmann
-@since version 1.0.0
-*/
-namespace nlohmann
-{
-/*!
-@brief default JSONSerializer template argument
-
-This serializer ignores the template arguments and uses ADL
-([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl))
-for serialization.
-*/
-template<typename = void, typename = void>
-struct adl_serializer;
-
-template<template<typename U, typename V, typename... Args> class ObjectType =
- std::map,
- template<typename U, typename... Args> class ArrayType = std::vector,
- class StringType = std::string, class BooleanType = bool,
- class NumberIntegerType = std::int64_t,
- class NumberUnsignedType = std::uint64_t,
- class NumberFloatType = double,
- template<typename U> class AllocatorType = std::allocator,
- template<typename T, typename SFINAE = void> class JSONSerializer =
- adl_serializer>
-class basic_json;
-
-/*!
-@brief JSON Pointer
-
-A JSON pointer defines a string syntax for identifying a specific value
-within a JSON document. It can be used with functions `at` and
-`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
-
-@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
-
-@since version 2.0.0
-*/
-template<typename BasicJsonType>
-class json_pointer;
-
-/*!
-@brief default JSON class
-
-This type is the default specialization of the @ref basic_json class which
-uses the standard template types.
-
-@since version 1.0.0
-*/
-using json = basic_json<>;
-}
-
-#endif
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-
-// This file contains all internal macro definitions
-// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them
-
-// exclude unsupported compilers
-#if defined(__clang__)
- #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400
- #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers"
- #endif
-#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))
- #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900
- #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers"
- #endif
-#endif
-
-// disable float-equal warnings on GCC/clang
-#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wfloat-equal"
-#endif
-
-// disable documentation warnings on clang
-#if defined(__clang__)
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wdocumentation"
-#endif
-
-// allow for portable deprecation warnings
-#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
- #define JSON_DEPRECATED __attribute__((deprecated))
-#elif defined(_MSC_VER)
- #define JSON_DEPRECATED __declspec(deprecated)
-#else
- #define JSON_DEPRECATED
-#endif
-
-// allow to disable exceptions
-#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
- #define JSON_THROW(exception) throw exception
- #define JSON_TRY try
- #define JSON_CATCH(exception) catch(exception)
-#else
- #define JSON_THROW(exception) std::abort()
- #define JSON_TRY if(true)
- #define JSON_CATCH(exception) if(false)
-#endif
-
-// override exception macros
-#if defined(JSON_THROW_USER)
- #undef JSON_THROW
- #define JSON_THROW JSON_THROW_USER
-#endif
-#if defined(JSON_TRY_USER)
- #undef JSON_TRY
- #define JSON_TRY JSON_TRY_USER
-#endif
-#if defined(JSON_CATCH_USER)
- #undef JSON_CATCH
- #define JSON_CATCH JSON_CATCH_USER
-#endif
-
-// manual branch prediction
-#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
- #define JSON_LIKELY(x) __builtin_expect(!!(x), 1)
- #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
- #define JSON_LIKELY(x) x
- #define JSON_UNLIKELY(x) x
-#endif
-
-// C++ language standard detection
-#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
- #define JSON_HAS_CPP_17
- #define JSON_HAS_CPP_14
-#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
- #define JSON_HAS_CPP_14
-#endif
-
-// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
-// may be removed in the future once the class is split.
-
-#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \
- template<template<typename, typename, typename...> class ObjectType, \
- template<typename, typename...> class ArrayType, \
- class StringType, class BooleanType, class NumberIntegerType, \
- class NumberUnsignedType, class NumberFloatType, \
- template<typename> class AllocatorType, \
- template<typename, typename = void> class JSONSerializer>
-
-#define NLOHMANN_BASIC_JSON_TPL \
- basic_json<ObjectType, ArrayType, StringType, BooleanType, \
- NumberIntegerType, NumberUnsignedType, NumberFloatType, \
- AllocatorType, JSONSerializer>
-
-/*!
-@brief Helper to determine whether there's a key_type for T.
-
-This helper is used to tell associative containers apart from other containers
-such as sequence containers. For instance, `std::map` passes the test as it
-contains a `mapped_type`, whereas `std::vector` fails the test.
-
-@sa http://stackoverflow.com/a/7728728/266378
-@since version 1.0.0, overworked in version 2.0.6
-*/
-#define NLOHMANN_JSON_HAS_HELPER(type) \
- template<typename T> struct has_##type { \
- private: \
- template<typename U, typename = typename U::type> \
- static int detect(U &&); \
- static void detect(...); \
- public: \
- static constexpr bool value = \
- std::is_integral<decltype(detect(std::declval<T>()))>::value; \
- }
-
-// #include <nlohmann/detail/meta.hpp>
-
-
-#include <ciso646> // not
-#include <cstddef> // size_t
-#include <limits> // numeric_limits
-#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
-#include <utility> // declval
-
-// #include <nlohmann/json_fwd.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-
-namespace nlohmann
-{
-/*!
-@brief detail namespace with internal helper functions
-
-This namespace collects functions that should not be exposed,
-implementations of some @ref basic_json methods, and meta-programming helpers.
-
-@since version 2.1.0
-*/
-namespace detail
-{
-/////////////
-// helpers //
-/////////////
-
-template<typename> struct is_basic_json : std::false_type {};
-
-NLOHMANN_BASIC_JSON_TPL_DECLARATION
-struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
-
-// alias templates to reduce boilerplate
-template<bool B, typename T = void>
-using enable_if_t = typename std::enable_if<B, T>::type;
-
-template<typename T>
-using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
-
-// implementation of C++14 index_sequence and affiliates
-// source: https://stackoverflow.com/a/32223343
-template<std::size_t... Ints>
-struct index_sequence
-{
- using type = index_sequence;
- using value_type = std::size_t;
- static constexpr std::size_t size() noexcept
- {
- return sizeof...(Ints);
- }
-};
-
-template<class Sequence1, class Sequence2>
-struct merge_and_renumber;
-
-template<std::size_t... I1, std::size_t... I2>
-struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
- : index_sequence < I1..., (sizeof...(I1) + I2)... > {};
-
-template<std::size_t N>
-struct make_index_sequence
- : merge_and_renumber < typename make_index_sequence < N / 2 >::type,
- typename make_index_sequence < N - N / 2 >::type > {};
-
-template<> struct make_index_sequence<0> : index_sequence<> {};
-template<> struct make_index_sequence<1> : index_sequence<0> {};
-
-template<typename... Ts>
-using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
-
-/*
-Implementation of two C++17 constructs: conjunction, negation. This is needed
-to avoid evaluating all the traits in a condition
-
-For example: not std::is_same<void, T>::value and has_value_type<T>::value
-will not compile when T = void (on MSVC at least). Whereas
-conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will
-stop evaluating if negation<...>::value == false
-
-Please note that those constructs must be used with caution, since symbols can
-become very long quickly (which can slow down compilation and cause MSVC
-internal compiler errors). Only use it when you have to (see example ahead).
-*/
-template<class...> struct conjunction : std::true_type {};
-template<class B1> struct conjunction<B1> : B1 {};
-template<class B1, class... Bn>
-struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};
-
-template<class B> struct negation : std::integral_constant<bool, not B::value> {};
-
-// dispatch utility (taken from ranges-v3)
-template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
-template<> struct priority_tag<0> {};
-
-////////////////////////
-// has_/is_ functions //
-////////////////////////
-
-// source: https://stackoverflow.com/a/37193089/4116453
-
-template <typename T, typename = void>
-struct is_complete_type : std::false_type {};
-
-template <typename T>
-struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
-
-NLOHMANN_JSON_HAS_HELPER(mapped_type);
-NLOHMANN_JSON_HAS_HELPER(key_type);
-NLOHMANN_JSON_HAS_HELPER(value_type);
-NLOHMANN_JSON_HAS_HELPER(iterator);
-
-template<bool B, class RealType, class CompatibleObjectType>
-struct is_compatible_object_type_impl : std::false_type {};
-
-template<class RealType, class CompatibleObjectType>
-struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType>
-{
- static constexpr auto value =
- std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and
- std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value;
-};
-
-template<class BasicJsonType, class CompatibleObjectType>
-struct is_compatible_object_type
-{
- static auto constexpr value = is_compatible_object_type_impl <
- conjunction<negation<std::is_same<void, CompatibleObjectType>>,
- has_mapped_type<CompatibleObjectType>,
- has_key_type<CompatibleObjectType>>::value,
- typename BasicJsonType::object_t, CompatibleObjectType >::value;
-};
-
-template<typename BasicJsonType, typename T>
-struct is_basic_json_nested_type
-{
- static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or
- std::is_same<T, typename BasicJsonType::const_iterator>::value or
- std::is_same<T, typename BasicJsonType::reverse_iterator>::value or
- std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value;
-};
-
-template<class BasicJsonType, class CompatibleArrayType>
-struct is_compatible_array_type
-{
- static auto constexpr value =
- conjunction<negation<std::is_same<void, CompatibleArrayType>>,
- negation<is_compatible_object_type<
- BasicJsonType, CompatibleArrayType>>,
- negation<std::is_constructible<typename BasicJsonType::string_t,
- CompatibleArrayType>>,
- negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>,
- has_value_type<CompatibleArrayType>,
- has_iterator<CompatibleArrayType>>::value;
-};
-
-template<bool, typename, typename>
-struct is_compatible_integer_type_impl : std::false_type {};
-
-template<typename RealIntegerType, typename CompatibleNumberIntegerType>
-struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType>
-{
- // is there an assert somewhere on overflows?
- using RealLimits = std::numeric_limits<RealIntegerType>;
- using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
-
- static constexpr auto value =
- std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and
- CompatibleLimits::is_integer and
- RealLimits::is_signed == CompatibleLimits::is_signed;
-};
-
-template<typename RealIntegerType, typename CompatibleNumberIntegerType>
-struct is_compatible_integer_type
-{
- static constexpr auto value =
- is_compatible_integer_type_impl <
- std::is_integral<CompatibleNumberIntegerType>::value and
- not std::is_same<bool, CompatibleNumberIntegerType>::value,
- RealIntegerType, CompatibleNumberIntegerType > ::value;
-};
-
-// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
-template<typename BasicJsonType, typename T>
-struct has_from_json
-{
- private:
- // also check the return type of from_json
- template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json(
- std::declval<BasicJsonType>(), std::declval<T&>()))>::value>>
- static int detect(U&&);
- static void detect(...);
-
- public:
- static constexpr bool value = std::is_integral<decltype(
- detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
-};
-
-// This trait checks if JSONSerializer<T>::from_json(json const&) exists
-// this overload is used for non-default-constructible user-defined-types
-template<typename BasicJsonType, typename T>
-struct has_non_default_from_json
-{
- private:
- template <
- typename U,
- typename = enable_if_t<std::is_same<
- T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >>
- static int detect(U&&);
- static void detect(...);
-
- public:
- static constexpr bool value = std::is_integral<decltype(detect(
- std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
-};
-
-// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
-template<typename BasicJsonType, typename T>
-struct has_to_json
-{
- private:
- template<typename U, typename = decltype(uncvref_t<U>::to_json(
- std::declval<BasicJsonType&>(), std::declval<T>()))>
- static int detect(U&&);
- static void detect(...);
-
- public:
- static constexpr bool value = std::is_integral<decltype(detect(
- std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value;
-};
-
-template <typename BasicJsonType, typename CompatibleCompleteType>
-struct is_compatible_complete_type
-{
- static constexpr bool value =
- not std::is_base_of<std::istream, CompatibleCompleteType>::value and
- not is_basic_json<CompatibleCompleteType>::value and
- not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and
- has_to_json<BasicJsonType, CompatibleCompleteType>::value;
-};
-
-template <typename BasicJsonType, typename CompatibleType>
-struct is_compatible_type
- : conjunction<is_complete_type<CompatibleType>,
- is_compatible_complete_type<BasicJsonType, CompatibleType>>
-{
-};
-
-// taken from ranges-v3
-template<typename T>
-struct static_const
-{
- static constexpr T value{};
-};
-
-template<typename T>
-constexpr T static_const<T>::value;
-}
-}
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-
-#include <exception> // exception
-#include <stdexcept> // runtime_error
-#include <string> // to_string
-
-namespace nlohmann
-{
-namespace detail
-{
-////////////////
-// exceptions //
-////////////////
-
-/*!
-@brief general exception of the @ref basic_json class
-
-This class is an extension of `std::exception` objects with a member @a id for
-exception ids. It is used as the base class for all exceptions thrown by the
-@ref basic_json class. This class can hence be used as "wildcard" to catch
-exceptions.
-
-Subclasses:
-- @ref parse_error for exceptions indicating a parse error
-- @ref invalid_iterator for exceptions indicating errors with iterators
-- @ref type_error for exceptions indicating executing a member function with
- a wrong type
-- @ref out_of_range for exceptions indicating access out of the defined range
-- @ref other_error for exceptions indicating other library errors
-
-@internal
-@note To have nothrow-copy-constructible exceptions, we internally use
- `std::runtime_error` which can cope with arbitrary-length error messages.
- Intermediate strings are built with static functions and then passed to
- the actual constructor.
-@endinternal
-
-@liveexample{The following code shows how arbitrary library exceptions can be
-caught.,exception}
-
-@since version 3.0.0
-*/
-class exception : public std::exception
-{
- public:
- /// returns the explanatory string
- const char* what() const noexcept override
- {
- return m.what();
- }
-
- /// the id of the exception
- const int id;
-
- protected:
- exception(int id_, const char* what_arg) : id(id_), m(what_arg) {}
-
- static std::string name(const std::string& ename, int id_)
- {
- return "[json.exception." + ename + "." + std::to_string(id_) + "] ";
- }
-
- private:
- /// an exception object as storage for error messages
- std::runtime_error m;
-};
-
-/*!
-@brief exception indicating a parse error
-
-This exception is thrown by the library when a parse error occurs. Parse errors
-can occur during the deserialization of JSON text, CBOR, MessagePack, as well
-as when using JSON Patch.
-
-Member @a byte holds the byte index of the last read character in the input
-file.
-
-Exceptions have ids 1xx.
-
-name / id | example message | description
------------------------------- | --------------- | -------------------------
-json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position.
-json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point.
-json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid.
-json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects.
-json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors.
-json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`.
-json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character.
-json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences.
-json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number.
-json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
-json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read.
-json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read.
-
-@note For an input with n bytes, 1 is the index of the first character and n+1
- is the index of the terminating null byte or the end of file. This also
- holds true when reading a byte vector (CBOR or MessagePack).
-
-@liveexample{The following code shows how a `parse_error` exception can be
-caught.,parse_error}
-
-@sa @ref exception for the base class of the library exceptions
-@sa @ref invalid_iterator for exceptions indicating errors with iterators
-@sa @ref type_error for exceptions indicating executing a member function with
- a wrong type
-@sa @ref out_of_range for exceptions indicating access out of the defined range
-@sa @ref other_error for exceptions indicating other library errors
-
-@since version 3.0.0
-*/
-class parse_error : public exception
-{
- public:
- /*!
- @brief create a parse error exception
- @param[in] id_ the id of the exception
- @param[in] byte_ the byte index where the error occurred (or 0 if the
- position cannot be determined)
- @param[in] what_arg the explanatory string
- @return parse_error object
- */
- static parse_error create(int id_, std::size_t byte_, const std::string& what_arg)
- {
- std::string w = exception::name("parse_error", id_) + "parse error" +
- (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") +
- ": " + what_arg;
- return parse_error(id_, byte_, w.c_str());
- }
-
- /*!
- @brief byte index of the parse error
-
- The byte index of the last read character in the input file.
-
- @note For an input with n bytes, 1 is the index of the first character and
- n+1 is the index of the terminating null byte or the end of file.
- This also holds true when reading a byte vector (CBOR or MessagePack).
- */
- const std::size_t byte;
-
- private:
- parse_error(int id_, std::size_t byte_, const char* what_arg)
- : exception(id_, what_arg), byte(byte_) {}
-};
-
-/*!
-@brief exception indicating errors with iterators
-
-This exception is thrown if iterators passed to a library function do not match
-the expected semantics.
-
-Exceptions have ids 2xx.
-
-name / id | example message | description
------------------------------------ | --------------- | -------------------------
-json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
-json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion.
-json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from.
-json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid.
-json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid.
-json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range.
-json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key.
-json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
-json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
-json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
-json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to.
-json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container.
-json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered.
-json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin().
-
-@liveexample{The following code shows how an `invalid_iterator` exception can be
-caught.,invalid_iterator}
-
-@sa @ref exception for the base class of the library exceptions
-@sa @ref parse_error for exceptions indicating a parse error
-@sa @ref type_error for exceptions indicating executing a member function with
- a wrong type
-@sa @ref out_of_range for exceptions indicating access out of the defined range
-@sa @ref other_error for exceptions indicating other library errors
-
-@since version 3.0.0
-*/
-class invalid_iterator : public exception
-{
- public:
- static invalid_iterator create(int id_, const std::string& what_arg)
- {
- std::string w = exception::name("invalid_iterator", id_) + what_arg;
- return invalid_iterator(id_, w.c_str());
- }
-
- private:
- invalid_iterator(int id_, const char* what_arg)
- : exception(id_, what_arg) {}
-};
-
-/*!
-@brief exception indicating executing a member function with a wrong type
-
-This exception is thrown in case of a type error; that is, a library function is
-executed on a JSON value whose type does not match the expected semantics.
-
-Exceptions have ids 3xx.
-
-name / id | example message | description
------------------------------ | --------------- | -------------------------
-json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead.
-json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types.
-json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&.
-json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types.
-json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types.
-json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types.
-json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types.
-json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types.
-json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types.
-json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types.
-json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types.
-json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types.
-json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined.
-json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers.
-json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive.
-json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. |
-
-@liveexample{The following code shows how a `type_error` exception can be
-caught.,type_error}
-
-@sa @ref exception for the base class of the library exceptions
-@sa @ref parse_error for exceptions indicating a parse error
-@sa @ref invalid_iterator for exceptions indicating errors with iterators
-@sa @ref out_of_range for exceptions indicating access out of the defined range
-@sa @ref other_error for exceptions indicating other library errors
-
-@since version 3.0.0
-*/
-class type_error : public exception
-{
- public:
- static type_error create(int id_, const std::string& what_arg)
- {
- std::string w = exception::name("type_error", id_) + what_arg;
- return type_error(id_, w.c_str());
- }
-
- private:
- type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
-};
-
-/*!
-@brief exception indicating access out of the defined range
-
-This exception is thrown in case a library function is called on an input
-parameter that exceeds the expected range, for instance in case of array
-indices or nonexisting object keys.
-
-Exceptions have ids 4xx.
-
-name / id | example message | description
-------------------------------- | --------------- | -------------------------
-json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1.
-json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it.
-json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object.
-json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved.
-json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value.
-json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF.
-json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. |
-json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. |
-
-@liveexample{The following code shows how an `out_of_range` exception can be
-caught.,out_of_range}
-
-@sa @ref exception for the base class of the library exceptions
-@sa @ref parse_error for exceptions indicating a parse error
-@sa @ref invalid_iterator for exceptions indicating errors with iterators
-@sa @ref type_error for exceptions indicating executing a member function with
- a wrong type
-@sa @ref other_error for exceptions indicating other library errors
-
-@since version 3.0.0
-*/
-class out_of_range : public exception
-{
- public:
- static out_of_range create(int id_, const std::string& what_arg)
- {
- std::string w = exception::name("out_of_range", id_) + what_arg;
- return out_of_range(id_, w.c_str());
- }
-
- private:
- out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}
-};
-
-/*!
-@brief exception indicating other library errors
-
-This exception is thrown in case of errors that cannot be classified with the
-other exception types.
-
-Exceptions have ids 5xx.
-
-name / id | example message | description
------------------------------- | --------------- | -------------------------
-json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed.
-
-@sa @ref exception for the base class of the library exceptions
-@sa @ref parse_error for exceptions indicating a parse error
-@sa @ref invalid_iterator for exceptions indicating errors with iterators
-@sa @ref type_error for exceptions indicating executing a member function with
- a wrong type
-@sa @ref out_of_range for exceptions indicating access out of the defined range
-
-@liveexample{The following code shows how an `other_error` exception can be
-caught.,other_error}
-
-@since version 3.0.0
-*/
-class other_error : public exception
-{
- public:
- static other_error create(int id_, const std::string& what_arg)
- {
- std::string w = exception::name("other_error", id_) + what_arg;
- return other_error(id_, w.c_str());
- }
-
- private:
- other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
-};
-}
-}
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-#include <array> // array
-#include <ciso646> // and
-#include <cstddef> // size_t
-#include <cstdint> // uint8_t
-
-namespace nlohmann
-{
-namespace detail
-{
-///////////////////////////
-// JSON type enumeration //
-///////////////////////////
-
-/*!
-@brief the JSON type enumeration
-
-This enumeration collects the different JSON types. It is internally used to
-distinguish the stored values, and the functions @ref basic_json::is_null(),
-@ref basic_json::is_object(), @ref basic_json::is_array(),
-@ref basic_json::is_string(), @ref basic_json::is_boolean(),
-@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
-@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
-@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
-@ref basic_json::is_structured() rely on it.
-
-@note There are three enumeration entries (number_integer, number_unsigned, and
-number_float), because the library distinguishes these three types for numbers:
-@ref basic_json::number_unsigned_t is used for unsigned integers,
-@ref basic_json::number_integer_t is used for signed integers, and
-@ref basic_json::number_float_t is used for floating-point numbers or to
-approximate integers which do not fit in the limits of their respective type.
-
-@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON
-value with the default value for a given type
-
-@since version 1.0.0
-*/
-enum class value_t : std::uint8_t
-{
- null, ///< null value
- object, ///< object (unordered set of name/value pairs)
- array, ///< array (ordered collection of values)
- string, ///< string value
- boolean, ///< boolean value
- number_integer, ///< number value (signed integer)
- number_unsigned, ///< number value (unsigned integer)
- number_float, ///< number value (floating-point)
- discarded ///< discarded by the the parser callback function
-};
-
-/*!
-@brief comparison operator for JSON types
-
-Returns an ordering that is similar to Python:
-- order: null < boolean < number < object < array < string
-- furthermore, each type is not smaller than itself
-- discarded values are not comparable
-
-@since version 1.0.0
-*/
-inline bool operator<(const value_t lhs, const value_t rhs) noexcept
-{
- static constexpr std::array<std::uint8_t, 8> order = {{
- 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
- 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */
- }
- };
-
- const auto l_index = static_cast<std::size_t>(lhs);
- const auto r_index = static_cast<std::size_t>(rhs);
- return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index];
-}
-}
-}
-
-// #include <nlohmann/detail/conversions/from_json.hpp>
-
-
-#include <algorithm> // transform
-#include <array> // array
-#include <ciso646> // and, not
-#include <forward_list> // forward_list
-#include <iterator> // inserter, front_inserter, end
-#include <string> // string
-#include <tuple> // tuple, make_tuple
-#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
-#include <utility> // pair, declval
-#include <valarray> // valarray
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/meta.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-// overloads for basic_json template parameters
-template<typename BasicJsonType, typename ArithmeticType,
- enable_if_t<std::is_arithmetic<ArithmeticType>::value and
- not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
- int> = 0>
-void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
-{
- switch (static_cast<value_t>(j))
- {
- case value_t::number_unsigned:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
- break;
- }
- case value_t::number_integer:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
- break;
- }
- case value_t::number_float:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
- break;
- }
-
- default:
- JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
- }
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
-{
- if (JSON_UNLIKELY(not j.is_boolean()))
- {
- JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name())));
- }
- b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
-{
- if (JSON_UNLIKELY(not j.is_string()))
- {
- JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
- }
- s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
-{
- get_arithmetic_value(j, val);
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
-{
- get_arithmetic_value(j, val);
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
-{
- get_arithmetic_value(j, val);
-}
-
-template<typename BasicJsonType, typename EnumType,
- enable_if_t<std::is_enum<EnumType>::value, int> = 0>
-void from_json(const BasicJsonType& j, EnumType& e)
-{
- typename std::underlying_type<EnumType>::type val;
- get_arithmetic_value(j, val);
- e = static_cast<EnumType>(val);
-}
-
-template<typename BasicJsonType>
-void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr)
-{
- if (JSON_UNLIKELY(not j.is_array()))
- {
- JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
- }
- arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
-}
-
-// forward_list doesn't have an insert method
-template<typename BasicJsonType, typename T, typename Allocator,
- enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
-void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
-{
- if (JSON_UNLIKELY(not j.is_array()))
- {
- JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
- }
- std::transform(j.rbegin(), j.rend(),
- std::front_inserter(l), [](const BasicJsonType & i)
- {
- return i.template get<T>();
- });
-}
-
-// valarray doesn't have an insert method
-template<typename BasicJsonType, typename T,
- enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
-void from_json(const BasicJsonType& j, std::valarray<T>& l)
-{
- if (JSON_UNLIKELY(not j.is_array()))
- {
- JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
- }
- l.resize(j.size());
- std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l));
-}
-
-template<typename BasicJsonType, typename CompatibleArrayType>
-void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/)
-{
- using std::end;
-
- std::transform(j.begin(), j.end(),
- std::inserter(arr, end(arr)), [](const BasicJsonType & i)
- {
- // get<BasicJsonType>() returns *this, this won't call a from_json
- // method when value_type is BasicJsonType
- return i.template get<typename CompatibleArrayType::value_type>();
- });
-}
-
-template<typename BasicJsonType, typename CompatibleArrayType>
-auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/)
--> decltype(
- arr.reserve(std::declval<typename CompatibleArrayType::size_type>()),
- void())
-{
- using std::end;
-
- arr.reserve(j.size());
- std::transform(j.begin(), j.end(),
- std::inserter(arr, end(arr)), [](const BasicJsonType & i)
- {
- // get<BasicJsonType>() returns *this, this won't call a from_json
- // method when value_type is BasicJsonType
- return i.template get<typename CompatibleArrayType::value_type>();
- });
-}
-
-template<typename BasicJsonType, typename T, std::size_t N>
-void from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr, priority_tag<2> /*unused*/)
-{
- for (std::size_t i = 0; i < N; ++i)
- {
- arr[i] = j.at(i).template get<T>();
- }
-}
-
-template <
- typename BasicJsonType, typename CompatibleArrayType,
- enable_if_t <
- is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and
- not std::is_same<typename BasicJsonType::array_t,
- CompatibleArrayType>::value and
- std::is_constructible <
- BasicJsonType, typename CompatibleArrayType::value_type >::value,
- int > = 0 >
-void from_json(const BasicJsonType& j, CompatibleArrayType& arr)
-{
- if (JSON_UNLIKELY(not j.is_array()))
- {
- JSON_THROW(type_error::create(302, "type must be array, but is " +
- std::string(j.type_name())));
- }
-
- from_json_array_impl(j, arr, priority_tag<2> {});
-}
-
-template<typename BasicJsonType, typename CompatibleObjectType,
- enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0>
-void from_json(const BasicJsonType& j, CompatibleObjectType& obj)
-{
- if (JSON_UNLIKELY(not j.is_object()))
- {
- JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name())));
- }
-
- auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
- using value_type = typename CompatibleObjectType::value_type;
- std::transform(
- inner_object->begin(), inner_object->end(),
- std::inserter(obj, obj.begin()),
- [](typename BasicJsonType::object_t::value_type const & p)
- {
- return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>());
- });
-}
-
-// overload for arithmetic types, not chosen for basic_json template arguments
-// (BooleanType, etc..); note: Is it really necessary to provide explicit
-// overloads for boolean_t etc. in case of a custom BooleanType which is not
-// an arithmetic type?
-template<typename BasicJsonType, typename ArithmeticType,
- enable_if_t <
- std::is_arithmetic<ArithmeticType>::value and
- not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and
- not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and
- not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and
- not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
- int> = 0>
-void from_json(const BasicJsonType& j, ArithmeticType& val)
-{
- switch (static_cast<value_t>(j))
- {
- case value_t::number_unsigned:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
- break;
- }
- case value_t::number_integer:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
- break;
- }
- case value_t::number_float:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
- break;
- }
- case value_t::boolean:
- {
- val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
- break;
- }
-
- default:
- JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
- }
-}
-
-template<typename BasicJsonType, typename A1, typename A2>
-void from_json(const BasicJsonType& j, std::pair<A1, A2>& p)
-{
- p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()};
-}
-
-template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
-void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...>)
-{
- t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...);
-}
-
-template<typename BasicJsonType, typename... Args>
-void from_json(const BasicJsonType& j, std::tuple<Args...>& t)
-{
- from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
-}
-
-struct from_json_fn
-{
- private:
- template<typename BasicJsonType, typename T>
- auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const
- noexcept(noexcept(from_json(j, val)))
- -> decltype(from_json(j, val), void())
- {
- return from_json(j, val);
- }
-
- template<typename BasicJsonType, typename T>
- void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept
- {
- static_assert(sizeof(BasicJsonType) == 0,
- "could not find from_json() method in T's namespace");
-#ifdef _MSC_VER
- // MSVC does not show a stacktrace for the above assert
- using decayed = uncvref_t<T>;
- static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0,
- "forcing MSVC stacktrace to show which T we're talking about.");
-#endif
- }
-
- public:
- template<typename BasicJsonType, typename T>
- void operator()(const BasicJsonType& j, T& val) const
- noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {})))
- {
- return call(j, val, priority_tag<1> {});
- }
-};
-}
-
-/// namespace to hold default `from_json` function
-/// to see why this is required:
-/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
-namespace
-{
-constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
-}
-}
-
-// #include <nlohmann/detail/conversions/to_json.hpp>
-
-
-#include <ciso646> // or, and, not
-#include <iterator> // begin, end
-#include <tuple> // tuple, get
-#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type
-#include <utility> // move, forward, declval, pair
-#include <valarray> // valarray
-#include <vector> // vector
-
-// #include <nlohmann/detail/meta.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-//////////////////
-// constructors //
-//////////////////
-
-template<value_t> struct external_constructor;
-
-template<>
-struct external_constructor<value_t::boolean>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
- {
- j.m_type = value_t::boolean;
- j.m_value = b;
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::string>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
- {
- j.m_type = value_t::string;
- j.m_value = s;
- j.assert_invariant();
- }
-
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
- {
- j.m_type = value_t::string;
- j.m_value = std::move(s);
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::number_float>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
- {
- j.m_type = value_t::number_float;
- j.m_value = val;
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::number_unsigned>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
- {
- j.m_type = value_t::number_unsigned;
- j.m_value = val;
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::number_integer>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
- {
- j.m_type = value_t::number_integer;
- j.m_value = val;
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::array>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
- {
- j.m_type = value_t::array;
- j.m_value = arr;
- j.assert_invariant();
- }
-
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
- {
- j.m_type = value_t::array;
- j.m_value = std::move(arr);
- j.assert_invariant();
- }
-
- template<typename BasicJsonType, typename CompatibleArrayType,
- enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
- int> = 0>
- static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
- {
- using std::begin;
- using std::end;
- j.m_type = value_t::array;
- j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
- j.assert_invariant();
- }
-
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, const std::vector<bool>& arr)
- {
- j.m_type = value_t::array;
- j.m_value = value_t::array;
- j.m_value.array->reserve(arr.size());
- for (const bool x : arr)
- {
- j.m_value.array->push_back(x);
- }
- j.assert_invariant();
- }
-
- template<typename BasicJsonType, typename T,
- enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
- static void construct(BasicJsonType& j, const std::valarray<T>& arr)
- {
- j.m_type = value_t::array;
- j.m_value = value_t::array;
- j.m_value.array->resize(arr.size());
- std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
- j.assert_invariant();
- }
-};
-
-template<>
-struct external_constructor<value_t::object>
-{
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
- {
- j.m_type = value_t::object;
- j.m_value = obj;
- j.assert_invariant();
- }
-
- template<typename BasicJsonType>
- static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
- {
- j.m_type = value_t::object;
- j.m_value = std::move(obj);
- j.assert_invariant();
- }
-
- template<typename BasicJsonType, typename CompatibleObjectType,
- enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0>
- static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
- {
- using std::begin;
- using std::end;
-
- j.m_type = value_t::object;
- j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
- j.assert_invariant();
- }
-};
-
-/////////////
-// to_json //
-/////////////
-
-template<typename BasicJsonType, typename T,
- enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>
-void to_json(BasicJsonType& j, T b) noexcept
-{
- external_constructor<value_t::boolean>::construct(j, b);
-}
-
-template<typename BasicJsonType, typename CompatibleString,
- enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>
-void to_json(BasicJsonType& j, const CompatibleString& s)
-{
- external_constructor<value_t::string>::construct(j, s);
-}
-
-template<typename BasicJsonType>
-void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)
-{
- external_constructor<value_t::string>::construct(j, std::move(s));
-}
-
-template<typename BasicJsonType, typename FloatType,
- enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
-void to_json(BasicJsonType& j, FloatType val) noexcept
-{
- external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
-}
-
-template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
- enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
-void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
-{
- external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
-}
-
-template<typename BasicJsonType, typename CompatibleNumberIntegerType,
- enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
-void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
-{
- external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
-}
-
-template<typename BasicJsonType, typename EnumType,
- enable_if_t<std::is_enum<EnumType>::value, int> = 0>
-void to_json(BasicJsonType& j, EnumType e) noexcept
-{
- using underlying_type = typename std::underlying_type<EnumType>::type;
- external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
-}
-
-template<typename BasicJsonType>
-void to_json(BasicJsonType& j, const std::vector<bool>& e)
-{
- external_constructor<value_t::array>::construct(j, e);
-}
-
-template<typename BasicJsonType, typename CompatibleArrayType,
- enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or
- std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value,
- int> = 0>
-void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
-{
- external_constructor<value_t::array>::construct(j, arr);
-}
-
-template<typename BasicJsonType, typename T,
- enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
-void to_json(BasicJsonType& j, std::valarray<T> arr)
-{
- external_constructor<value_t::array>::construct(j, std::move(arr));
-}
-
-template<typename BasicJsonType>
-void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
-{
- external_constructor<value_t::array>::construct(j, std::move(arr));
-}
-
-template<typename BasicJsonType, typename CompatibleObjectType,
- enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0>
-void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
-{
- external_constructor<value_t::object>::construct(j, obj);
-}
-
-template<typename BasicJsonType>
-void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
-{
- external_constructor<value_t::object>::construct(j, std::move(obj));
-}
-
-template<typename BasicJsonType, typename T, std::size_t N,
- enable_if_t<not std::is_constructible<typename BasicJsonType::string_t, T (&)[N]>::value, int> = 0>
-void to_json(BasicJsonType& j, T (&arr)[N])
-{
- external_constructor<value_t::array>::construct(j, arr);
-}
-
-template<typename BasicJsonType, typename... Args>
-void to_json(BasicJsonType& j, const std::pair<Args...>& p)
-{
- j = {p.first, p.second};
-}
-
-template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
-void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...>)
-{
- j = {std::get<Idx>(t)...};
-}
-
-template<typename BasicJsonType, typename... Args>
-void to_json(BasicJsonType& j, const std::tuple<Args...>& t)
-{
- to_json_tuple_impl(j, t, index_sequence_for<Args...> {});
-}
-
-struct to_json_fn
-{
- private:
- template<typename BasicJsonType, typename T>
- auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
- -> decltype(to_json(j, std::forward<T>(val)), void())
- {
- return to_json(j, std::forward<T>(val));
- }
-
- template<typename BasicJsonType, typename T>
- void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept
- {
- static_assert(sizeof(BasicJsonType) == 0,
- "could not find to_json() method in T's namespace");
-
-#ifdef _MSC_VER
- // MSVC does not show a stacktrace for the above assert
- using decayed = uncvref_t<T>;
- static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0,
- "forcing MSVC stacktrace to show which T we're talking about.");
-#endif
- }
-
- public:
- template<typename BasicJsonType, typename T>
- void operator()(BasicJsonType& j, T&& val) const
- noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {})))
- {
- return call(j, std::forward<T>(val), priority_tag<1> {});
- }
-};
-}
-
-/// namespace to hold default `to_json` function
-namespace
-{
-constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
-}
-}
-
-// #include <nlohmann/detail/input/input_adapters.hpp>
-
-
-#include <algorithm> // min
-#include <array> // array
-#include <cassert> // assert
-#include <cstddef> // size_t
-#include <cstring> // strlen
-#include <ios> // streamsize, streamoff, streampos
-#include <istream> // istream
-#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
-#include <memory> // shared_ptr, make_shared, addressof
-#include <numeric> // accumulate
-#include <string> // string, char_traits
-#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
-#include <utility> // pair, declval
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-////////////////////
-// input adapters //
-////////////////////
-
-/*!
-@brief abstract input adapter interface
-
-Produces a stream of std::char_traits<char>::int_type characters from a
-std::istream, a buffer, or some other input type. Accepts the return of exactly
-one non-EOF character for future input. The int_type characters returned
-consist of all valid char values as positive values (typically unsigned char),
-plus an EOF value outside that range, specified by the value of the function
-std::char_traits<char>::eof(). This value is typically -1, but could be any
-arbitrary value which is not a valid char value.
-*/
-struct input_adapter_protocol
-{
- /// get a character [0,255] or std::char_traits<char>::eof().
- virtual std::char_traits<char>::int_type get_character() = 0;
- /// restore the last non-eof() character to input
- virtual void unget_character() = 0;
- virtual ~input_adapter_protocol() = default;
-};
-
-/// a type to simplify interfaces
-using input_adapter_t = std::shared_ptr<input_adapter_protocol>;
-
-/*!
-Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at
-beginning of input. Does not support changing the underlying std::streambuf
-in mid-input. Maintains underlying std::istream and std::streambuf to support
-subsequent use of standard std::istream operations to process any input
-characters following those used in parsing the JSON input. Clears the
-std::istream flags; any input errors (e.g., EOF) will be detected by the first
-subsequent call for input from the std::istream.
-*/
-class input_stream_adapter : public input_adapter_protocol
-{
- public:
- ~input_stream_adapter() override
- {
- // clear stream flags; we use underlying streambuf I/O, do not
- // maintain ifstream flags
- is.clear();
- }
-
- explicit input_stream_adapter(std::istream& i)
- : is(i), sb(*i.rdbuf())
- {
- // skip byte order mark
- std::char_traits<char>::int_type c;
- if ((c = get_character()) == 0xEF)
- {
- if ((c = get_character()) == 0xBB)
- {
- if ((c = get_character()) == 0xBF)
- {
- return; // Ignore BOM
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget();
- }
- is.putback('\xBB');
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget();
- }
- is.putback('\xEF');
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget(); // no byte order mark; process as usual
- }
- }
-
- // delete because of pointer members
- input_stream_adapter(const input_stream_adapter&) = delete;
- input_stream_adapter& operator=(input_stream_adapter&) = delete;
-
- // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
- // ensure that std::char_traits<char>::eof() and the character 0xFF do not
- // end up as the same value, eg. 0xFFFFFFFF.
- std::char_traits<char>::int_type get_character() override
- {
- return sb.sbumpc();
- }
-
- void unget_character() override
- {
- sb.sungetc(); // is.unget() avoided for performance
- }
-
- private:
- /// the associated input stream
- std::istream& is;
- std::streambuf& sb;
-};
-
-/// input adapter for buffer input
-class input_buffer_adapter : public input_adapter_protocol
-{
- public:
- input_buffer_adapter(const char* b, const std::size_t l)
- : cursor(b), limit(b + l), start(b)
- {
- // skip byte order mark
- if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF')
- {
- cursor += 3;
- }
- }
-
- // delete because of pointer members
- input_buffer_adapter(const input_buffer_adapter&) = delete;
- input_buffer_adapter& operator=(input_buffer_adapter&) = delete;
-
- std::char_traits<char>::int_type get_character() noexcept override
- {
- if (JSON_LIKELY(cursor < limit))
- {
- return std::char_traits<char>::to_int_type(*(cursor++));
- }
-
- return std::char_traits<char>::eof();
- }
-
- void unget_character() noexcept override
- {
- if (JSON_LIKELY(cursor > start))
- {
- --cursor;
- }
- }
-
- private:
- /// pointer to the current character
- const char* cursor;
- /// pointer past the last character
- const char* limit;
- /// pointer to the first character
- const char* start;
-};
-
-class input_adapter
-{
- public:
- // native support
-
- /// input adapter for input stream
- input_adapter(std::istream& i)
- : ia(std::make_shared<input_stream_adapter>(i)) {}
-
- /// input adapter for input stream
- input_adapter(std::istream&& i)
- : ia(std::make_shared<input_stream_adapter>(i)) {}
-
- /// input adapter for buffer
- template<typename CharT,
- typename std::enable_if<
- std::is_pointer<CharT>::value and
- std::is_integral<typename std::remove_pointer<CharT>::type>::value and
- sizeof(typename std::remove_pointer<CharT>::type) == 1,
- int>::type = 0>
- input_adapter(CharT b, std::size_t l)
- : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {}
-
- // derived support
-
- /// input adapter for string literal
- template<typename CharT,
- typename std::enable_if<
- std::is_pointer<CharT>::value and
- std::is_integral<typename std::remove_pointer<CharT>::type>::value and
- sizeof(typename std::remove_pointer<CharT>::type) == 1,
- int>::type = 0>
- input_adapter(CharT b)
- : input_adapter(reinterpret_cast<const char*>(b),
- std::strlen(reinterpret_cast<const char*>(b))) {}
-
- /// input adapter for iterator range with contiguous storage
- template<class IteratorType,
- typename std::enable_if<
- std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,
- int>::type = 0>
- input_adapter(IteratorType first, IteratorType last)
- {
- // assertion to check that the iterator range is indeed contiguous,
- // see http://stackoverflow.com/a/35008842/266378 for more discussion
- assert(std::accumulate(
- first, last, std::pair<bool, int>(true, 0),
- [&first](std::pair<bool, int> res, decltype(*first) val)
- {
- res.first &= (val == *(std::next(std::addressof(*first), res.second++)));
- return res;
- }).first);
-
- // assertion to check that each element is 1 byte long
- static_assert(
- sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1,
- "each element in the iterator range must have the size of 1 byte");
-
- const auto len = static_cast<size_t>(std::distance(first, last));
- if (JSON_LIKELY(len > 0))
- {
- // there is at least one element: use the address of first
- ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len);
- }
- else
- {
- // the address of first cannot be used: use nullptr
- ia = std::make_shared<input_buffer_adapter>(nullptr, len);
- }
- }
-
- /// input adapter for array
- template<class T, std::size_t N>
- input_adapter(T (&array)[N])
- : input_adapter(std::begin(array), std::end(array)) {}
-
- /// input adapter for contiguous container
- template<class ContiguousContainer, typename
- std::enable_if<not std::is_pointer<ContiguousContainer>::value and
- std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value,
- int>::type = 0>
- input_adapter(const ContiguousContainer& c)
- : input_adapter(std::begin(c), std::end(c)) {}
-
- operator input_adapter_t()
- {
- return ia;
- }
-
- private:
- /// the actual adapter
- input_adapter_t ia = nullptr;
-};
-}
-}
-
-// #include <nlohmann/detail/input/lexer.hpp>
-
-
-#include <clocale> // localeconv
-#include <cstddef> // size_t
-#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
-#include <initializer_list> // initializer_list
-#include <ios> // hex, uppercase
-#include <iomanip> // setw, setfill
-#include <sstream> // stringstream
-#include <string> // char_traits, string
-#include <vector> // vector
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/input/input_adapters.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-///////////
-// lexer //
-///////////
-
-/*!
-@brief lexical analysis
-
-This class organizes the lexical analysis during JSON deserialization.
-*/
-template<typename BasicJsonType>
-class lexer
-{
- using number_integer_t = typename BasicJsonType::number_integer_t;
- using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
- using number_float_t = typename BasicJsonType::number_float_t;
- using string_t = typename BasicJsonType::string_t;
-
- public:
- /// token types for the parser
- enum class token_type
- {
- uninitialized, ///< indicating the scanner is uninitialized
- literal_true, ///< the `true` literal
- literal_false, ///< the `false` literal
- literal_null, ///< the `null` literal
- value_string, ///< a string -- use get_string() for actual value
- value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value
- value_integer, ///< a signed integer -- use get_number_integer() for actual value
- value_float, ///< an floating point number -- use get_number_float() for actual value
- begin_array, ///< the character for array begin `[`
- begin_object, ///< the character for object begin `{`
- end_array, ///< the character for array end `]`
- end_object, ///< the character for object end `}`
- name_separator, ///< the name separator `:`
- value_separator, ///< the value separator `,`
- parse_error, ///< indicating a parse error
- end_of_input, ///< indicating the end of the input buffer
- literal_or_value ///< a literal or the begin of a value (only for diagnostics)
- };
-
- /// return name of values of type token_type (only used for errors)
- static const char* token_type_name(const token_type t) noexcept
- {
- switch (t)
- {
- case token_type::uninitialized:
- return "<uninitialized>";
- case token_type::literal_true:
- return "true literal";
- case token_type::literal_false:
- return "false literal";
- case token_type::literal_null:
- return "null literal";
- case token_type::value_string:
- return "string literal";
- case lexer::token_type::value_unsigned:
- case lexer::token_type::value_integer:
- case lexer::token_type::value_float:
- return "number literal";
- case token_type::begin_array:
- return "'['";
- case token_type::begin_object:
- return "'{'";
- case token_type::end_array:
- return "']'";
- case token_type::end_object:
- return "'}'";
- case token_type::name_separator:
- return "':'";
- case token_type::value_separator:
- return "','";
- case token_type::parse_error:
- return "<parse error>";
- case token_type::end_of_input:
- return "end of input";
- case token_type::literal_or_value:
- return "'[', '{', or a literal";
- default: // catch non-enum values
- return "unknown token"; // LCOV_EXCL_LINE
- }
- }
-
- explicit lexer(detail::input_adapter_t adapter)
- : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {}
-
- // delete because of pointer members
- lexer(const lexer&) = delete;
- lexer& operator=(lexer&) = delete;
-
- private:
- /////////////////////
- // locales
- /////////////////////
-
- /// return the locale-dependent decimal point
- static char get_decimal_point() noexcept
- {
- const auto loc = localeconv();
- assert(loc != nullptr);
- return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
- }
-
- /////////////////////
- // scan functions
- /////////////////////
-
- /*!
- @brief get codepoint from 4 hex characters following `\u`
-
- For input "\u c1 c2 c3 c4" the codepoint is:
- (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
- = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
-
- Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'
- must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The
- conversion is done by subtracting the offset (0x30, 0x37, and 0x57)
- between the ASCII value of the character and the desired integer value.
-
- @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or
- non-hex character)
- */
- int get_codepoint()
- {
- // this function only makes sense after reading `\u`
- assert(current == 'u');
- int codepoint = 0;
-
- const auto factors = { 12, 8, 4, 0 };
- for (const auto factor : factors)
- {
- get();
-
- if (current >= '0' and current <= '9')
- {
- codepoint += ((current - 0x30) << factor);
- }
- else if (current >= 'A' and current <= 'F')
- {
- codepoint += ((current - 0x37) << factor);
- }
- else if (current >= 'a' and current <= 'f')
- {
- codepoint += ((current - 0x57) << factor);
- }
- else
- {
- return -1;
- }
- }
-
- assert(0x0000 <= codepoint and codepoint <= 0xFFFF);
- return codepoint;
- }
-
- /*!
- @brief check if the next byte(s) are inside a given range
-
- Adds the current byte and, for each passed range, reads a new byte and
- checks if it is inside the range. If a violation was detected, set up an
- error message and return false. Otherwise, return true.
-
- @param[in] ranges list of integers; interpreted as list of pairs of
- inclusive lower and upper bound, respectively
-
- @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,
- 1, 2, or 3 pairs. This precondition is enforced by an assertion.
-
- @return true if and only if no range violation was detected
- */
- bool next_byte_in_range(std::initializer_list<int> ranges)
- {
- assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6);
- add(current);
-
- for (auto range = ranges.begin(); range != ranges.end(); ++range)
- {
- get();
- if (JSON_LIKELY(*range <= current and current <= *(++range)))
- {
- add(current);
- }
- else
- {
- error_message = "invalid string: ill-formed UTF-8 byte";
- return false;
- }
- }
-
- return true;
- }
-
- /*!
- @brief scan a string literal
-
- This function scans a string according to Sect. 7 of RFC 7159. While
- scanning, bytes are escaped and copied into buffer token_buffer. Then the
- function returns successfully, token_buffer is *not* null-terminated (as it
- may contain \0 bytes), and token_buffer.size() is the number of bytes in the
- string.
-
- @return token_type::value_string if string could be successfully scanned,
- token_type::parse_error otherwise
-
- @note In case of errors, variable error_message contains a textual
- description.
- */
- token_type scan_string()
- {
- // reset token_buffer (ignore opening quote)
- reset();
-
- // we entered the function by reading an open quote
- assert(current == '\"');
-
- while (true)
- {
- // get next character
- switch (get())
- {
- // end of file while parsing string
- case std::char_traits<char>::eof():
- {
- error_message = "invalid string: missing closing quote";
- return token_type::parse_error;
- }
-
- // closing quote
- case '\"':
- {
- return token_type::value_string;
- }
-
- // escapes
- case '\\':
- {
- switch (get())
- {
- // quotation mark
- case '\"':
- add('\"');
- break;
- // reverse solidus
- case '\\':
- add('\\');
- break;
- // solidus
- case '/':
- add('/');
- break;
- // backspace
- case 'b':
- add('\b');
- break;
- // form feed
- case 'f':
- add('\f');
- break;
- // line feed
- case 'n':
- add('\n');
- break;
- // carriage return
- case 'r':
- add('\r');
- break;
- // tab
- case 't':
- add('\t');
- break;
-
- // unicode escapes
- case 'u':
- {
- const int codepoint1 = get_codepoint();
- int codepoint = codepoint1; // start with codepoint1
-
- if (JSON_UNLIKELY(codepoint1 == -1))
- {
- error_message = "invalid string: '\\u' must be followed by 4 hex digits";
- return token_type::parse_error;
- }
-
- // check if code point is a high surrogate
- if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF)
- {
- // expect next \uxxxx entry
- if (JSON_LIKELY(get() == '\\' and get() == 'u'))
- {
- const int codepoint2 = get_codepoint();
-
- if (JSON_UNLIKELY(codepoint2 == -1))
- {
- error_message = "invalid string: '\\u' must be followed by 4 hex digits";
- return token_type::parse_error;
- }
-
- // check if codepoint2 is a low surrogate
- if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF))
- {
- // overwrite codepoint
- codepoint =
- // high surrogate occupies the most significant 22 bits
- (codepoint1 << 10)
- // low surrogate occupies the least significant 15 bits
- + codepoint2
- // there is still the 0xD800, 0xDC00 and 0x10000 noise
- // in the result so we have to subtract with:
- // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
- - 0x35FDC00;
- }
- else
- {
- error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
- return token_type::parse_error;
- }
- }
- else
- {
- error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
- return token_type::parse_error;
- }
- }
- else
- {
- if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF))
- {
- error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
- return token_type::parse_error;
- }
- }
-
- // result of the above calculation yields a proper codepoint
- assert(0x00 <= codepoint and codepoint <= 0x10FFFF);
-
- // translate codepoint into bytes
- if (codepoint < 0x80)
- {
- // 1-byte characters: 0xxxxxxx (ASCII)
- add(codepoint);
- }
- else if (codepoint <= 0x7FF)
- {
- // 2-byte characters: 110xxxxx 10xxxxxx
- add(0xC0 | (codepoint >> 6));
- add(0x80 | (codepoint & 0x3F));
- }
- else if (codepoint <= 0xFFFF)
- {
- // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
- add(0xE0 | (codepoint >> 12));
- add(0x80 | ((codepoint >> 6) & 0x3F));
- add(0x80 | (codepoint & 0x3F));
- }
- else
- {
- // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- add(0xF0 | (codepoint >> 18));
- add(0x80 | ((codepoint >> 12) & 0x3F));
- add(0x80 | ((codepoint >> 6) & 0x3F));
- add(0x80 | (codepoint & 0x3F));
- }
-
- break;
- }
-
- // other characters after escape
- default:
- error_message = "invalid string: forbidden character after backslash";
- return token_type::parse_error;
- }
-
- break;
- }
-
- // invalid control characters
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x08:
- case 0x09:
- case 0x0A:
- case 0x0B:
- case 0x0C:
- case 0x0D:
- case 0x0E:
- case 0x0F:
- case 0x10:
- case 0x11:
- case 0x12:
- case 0x13:
- case 0x14:
- case 0x15:
- case 0x16:
- case 0x17:
- case 0x18:
- case 0x19:
- case 0x1A:
- case 0x1B:
- case 0x1C:
- case 0x1D:
- case 0x1E:
- case 0x1F:
- {
- error_message = "invalid string: control character must be escaped";
- return token_type::parse_error;
- }
-
- // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))
- case 0x20:
- case 0x21:
- case 0x23:
- case 0x24:
- case 0x25:
- case 0x26:
- case 0x27:
- case 0x28:
- case 0x29:
- case 0x2A:
- case 0x2B:
- case 0x2C:
- case 0x2D:
- case 0x2E:
- case 0x2F:
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- case 0x38:
- case 0x39:
- case 0x3A:
- case 0x3B:
- case 0x3C:
- case 0x3D:
- case 0x3E:
- case 0x3F:
- case 0x40:
- case 0x41:
- case 0x42:
- case 0x43:
- case 0x44:
- case 0x45:
- case 0x46:
- case 0x47:
- case 0x48:
- case 0x49:
- case 0x4A:
- case 0x4B:
- case 0x4C:
- case 0x4D:
- case 0x4E:
- case 0x4F:
- case 0x50:
- case 0x51:
- case 0x52:
- case 0x53:
- case 0x54:
- case 0x55:
- case 0x56:
- case 0x57:
- case 0x58:
- case 0x59:
- case 0x5A:
- case 0x5B:
- case 0x5D:
- case 0x5E:
- case 0x5F:
- case 0x60:
- case 0x61:
- case 0x62:
- case 0x63:
- case 0x64:
- case 0x65:
- case 0x66:
- case 0x67:
- case 0x68:
- case 0x69:
- case 0x6A:
- case 0x6B:
- case 0x6C:
- case 0x6D:
- case 0x6E:
- case 0x6F:
- case 0x70:
- case 0x71:
- case 0x72:
- case 0x73:
- case 0x74:
- case 0x75:
- case 0x76:
- case 0x77:
- case 0x78:
- case 0x79:
- case 0x7A:
- case 0x7B:
- case 0x7C:
- case 0x7D:
- case 0x7E:
- case 0x7F:
- {
- add(current);
- break;
- }
-
- // U+0080..U+07FF: bytes C2..DF 80..BF
- case 0xC2:
- case 0xC3:
- case 0xC4:
- case 0xC5:
- case 0xC6:
- case 0xC7:
- case 0xC8:
- case 0xC9:
- case 0xCA:
- case 0xCB:
- case 0xCC:
- case 0xCD:
- case 0xCE:
- case 0xCF:
- case 0xD0:
- case 0xD1:
- case 0xD2:
- case 0xD3:
- case 0xD4:
- case 0xD5:
- case 0xD6:
- case 0xD7:
- case 0xD8:
- case 0xD9:
- case 0xDA:
- case 0xDB:
- case 0xDC:
- case 0xDD:
- case 0xDE:
- case 0xDF:
- {
- if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF})))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+0800..U+0FFF: bytes E0 A0..BF 80..BF
- case 0xE0:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF
- // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF
- case 0xE1:
- case 0xE2:
- case 0xE3:
- case 0xE4:
- case 0xE5:
- case 0xE6:
- case 0xE7:
- case 0xE8:
- case 0xE9:
- case 0xEA:
- case 0xEB:
- case 0xEC:
- case 0xEE:
- case 0xEF:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+D000..U+D7FF: bytes ED 80..9F 80..BF
- case 0xED:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
- case 0xF0:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
- case 0xF1:
- case 0xF2:
- case 0xF3:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
- case 0xF4:
- {
- if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
- {
- return token_type::parse_error;
- }
- break;
- }
-
- // remaining bytes (80..C1 and F5..FF) are ill-formed
- default:
- {
- error_message = "invalid string: ill-formed UTF-8 byte";
- return token_type::parse_error;
- }
- }
- }
- }
-
- static void strtof(float& f, const char* str, char** endptr) noexcept
- {
- f = std::strtof(str, endptr);
- }
-
- static void strtof(double& f, const char* str, char** endptr) noexcept
- {
- f = std::strtod(str, endptr);
- }
-
- static void strtof(long double& f, const char* str, char** endptr) noexcept
- {
- f = std::strtold(str, endptr);
- }
-
- /*!
- @brief scan a number literal
-
- This function scans a string according to Sect. 6 of RFC 7159.
-
- The function is realized with a deterministic finite state machine derived
- from the grammar described in RFC 7159. Starting in state "init", the
- input is read and used to determined the next state. Only state "done"
- accepts the number. State "error" is a trap state to model errors. In the
- table below, "anything" means any character but the ones listed before.
-
- state | 0 | 1-9 | e E | + | - | . | anything
- ---------|----------|----------|----------|---------|---------|----------|-----------
- init | zero | any1 | [error] | [error] | minus | [error] | [error]
- minus | zero | any1 | [error] | [error] | [error] | [error] | [error]
- zero | done | done | exponent | done | done | decimal1 | done
- any1 | any1 | any1 | exponent | done | done | decimal1 | done
- decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error]
- decimal2 | decimal2 | decimal2 | exponent | done | done | done | done
- exponent | any2 | any2 | [error] | sign | sign | [error] | [error]
- sign | any2 | any2 | [error] | [error] | [error] | [error] | [error]
- any2 | any2 | any2 | done | done | done | done | done
-
- The state machine is realized with one label per state (prefixed with
- "scan_number_") and `goto` statements between them. The state machine
- contains cycles, but any cycle can be left when EOF is read. Therefore,
- the function is guaranteed to terminate.
-
- During scanning, the read bytes are stored in token_buffer. This string is
- then converted to a signed integer, an unsigned integer, or a
- floating-point number.
-
- @return token_type::value_unsigned, token_type::value_integer, or
- token_type::value_float if number could be successfully scanned,
- token_type::parse_error otherwise
-
- @note The scanner is independent of the current locale. Internally, the
- locale's decimal point is used instead of `.` to work with the
- locale-dependent converters.
- */
- token_type scan_number()
- {
- // reset token_buffer to store the number's bytes
- reset();
-
- // the type of the parsed number; initially set to unsigned; will be
- // changed if minus sign, decimal point or exponent is read
- token_type number_type = token_type::value_unsigned;
-
- // state (init): we just found out we need to scan a number
- switch (current)
- {
- case '-':
- {
- add(current);
- goto scan_number_minus;
- }
-
- case '0':
- {
- add(current);
- goto scan_number_zero;
- }
-
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any1;
- }
-
- default:
- {
- // all other characters are rejected outside scan_number()
- assert(false); // LCOV_EXCL_LINE
- }
- }
-
-scan_number_minus:
- // state: we just parsed a leading minus sign
- number_type = token_type::value_integer;
- switch (get())
- {
- case '0':
- {
- add(current);
- goto scan_number_zero;
- }
-
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any1;
- }
-
- default:
- {
- error_message = "invalid number; expected digit after '-'";
- return token_type::parse_error;
- }
- }
-
-scan_number_zero:
- // state: we just parse a zero (maybe with a leading minus sign)
- switch (get())
- {
- case '.':
- {
- add(decimal_point_char);
- goto scan_number_decimal1;
- }
-
- case 'e':
- case 'E':
- {
- add(current);
- goto scan_number_exponent;
- }
-
- default:
- goto scan_number_done;
- }
-
-scan_number_any1:
- // state: we just parsed a number 0-9 (maybe with a leading minus sign)
- switch (get())
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any1;
- }
-
- case '.':
- {
- add(decimal_point_char);
- goto scan_number_decimal1;
- }
-
- case 'e':
- case 'E':
- {
- add(current);
- goto scan_number_exponent;
- }
-
- default:
- goto scan_number_done;
- }
-
-scan_number_decimal1:
- // state: we just parsed a decimal point
- number_type = token_type::value_float;
- switch (get())
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_decimal2;
- }
-
- default:
- {
- error_message = "invalid number; expected digit after '.'";
- return token_type::parse_error;
- }
- }
-
-scan_number_decimal2:
- // we just parsed at least one number after a decimal point
- switch (get())
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_decimal2;
- }
-
- case 'e':
- case 'E':
- {
- add(current);
- goto scan_number_exponent;
- }
-
- default:
- goto scan_number_done;
- }
-
-scan_number_exponent:
- // we just parsed an exponent
- number_type = token_type::value_float;
- switch (get())
- {
- case '+':
- case '-':
- {
- add(current);
- goto scan_number_sign;
- }
-
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any2;
- }
-
- default:
- {
- error_message =
- "invalid number; expected '+', '-', or digit after exponent";
- return token_type::parse_error;
- }
- }
-
-scan_number_sign:
- // we just parsed an exponent sign
- switch (get())
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any2;
- }
-
- default:
- {
- error_message = "invalid number; expected digit after exponent sign";
- return token_type::parse_error;
- }
- }
-
-scan_number_any2:
- // we just parsed a number after the exponent or exponent sign
- switch (get())
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- add(current);
- goto scan_number_any2;
- }
-
- default:
- goto scan_number_done;
- }
-
-scan_number_done:
- // unget the character after the number (we only read it to know that
- // we are done scanning a number)
- unget();
-
- char* endptr = nullptr;
- errno = 0;
-
- // try to parse integers first and fall back to floats
- if (number_type == token_type::value_unsigned)
- {
- const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
-
- // we checked the number format before
- assert(endptr == token_buffer.data() + token_buffer.size());
-
- if (errno == 0)
- {
- value_unsigned = static_cast<number_unsigned_t>(x);
- if (value_unsigned == x)
- {
- return token_type::value_unsigned;
- }
- }
- }
- else if (number_type == token_type::value_integer)
- {
- const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
-
- // we checked the number format before
- assert(endptr == token_buffer.data() + token_buffer.size());
-
- if (errno == 0)
- {
- value_integer = static_cast<number_integer_t>(x);
- if (value_integer == x)
- {
- return token_type::value_integer;
- }
- }
- }
-
- // this code is reached if we parse a floating-point number or if an
- // integer conversion above failed
- strtof(value_float, token_buffer.data(), &endptr);
-
- // we checked the number format before
- assert(endptr == token_buffer.data() + token_buffer.size());
-
- return token_type::value_float;
- }
-
- /*!
- @param[in] literal_text the literal text to expect
- @param[in] length the length of the passed literal text
- @param[in] return_type the token type to return on success
- */
- token_type scan_literal(const char* literal_text, const std::size_t length,
- token_type return_type)
- {
- assert(current == literal_text[0]);
- for (std::size_t i = 1; i < length; ++i)
- {
- if (JSON_UNLIKELY(get() != literal_text[i]))
- {
- error_message = "invalid literal";
- return token_type::parse_error;
- }
- }
- return return_type;
- }
-
- /////////////////////
- // input management
- /////////////////////
-
- /// reset token_buffer; current character is beginning of token
- void reset() noexcept
- {
- token_buffer.clear();
- token_string.clear();
- token_string.push_back(std::char_traits<char>::to_char_type(current));
- }
-
- /*
- @brief get next character from the input
-
- This function provides the interface to the used input adapter. It does
- not throw in case the input reached EOF, but returns a
- `std::char_traits<char>::eof()` in that case. Stores the scanned characters
- for use in error messages.
-
- @return character read from the input
- */
- std::char_traits<char>::int_type get()
- {
- ++chars_read;
- current = ia->get_character();
- if (JSON_LIKELY(current != std::char_traits<char>::eof()))
- {
- token_string.push_back(std::char_traits<char>::to_char_type(current));
- }
- return current;
- }
-
- /// unget current character (return it again on next get)
- void unget()
- {
- --chars_read;
- if (JSON_LIKELY(current != std::char_traits<char>::eof()))
- {
- ia->unget_character();
- assert(token_string.size() != 0);
- token_string.pop_back();
- }
- }
-
- /// add a character to token_buffer
- void add(int c)
- {
- token_buffer.push_back(std::char_traits<char>::to_char_type(c));
- }
-
- public:
- /////////////////////
- // value getters
- /////////////////////
-
- /// return integer value
- constexpr number_integer_t get_number_integer() const noexcept
- {
- return value_integer;
- }
-
- /// return unsigned integer value
- constexpr number_unsigned_t get_number_unsigned() const noexcept
- {
- return value_unsigned;
- }
-
- /// return floating-point value
- constexpr number_float_t get_number_float() const noexcept
- {
- return value_float;
- }
-
- /// return current string value (implicitly resets the token; useful only once)
- string_t&& move_string()
- {
- return std::move(token_buffer);
- }
-
- /////////////////////
- // diagnostics
- /////////////////////
-
- /// return position of last read token
- constexpr std::size_t get_position() const noexcept
- {
- return chars_read;
- }
-
- /// return the last read token (for errors only). Will never contain EOF
- /// (an arbitrary value that is not a valid char value, often -1), because
- /// 255 may legitimately occur. May contain NUL, which should be escaped.
- std::string get_token_string() const
- {
- // escape control characters
- std::string result;
- for (const auto c : token_string)
- {
- if ('\x00' <= c and c <= '\x1F')
- {
- // escape control characters
- std::stringstream ss;
- ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0')
- << std::hex << static_cast<int>(c) << ">";
- result += ss.str();
- }
- else
- {
- // add character as is
- result.push_back(c);
- }
- }
-
- return result;
- }
-
- /// return syntax error message
- constexpr const char* get_error_message() const noexcept
- {
- return error_message;
- }
-
- /////////////////////
- // actual scanner
- /////////////////////
-
- token_type scan()
- {
- // read next character and ignore whitespace
- do
- {
- get();
- }
- while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
-
- switch (current)
- {
- // structural characters
- case '[':
- return token_type::begin_array;
- case ']':
- return token_type::end_array;
- case '{':
- return token_type::begin_object;
- case '}':
- return token_type::end_object;
- case ':':
- return token_type::name_separator;
- case ',':
- return token_type::value_separator;
-
- // literals
- case 't':
- return scan_literal("true", 4, token_type::literal_true);
- case 'f':
- return scan_literal("false", 5, token_type::literal_false);
- case 'n':
- return scan_literal("null", 4, token_type::literal_null);
-
- // string
- case '\"':
- return scan_string();
-
- // number
- case '-':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- return scan_number();
-
- // end of input (the null byte is needed when parsing from
- // string literals)
- case '\0':
- case std::char_traits<char>::eof():
- return token_type::end_of_input;
-
- // error
- default:
- error_message = "invalid literal";
- return token_type::parse_error;
- }
- }
-
- private:
- /// input adapter
- detail::input_adapter_t ia = nullptr;
-
- /// the current character
- std::char_traits<char>::int_type current = std::char_traits<char>::eof();
-
- /// the number of characters read
- std::size_t chars_read = 0;
-
- /// raw input token string (for error messages)
- std::vector<char> token_string {};
-
- /// buffer for variable-length tokens (numbers, strings)
- string_t token_buffer {};
-
- /// a description of occurred lexer errors
- const char* error_message = "";
-
- // number values
- number_integer_t value_integer = 0;
- number_unsigned_t value_unsigned = 0;
- number_float_t value_float = 0;
-
- /// the decimal point
- const char decimal_point_char = '.';
-};
-}
-}
-
-// #include <nlohmann/detail/input/parser.hpp>
-
-
-#include <cassert> // assert
-#include <cmath> // isfinite
-#include <cstdint> // uint8_t
-#include <functional> // function
-#include <string> // string
-#include <utility> // move
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/input/input_adapters.hpp>
-
-// #include <nlohmann/detail/input/lexer.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-////////////
-// parser //
-////////////
-
-/*!
-@brief syntax analysis
-
-This class implements a recursive decent parser.
-*/
-template<typename BasicJsonType>
-class parser
-{
- using number_integer_t = typename BasicJsonType::number_integer_t;
- using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
- using number_float_t = typename BasicJsonType::number_float_t;
- using string_t = typename BasicJsonType::string_t;
- using lexer_t = lexer<BasicJsonType>;
- using token_type = typename lexer_t::token_type;
-
- public:
- enum class parse_event_t : uint8_t
- {
- /// the parser read `{` and started to process a JSON object
- object_start,
- /// the parser read `}` and finished processing a JSON object
- object_end,
- /// the parser read `[` and started to process a JSON array
- array_start,
- /// the parser read `]` and finished processing a JSON array
- array_end,
- /// the parser read a key of a value in an object
- key,
- /// the parser finished reading a JSON value
- value
- };
-
- using parser_callback_t =
- std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
-
- /// a parser reading from an input adapter
- explicit parser(detail::input_adapter_t adapter,
- const parser_callback_t cb = nullptr,
- const bool allow_exceptions_ = true)
- : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_)
- {}
-
- /*!
- @brief public parser interface
-
- @param[in] strict whether to expect the last token to be EOF
- @param[in,out] result parsed JSON value
-
- @throw parse_error.101 in case of an unexpected token
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
- */
- void parse(const bool strict, BasicJsonType& result)
- {
- // read first token
- get_token();
-
- parse_internal(true, result);
- result.assert_invariant();
-
- // in strict mode, input must be completely read
- if (strict)
- {
- get_token();
- expect(token_type::end_of_input);
- }
-
- // in case of an error, return discarded value
- if (errored)
- {
- result = value_t::discarded;
- return;
- }
-
- // set top-level value to null if it was discarded by the callback
- // function
- if (result.is_discarded())
- {
- result = nullptr;
- }
- }
-
- /*!
- @brief public accept interface
-
- @param[in] strict whether to expect the last token to be EOF
- @return whether the input is a proper JSON text
- */
- bool accept(const bool strict = true)
- {
- // read first token
- get_token();
-
- if (not accept_internal())
- {
- return false;
- }
-
- // strict => last token must be EOF
- return not strict or (get_token() == token_type::end_of_input);
- }
-
- private:
- /*!
- @brief the actual parser
- @throw parse_error.101 in case of an unexpected token
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
- */
- void parse_internal(bool keep, BasicJsonType& result)
- {
- // never parse after a parse error was detected
- assert(not errored);
-
- // start with a discarded value
- if (not result.is_discarded())
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
-
- switch (last_token)
- {
- case token_type::begin_object:
- {
- if (keep)
- {
- if (callback)
- {
- keep = callback(depth++, parse_event_t::object_start, result);
- }
-
- if (not callback or keep)
- {
- // explicitly set result to object to cope with {}
- result.m_type = value_t::object;
- result.m_value = value_t::object;
- }
- }
-
- // read next token
- get_token();
-
- // closing } -> we are done
- if (last_token == token_type::end_object)
- {
- if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- // parse values
- string_t key;
- BasicJsonType value;
- while (true)
- {
- // store key
- if (not expect(token_type::value_string))
- {
- return;
- }
- key = m_lexer.move_string();
-
- bool keep_tag = false;
- if (keep)
- {
- if (callback)
- {
- BasicJsonType k(key);
- keep_tag = callback(depth, parse_event_t::key, k);
- }
- else
- {
- keep_tag = true;
- }
- }
-
- // parse separator (:)
- get_token();
- if (not expect(token_type::name_separator))
- {
- return;
- }
-
- // parse and add value
- get_token();
- value.m_value.destroy(value.m_type);
- value.m_type = value_t::discarded;
- parse_internal(keep, value);
-
- if (JSON_UNLIKELY(errored))
- {
- return;
- }
-
- if (keep and keep_tag and not value.is_discarded())
- {
- result.m_value.object->emplace(std::move(key), std::move(value));
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing }
- if (not expect(token_type::end_object))
- {
- return;
- }
- break;
- }
-
- if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- case token_type::begin_array:
- {
- if (keep)
- {
- if (callback)
- {
- keep = callback(depth++, parse_event_t::array_start, result);
- }
-
- if (not callback or keep)
- {
- // explicitly set result to array to cope with []
- result.m_type = value_t::array;
- result.m_value = value_t::array;
- }
- }
-
- // read next token
- get_token();
-
- // closing ] -> we are done
- if (last_token == token_type::end_array)
- {
- if (callback and not callback(--depth, parse_event_t::array_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- // parse values
- BasicJsonType value;
- while (true)
- {
- // parse value
- value.m_value.destroy(value.m_type);
- value.m_type = value_t::discarded;
- parse_internal(keep, value);
-
- if (JSON_UNLIKELY(errored))
- {
- return;
- }
-
- if (keep and not value.is_discarded())
- {
- result.m_value.array->push_back(std::move(value));
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing ]
- if (not expect(token_type::end_array))
- {
- return;
- }
- break;
- }
-
- if (keep and callback and not callback(--depth, parse_event_t::array_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- case token_type::literal_null:
- {
- result.m_type = value_t::null;
- break;
- }
-
- case token_type::value_string:
- {
- result.m_type = value_t::string;
- result.m_value = m_lexer.move_string();
- break;
- }
-
- case token_type::literal_true:
- {
- result.m_type = value_t::boolean;
- result.m_value = true;
- break;
- }
-
- case token_type::literal_false:
- {
- result.m_type = value_t::boolean;
- result.m_value = false;
- break;
- }
-
- case token_type::value_unsigned:
- {
- result.m_type = value_t::number_unsigned;
- result.m_value = m_lexer.get_number_unsigned();
- break;
- }
-
- case token_type::value_integer:
- {
- result.m_type = value_t::number_integer;
- result.m_value = m_lexer.get_number_integer();
- break;
- }
-
- case token_type::value_float:
- {
- result.m_type = value_t::number_float;
- result.m_value = m_lexer.get_number_float();
-
- // throw in case of infinity or NAN
- if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float)))
- {
- if (allow_exceptions)
- {
- JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
- m_lexer.get_token_string() + "'"));
- }
- expect(token_type::uninitialized);
- }
- break;
- }
-
- case token_type::parse_error:
- {
- // using "uninitialized" to avoid "expected" message
- if (not expect(token_type::uninitialized))
- {
- return;
- }
- break; // LCOV_EXCL_LINE
- }
-
- default:
- {
- // the last token was unexpected; we expected a value
- if (not expect(token_type::literal_or_value))
- {
- return;
- }
- break; // LCOV_EXCL_LINE
- }
- }
-
- if (keep and callback and not callback(depth, parse_event_t::value, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- }
-
- /*!
- @brief the actual acceptor
-
- @invariant 1. The last token is not yet processed. Therefore, the caller
- of this function must make sure a token has been read.
- 2. When this function returns, the last token is processed.
- That is, the last read character was already considered.
-
- This invariant makes sure that no token needs to be "unput".
- */
- bool accept_internal()
- {
- switch (last_token)
- {
- case token_type::begin_object:
- {
- // read next token
- get_token();
-
- // closing } -> we are done
- if (last_token == token_type::end_object)
- {
- return true;
- }
-
- // parse values
- while (true)
- {
- // parse key
- if (last_token != token_type::value_string)
- {
- return false;
- }
-
- // parse separator (:)
- get_token();
- if (last_token != token_type::name_separator)
- {
- return false;
- }
-
- // parse value
- get_token();
- if (not accept_internal())
- {
- return false;
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing }
- return (last_token == token_type::end_object);
- }
- }
-
- case token_type::begin_array:
- {
- // read next token
- get_token();
-
- // closing ] -> we are done
- if (last_token == token_type::end_array)
- {
- return true;
- }
-
- // parse values
- while (true)
- {
- // parse value
- if (not accept_internal())
- {
- return false;
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing ]
- return (last_token == token_type::end_array);
- }
- }
-
- case token_type::value_float:
- {
- // reject infinity or NAN
- return std::isfinite(m_lexer.get_number_float());
- }
-
- case token_type::literal_false:
- case token_type::literal_null:
- case token_type::literal_true:
- case token_type::value_integer:
- case token_type::value_string:
- case token_type::value_unsigned:
- return true;
-
- default: // the last token was unexpected
- return false;
- }
- }
-
- /// get next token from lexer
- token_type get_token()
- {
- return (last_token = m_lexer.scan());
- }
-
- /*!
- @throw parse_error.101 if expected token did not occur
- */
- bool expect(token_type t)
- {
- if (JSON_UNLIKELY(t != last_token))
- {
- errored = true;
- expected = t;
- if (allow_exceptions)
- {
- throw_exception();
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
-
- [[noreturn]] void throw_exception() const
- {
- std::string error_msg = "syntax error - ";
- if (last_token == token_type::parse_error)
- {
- error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" +
- m_lexer.get_token_string() + "'";
- }
- else
- {
- error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token));
- }
-
- if (expected != token_type::uninitialized)
- {
- error_msg += "; expected " + std::string(lexer_t::token_type_name(expected));
- }
-
- JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg));
- }
-
- private:
- /// current level of recursion
- int depth = 0;
- /// callback function
- const parser_callback_t callback = nullptr;
- /// the type of the last read token
- token_type last_token = token_type::uninitialized;
- /// the lexer
- lexer_t m_lexer;
- /// whether a syntax error occurred
- bool errored = false;
- /// possible reason for the syntax error
- token_type expected = token_type::uninitialized;
- /// whether to throw exceptions in case of errors
- const bool allow_exceptions = true;
-};
-}
-}
-
-// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
-
-
-#include <cstddef> // ptrdiff_t
-#include <limits> // numeric_limits
-
-namespace nlohmann
-{
-namespace detail
-{
-/*
-@brief an iterator for primitive JSON types
-
-This class models an iterator for primitive JSON types (boolean, number,
-string). It's only purpose is to allow the iterator/const_iterator classes
-to "iterate" over primitive values. Internally, the iterator is modeled by
-a `difference_type` variable. Value begin_value (`0`) models the begin,
-end_value (`1`) models past the end.
-*/
-class primitive_iterator_t
-{
- private:
- using difference_type = std::ptrdiff_t;
- static constexpr difference_type begin_value = 0;
- static constexpr difference_type end_value = begin_value + 1;
-
- /// iterator as signed integer type
- difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();
-
- public:
- constexpr difference_type get_value() const noexcept
- {
- return m_it;
- }
-
- /// set iterator to a defined beginning
- void set_begin() noexcept
- {
- m_it = begin_value;
- }
-
- /// set iterator to a defined past the end
- void set_end() noexcept
- {
- m_it = end_value;
- }
-
- /// return whether the iterator can be dereferenced
- constexpr bool is_begin() const noexcept
- {
- return m_it == begin_value;
- }
-
- /// return whether the iterator is at end
- constexpr bool is_end() const noexcept
- {
- return m_it == end_value;
- }
-
- friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
- {
- return lhs.m_it == rhs.m_it;
- }
-
- friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
- {
- return lhs.m_it < rhs.m_it;
- }
-
- primitive_iterator_t operator+(difference_type n) noexcept
- {
- auto result = *this;
- result += n;
- return result;
- }
-
- friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
- {
- return lhs.m_it - rhs.m_it;
- }
-
- primitive_iterator_t& operator++() noexcept
- {
- ++m_it;
- return *this;
- }
-
- primitive_iterator_t const operator++(int) noexcept
- {
- auto result = *this;
- m_it++;
- return result;
- }
-
- primitive_iterator_t& operator--() noexcept
- {
- --m_it;
- return *this;
- }
-
- primitive_iterator_t const operator--(int) noexcept
- {
- auto result = *this;
- m_it--;
- return result;
- }
-
- primitive_iterator_t& operator+=(difference_type n) noexcept
- {
- m_it += n;
- return *this;
- }
-
- primitive_iterator_t& operator-=(difference_type n) noexcept
- {
- m_it -= n;
- return *this;
- }
-};
-}
-}
-
-// #include <nlohmann/detail/iterators/internal_iterator.hpp>
-
-
-// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-/*!
-@brief an iterator value
-
-@note This structure could easily be a union, but MSVC currently does not allow
-unions members with complex constructors, see https://github.com/nlohmann/json/pull/105.
-*/
-template<typename BasicJsonType> struct internal_iterator
-{
- /// iterator for JSON objects
- typename BasicJsonType::object_t::iterator object_iterator {};
- /// iterator for JSON arrays
- typename BasicJsonType::array_t::iterator array_iterator {};
- /// generic iterator for all other types
- primitive_iterator_t primitive_iterator {};
-};
-}
-}
-
-// #include <nlohmann/detail/iterators/iter_impl.hpp>
-
-
-#include <ciso646> // not
-#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next
-#include <type_traits> // conditional, is_const, remove_const
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/iterators/internal_iterator.hpp>
-
-// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/meta.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-// forward declare, to be able to friend it later on
-template<typename IteratorType> class iteration_proxy;
-
-/*!
-@brief a template for a bidirectional iterator for the @ref basic_json class
-
-This class implements a both iterators (iterator and const_iterator) for the
-@ref basic_json class.
-
-@note An iterator is called *initialized* when a pointer to a JSON value has
- been set (e.g., by a constructor or a copy assignment). If the iterator is
- default-constructed, it is *uninitialized* and most methods are undefined.
- **The library uses assertions to detect calls on uninitialized iterators.**
-
-@requirement The class satisfies the following concept requirements:
--
-[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator):
- The iterator that can be moved can be moved in both directions (i.e.
- incremented and decremented).
-
-@since version 1.0.0, simplified in version 2.0.9, change to bidirectional
- iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)
-*/
-template<typename BasicJsonType>
-class iter_impl
-{
- /// allow basic_json to access private members
- friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;
- friend BasicJsonType;
- friend iteration_proxy<iter_impl>;
-
- using object_t = typename BasicJsonType::object_t;
- using array_t = typename BasicJsonType::array_t;
- // make sure BasicJsonType is basic_json or const basic_json
- static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
- "iter_impl only accepts (const) basic_json");
-
- public:
-
- /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
- /// The C++ Standard has never required user-defined iterators to derive from std::iterator.
- /// A user-defined iterator should provide publicly accessible typedefs named
- /// iterator_category, value_type, difference_type, pointer, and reference.
- /// Note that value_type is required to be non-const, even for constant iterators.
- using iterator_category = std::bidirectional_iterator_tag;
-
- /// the type of the values when the iterator is dereferenced
- using value_type = typename BasicJsonType::value_type;
- /// a type to represent differences between iterators
- using difference_type = typename BasicJsonType::difference_type;
- /// defines a pointer to the type iterated over (value_type)
- using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,
- typename BasicJsonType::const_pointer,
- typename BasicJsonType::pointer>::type;
- /// defines a reference to the type iterated over (value_type)
- using reference =
- typename std::conditional<std::is_const<BasicJsonType>::value,
- typename BasicJsonType::const_reference,
- typename BasicJsonType::reference>::type;
-
- /// default constructor
- iter_impl() = default;
-
- /*!
- @brief constructor for a given JSON instance
- @param[in] object pointer to a JSON object for this iterator
- @pre object != nullptr
- @post The iterator is initialized; i.e. `m_object != nullptr`.
- */
- explicit iter_impl(pointer object) noexcept : m_object(object)
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- m_it.object_iterator = typename object_t::iterator();
- break;
- }
-
- case value_t::array:
- {
- m_it.array_iterator = typename array_t::iterator();
- break;
- }
-
- default:
- {
- m_it.primitive_iterator = primitive_iterator_t();
- break;
- }
- }
- }
-
- /*!
- @note The conventional copy constructor and copy assignment are implicitly
- defined. Combined with the following converting constructor and
- assignment, they support: (1) copy from iterator to iterator, (2)
- copy from const iterator to const iterator, and (3) conversion from
- iterator to const iterator. However conversion from const iterator
- to iterator is not defined.
- */
-
- /*!
- @brief converting constructor
- @param[in] other non-const iterator to copy from
- @note It is not checked whether @a other is initialized.
- */
- iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
- : m_object(other.m_object), m_it(other.m_it) {}
-
- /*!
- @brief converting assignment
- @param[in,out] other non-const iterator to copy from
- @return const/non-const iterator
- @note It is not checked whether @a other is initialized.
- */
- iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
- {
- m_object = other.m_object;
- m_it = other.m_it;
- return *this;
- }
-
- private:
- /*!
- @brief set the iterator to the first value
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- void set_begin() noexcept
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- m_it.object_iterator = m_object->m_value.object->begin();
- break;
- }
-
- case value_t::array:
- {
- m_it.array_iterator = m_object->m_value.array->begin();
- break;
- }
-
- case value_t::null:
- {
- // set to end so begin()==end() is true: null is empty
- m_it.primitive_iterator.set_end();
- break;
- }
-
- default:
- {
- m_it.primitive_iterator.set_begin();
- break;
- }
- }
- }
-
- /*!
- @brief set the iterator past the last value
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- void set_end() noexcept
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- m_it.object_iterator = m_object->m_value.object->end();
- break;
- }
-
- case value_t::array:
- {
- m_it.array_iterator = m_object->m_value.array->end();
- break;
- }
-
- default:
- {
- m_it.primitive_iterator.set_end();
- break;
- }
- }
- }
-
- public:
- /*!
- @brief return a reference to the value pointed to by the iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- reference operator*() const
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- assert(m_it.object_iterator != m_object->m_value.object->end());
- return m_it.object_iterator->second;
- }
-
- case value_t::array:
- {
- assert(m_it.array_iterator != m_object->m_value.array->end());
- return *m_it.array_iterator;
- }
-
- case value_t::null:
- JSON_THROW(invalid_iterator::create(214, "cannot get value"));
-
- default:
- {
- if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
- {
- return *m_object;
- }
-
- JSON_THROW(invalid_iterator::create(214, "cannot get value"));
- }
- }
- }
-
- /*!
- @brief dereference the iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- pointer operator->() const
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- assert(m_it.object_iterator != m_object->m_value.object->end());
- return &(m_it.object_iterator->second);
- }
-
- case value_t::array:
- {
- assert(m_it.array_iterator != m_object->m_value.array->end());
- return &*m_it.array_iterator;
- }
-
- default:
- {
- if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
- {
- return m_object;
- }
-
- JSON_THROW(invalid_iterator::create(214, "cannot get value"));
- }
- }
- }
-
- /*!
- @brief post-increment (it++)
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl const operator++(int)
- {
- auto result = *this;
- ++(*this);
- return result;
- }
-
- /*!
- @brief pre-increment (++it)
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl& operator++()
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- std::advance(m_it.object_iterator, 1);
- break;
- }
-
- case value_t::array:
- {
- std::advance(m_it.array_iterator, 1);
- break;
- }
-
- default:
- {
- ++m_it.primitive_iterator;
- break;
- }
- }
-
- return *this;
- }
-
- /*!
- @brief post-decrement (it--)
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl const operator--(int)
- {
- auto result = *this;
- --(*this);
- return result;
- }
-
- /*!
- @brief pre-decrement (--it)
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl& operator--()
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- {
- std::advance(m_it.object_iterator, -1);
- break;
- }
-
- case value_t::array:
- {
- std::advance(m_it.array_iterator, -1);
- break;
- }
-
- default:
- {
- --m_it.primitive_iterator;
- break;
- }
- }
-
- return *this;
- }
-
- /*!
- @brief comparison: equal
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator==(const iter_impl& other) const
- {
- // if objects are not the same, the comparison is undefined
- if (JSON_UNLIKELY(m_object != other.m_object))
- {
- JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
- }
-
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- return (m_it.object_iterator == other.m_it.object_iterator);
-
- case value_t::array:
- return (m_it.array_iterator == other.m_it.array_iterator);
-
- default:
- return (m_it.primitive_iterator == other.m_it.primitive_iterator);
- }
- }
-
- /*!
- @brief comparison: not equal
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator!=(const iter_impl& other) const
- {
- return not operator==(other);
- }
-
- /*!
- @brief comparison: smaller
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator<(const iter_impl& other) const
- {
- // if objects are not the same, the comparison is undefined
- if (JSON_UNLIKELY(m_object != other.m_object))
- {
- JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
- }
-
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators"));
-
- case value_t::array:
- return (m_it.array_iterator < other.m_it.array_iterator);
-
- default:
- return (m_it.primitive_iterator < other.m_it.primitive_iterator);
- }
- }
-
- /*!
- @brief comparison: less than or equal
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator<=(const iter_impl& other) const
- {
- return not other.operator < (*this);
- }
-
- /*!
- @brief comparison: greater than
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator>(const iter_impl& other) const
- {
- return not operator<=(other);
- }
-
- /*!
- @brief comparison: greater than or equal
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- bool operator>=(const iter_impl& other) const
- {
- return not operator<(other);
- }
-
- /*!
- @brief add to iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl& operator+=(difference_type i)
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
-
- case value_t::array:
- {
- std::advance(m_it.array_iterator, i);
- break;
- }
-
- default:
- {
- m_it.primitive_iterator += i;
- break;
- }
- }
-
- return *this;
- }
-
- /*!
- @brief subtract from iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl& operator-=(difference_type i)
- {
- return operator+=(-i);
- }
-
- /*!
- @brief add to iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl operator+(difference_type i) const
- {
- auto result = *this;
- result += i;
- return result;
- }
-
- /*!
- @brief addition of distance and iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- friend iter_impl operator+(difference_type i, const iter_impl& it)
- {
- auto result = it;
- result += i;
- return result;
- }
-
- /*!
- @brief subtract from iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- iter_impl operator-(difference_type i) const
- {
- auto result = *this;
- result -= i;
- return result;
- }
-
- /*!
- @brief return difference
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- difference_type operator-(const iter_impl& other) const
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
-
- case value_t::array:
- return m_it.array_iterator - other.m_it.array_iterator;
-
- default:
- return m_it.primitive_iterator - other.m_it.primitive_iterator;
- }
- }
-
- /*!
- @brief access to successor
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- reference operator[](difference_type n) const
- {
- assert(m_object != nullptr);
-
- switch (m_object->m_type)
- {
- case value_t::object:
- JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators"));
-
- case value_t::array:
- return *std::next(m_it.array_iterator, n);
-
- case value_t::null:
- JSON_THROW(invalid_iterator::create(214, "cannot get value"));
-
- default:
- {
- if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n))
- {
- return *m_object;
- }
-
- JSON_THROW(invalid_iterator::create(214, "cannot get value"));
- }
- }
- }
-
- /*!
- @brief return the key of an object iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- typename object_t::key_type key() const
- {
- assert(m_object != nullptr);
-
- if (JSON_LIKELY(m_object->is_object()))
- {
- return m_it.object_iterator->first;
- }
-
- JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators"));
- }
-
- /*!
- @brief return the value of an iterator
- @pre The iterator is initialized; i.e. `m_object != nullptr`.
- */
- reference value() const
- {
- return operator*();
- }
-
- private:
- /// associated JSON instance
- pointer m_object = nullptr;
- /// the actual iterator of the associated instance
- internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it;
-};
-}
-}
-
-// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
-
-
-#include <cstddef> // size_t
-#include <string> // string, to_string
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-/// proxy class for the items() function
-template<typename IteratorType> class iteration_proxy
-{
- private:
- /// helper class for iteration
- class iteration_proxy_internal
- {
- private:
- /// the iterator
- IteratorType anchor;
- /// an index for arrays (used to create key names)
- std::size_t array_index = 0;
-
- public:
- explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
-
- /// dereference operator (needed for range-based for)
- iteration_proxy_internal& operator*()
- {
- return *this;
- }
-
- /// increment operator (needed for range-based for)
- iteration_proxy_internal& operator++()
- {
- ++anchor;
- ++array_index;
-
- return *this;
- }
-
- /// inequality operator (needed for range-based for)
- bool operator!=(const iteration_proxy_internal& o) const noexcept
- {
- return anchor != o.anchor;
- }
-
- /// return key of the iterator
- std::string key() const
- {
- assert(anchor.m_object != nullptr);
-
- switch (anchor.m_object->type())
- {
- // use integer array index as key
- case value_t::array:
- return std::to_string(array_index);
-
- // use key from the object
- case value_t::object:
- return anchor.key();
-
- // use an empty key for all primitive types
- default:
- return "";
- }
- }
-
- /// return value of the iterator
- typename IteratorType::reference value() const
- {
- return anchor.value();
- }
- };
-
- /// the container to iterate
- typename IteratorType::reference container;
-
- public:
- /// construct iteration proxy from a container
- explicit iteration_proxy(typename IteratorType::reference cont) noexcept
- : container(cont) {}
-
- /// return iterator begin (needed for range-based for)
- iteration_proxy_internal begin() noexcept
- {
- return iteration_proxy_internal(container.begin());
- }
-
- /// return iterator end (needed for range-based for)
- iteration_proxy_internal end() noexcept
- {
- return iteration_proxy_internal(container.end());
- }
-};
-}
-}
-
-// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>
-
-
-#include <cstddef> // ptrdiff_t
-#include <iterator> // reverse_iterator
-#include <utility> // declval
-
-namespace nlohmann
-{
-namespace detail
-{
-//////////////////////
-// reverse_iterator //
-//////////////////////
-
-/*!
-@brief a template for a reverse iterator class
-
-@tparam Base the base iterator type to reverse. Valid types are @ref
-iterator (to create @ref reverse_iterator) and @ref const_iterator (to
-create @ref const_reverse_iterator).
-
-@requirement The class satisfies the following concept requirements:
--
-[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator):
- The iterator that can be moved can be moved in both directions (i.e.
- incremented and decremented).
-- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator):
- It is possible to write to the pointed-to element (only if @a Base is
- @ref iterator).
-
-@since version 1.0.0
-*/
-template<typename Base>
-class json_reverse_iterator : public std::reverse_iterator<Base>
-{
- public:
- using difference_type = std::ptrdiff_t;
- /// shortcut to the reverse iterator adapter
- using base_iterator = std::reverse_iterator<Base>;
- /// the reference type for the pointed-to element
- using reference = typename Base::reference;
-
- /// create reverse iterator from iterator
- json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept
- : base_iterator(it) {}
-
- /// create reverse iterator from base class
- json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}
-
- /// post-increment (it++)
- json_reverse_iterator const operator++(int)
- {
- return static_cast<json_reverse_iterator>(base_iterator::operator++(1));
- }
-
- /// pre-increment (++it)
- json_reverse_iterator& operator++()
- {
- return static_cast<json_reverse_iterator&>(base_iterator::operator++());
- }
-
- /// post-decrement (it--)
- json_reverse_iterator const operator--(int)
- {
- return static_cast<json_reverse_iterator>(base_iterator::operator--(1));
- }
-
- /// pre-decrement (--it)
- json_reverse_iterator& operator--()
- {
- return static_cast<json_reverse_iterator&>(base_iterator::operator--());
- }
-
- /// add to iterator
- json_reverse_iterator& operator+=(difference_type i)
- {
- return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));
- }
-
- /// add to iterator
- json_reverse_iterator operator+(difference_type i) const
- {
- return static_cast<json_reverse_iterator>(base_iterator::operator+(i));
- }
-
- /// subtract from iterator
- json_reverse_iterator operator-(difference_type i) const
- {
- return static_cast<json_reverse_iterator>(base_iterator::operator-(i));
- }
-
- /// return difference
- difference_type operator-(const json_reverse_iterator& other) const
- {
- return base_iterator(*this) - base_iterator(other);
- }
-
- /// access to successor
- reference operator[](difference_type n) const
- {
- return *(this->operator+(n));
- }
-
- /// return the key of an object iterator
- auto key() const -> decltype(std::declval<Base>().key())
- {
- auto it = --this->base();
- return it.key();
- }
-
- /// return the value of an iterator
- reference value() const
- {
- auto it = --this->base();
- return it.operator * ();
- }
-};
-}
-}
-
-// #include <nlohmann/detail/output/output_adapters.hpp>
-
-
-#include <algorithm> // copy
-#include <cstddef> // size_t
-#include <ios> // streamsize
-#include <iterator> // back_inserter
-#include <memory> // shared_ptr, make_shared
-#include <ostream> // basic_ostream
-#include <string> // basic_string
-#include <vector> // vector
-
-namespace nlohmann
-{
-namespace detail
-{
-/// abstract output adapter interface
-template<typename CharType> struct output_adapter_protocol
-{
- virtual void write_character(CharType c) = 0;
- virtual void write_characters(const CharType* s, std::size_t length) = 0;
- virtual ~output_adapter_protocol() = default;
-};
-
-/// a type to simplify interfaces
-template<typename CharType>
-using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
-
-/// output adapter for byte vectors
-template<typename CharType>
-class output_vector_adapter : public output_adapter_protocol<CharType>
-{
- public:
- explicit output_vector_adapter(std::vector<CharType>& vec) : v(vec) {}
-
- void write_character(CharType c) override
- {
- v.push_back(c);
- }
-
- void write_characters(const CharType* s, std::size_t length) override
- {
- std::copy(s, s + length, std::back_inserter(v));
- }
-
- private:
- std::vector<CharType>& v;
-};
-
-/// output adapter for output streams
-template<typename CharType>
-class output_stream_adapter : public output_adapter_protocol<CharType>
-{
- public:
- explicit output_stream_adapter(std::basic_ostream<CharType>& s) : stream(s) {}
-
- void write_character(CharType c) override
- {
- stream.put(c);
- }
-
- void write_characters(const CharType* s, std::size_t length) override
- {
- stream.write(s, static_cast<std::streamsize>(length));
- }
-
- private:
- std::basic_ostream<CharType>& stream;
-};
-
-/// output adapter for basic_string
-template<typename CharType, typename StringType = std::basic_string<CharType>>
-class output_string_adapter : public output_adapter_protocol<CharType>
-{
- public:
- explicit output_string_adapter(StringType& s) : str(s) {}
-
- void write_character(CharType c) override
- {
- str.push_back(c);
- }
-
- void write_characters(const CharType* s, std::size_t length) override
- {
- str.append(s, length);
- }
-
- private:
- StringType& str;
-};
-
-template<typename CharType, typename StringType = std::basic_string<CharType>>
-class output_adapter
-{
- public:
- output_adapter(std::vector<CharType>& vec)
- : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {}
-
- output_adapter(std::basic_ostream<CharType>& s)
- : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
-
- output_adapter(StringType& s)
- : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
-
- operator output_adapter_t<CharType>()
- {
- return oa;
- }
-
- private:
- output_adapter_t<CharType> oa = nullptr;
-};
-}
-}
-
-// #include <nlohmann/detail/input/binary_reader.hpp>
-
-
-#include <algorithm> // generate_n
-#include <array> // array
-#include <cassert> // assert
-#include <cmath> // ldexp
-#include <cstddef> // size_t
-#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
-#include <cstring> // memcpy
-#include <iomanip> // setw, setfill
-#include <ios> // hex
-#include <iterator> // back_inserter
-#include <limits> // numeric_limits
-#include <sstream> // stringstream
-#include <string> // char_traits, string
-#include <utility> // make_pair, move
-
-// #include <nlohmann/detail/input/input_adapters.hpp>
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-///////////////////
-// binary reader //
-///////////////////
-
-/*!
-@brief deserialization of CBOR and MessagePack values
-*/
-template<typename BasicJsonType>
-class binary_reader
-{
- using number_integer_t = typename BasicJsonType::number_integer_t;
- using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
- using string_t = typename BasicJsonType::string_t;
-
- public:
- /*!
- @brief create a binary reader
-
- @param[in] adapter input adapter to read from
- */
- explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter))
- {
- assert(ia);
- }
-
- /*!
- @brief create a JSON value from CBOR input
-
- @param[in] strict whether to expect the input to be consumed completed
- @return JSON value created from CBOR input
-
- @throw parse_error.110 if input ended unexpectedly or the end of file was
- not reached when @a strict was set to true
- @throw parse_error.112 if unsupported byte was read
- */
- BasicJsonType parse_cbor(const bool strict)
- {
- const auto res = parse_cbor_internal();
- if (strict)
- {
- get();
- expect_eof();
- }
- return res;
- }
-
- /*!
- @brief create a JSON value from MessagePack input
-
- @param[in] strict whether to expect the input to be consumed completed
- @return JSON value created from MessagePack input
-
- @throw parse_error.110 if input ended unexpectedly or the end of file was
- not reached when @a strict was set to true
- @throw parse_error.112 if unsupported byte was read
- */
- BasicJsonType parse_msgpack(const bool strict)
- {
- const auto res = parse_msgpack_internal();
- if (strict)
- {
- get();
- expect_eof();
- }
- return res;
- }
-
- /*!
- @brief create a JSON value from UBJSON input
-
- @param[in] strict whether to expect the input to be consumed completed
- @return JSON value created from UBJSON input
-
- @throw parse_error.110 if input ended unexpectedly or the end of file was
- not reached when @a strict was set to true
- @throw parse_error.112 if unsupported byte was read
- */
- BasicJsonType parse_ubjson(const bool strict)
- {
- const auto res = parse_ubjson_internal();
- if (strict)
- {
- get_ignore_noop();
- expect_eof();
- }
- return res;
- }
-
- /*!
- @brief determine system byte order
-
- @return true if and only if system's byte order is little endian
-
- @note from http://stackoverflow.com/a/1001328/266378
- */
- static constexpr bool little_endianess(int num = 1) noexcept
- {
- return (*reinterpret_cast<char*>(&num) == 1);
- }
-
- private:
- /*!
- @param[in] get_char whether a new character should be retrieved from the
- input (true, default) or whether the last read
- character should be considered instead
- */
- BasicJsonType parse_cbor_internal(const bool get_char = true)
- {
- switch (get_char ? get() : current)
- {
- // EOF
- case std::char_traits<char>::eof():
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
- // Integer 0x00..0x17 (0..23)
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x08:
- case 0x09:
- case 0x0A:
- case 0x0B:
- case 0x0C:
- case 0x0D:
- case 0x0E:
- case 0x0F:
- case 0x10:
- case 0x11:
- case 0x12:
- case 0x13:
- case 0x14:
- case 0x15:
- case 0x16:
- case 0x17:
- return static_cast<number_unsigned_t>(current);
-
- case 0x18: // Unsigned integer (one-byte uint8_t follows)
- return get_number<uint8_t>();
-
- case 0x19: // Unsigned integer (two-byte uint16_t follows)
- return get_number<uint16_t>();
-
- case 0x1A: // Unsigned integer (four-byte uint32_t follows)
- return get_number<uint32_t>();
-
- case 0x1B: // Unsigned integer (eight-byte uint64_t follows)
- return get_number<uint64_t>();
-
- // Negative integer -1-0x00..-1-0x17 (-1..-24)
- case 0x20:
- case 0x21:
- case 0x22:
- case 0x23:
- case 0x24:
- case 0x25:
- case 0x26:
- case 0x27:
- case 0x28:
- case 0x29:
- case 0x2A:
- case 0x2B:
- case 0x2C:
- case 0x2D:
- case 0x2E:
- case 0x2F:
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- return static_cast<int8_t>(0x20 - 1 - current);
-
- case 0x38: // Negative integer (one-byte uint8_t follows)
- {
- return static_cast<number_integer_t>(-1) - get_number<uint8_t>();
- }
-
- case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
- {
- return static_cast<number_integer_t>(-1) - get_number<uint16_t>();
- }
-
- case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
- {
- return static_cast<number_integer_t>(-1) - get_number<uint32_t>();
- }
-
- case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
- {
- return static_cast<number_integer_t>(-1) -
- static_cast<number_integer_t>(get_number<uint64_t>());
- }
-
- // UTF-8 string (0x00..0x17 bytes follow)
- case 0x60:
- case 0x61:
- case 0x62:
- case 0x63:
- case 0x64:
- case 0x65:
- case 0x66:
- case 0x67:
- case 0x68:
- case 0x69:
- case 0x6A:
- case 0x6B:
- case 0x6C:
- case 0x6D:
- case 0x6E:
- case 0x6F:
- case 0x70:
- case 0x71:
- case 0x72:
- case 0x73:
- case 0x74:
- case 0x75:
- case 0x76:
- case 0x77:
- case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
- case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
- case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
- case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
- case 0x7F: // UTF-8 string (indefinite length)
- {
- return get_cbor_string();
- }
-
- // array (0x00..0x17 data items follow)
- case 0x80:
- case 0x81:
- case 0x82:
- case 0x83:
- case 0x84:
- case 0x85:
- case 0x86:
- case 0x87:
- case 0x88:
- case 0x89:
- case 0x8A:
- case 0x8B:
- case 0x8C:
- case 0x8D:
- case 0x8E:
- case 0x8F:
- case 0x90:
- case 0x91:
- case 0x92:
- case 0x93:
- case 0x94:
- case 0x95:
- case 0x96:
- case 0x97:
- {
- return get_cbor_array(current & 0x1F);
- }
-
- case 0x98: // array (one-byte uint8_t for n follows)
- {
- return get_cbor_array(get_number<uint8_t>());
- }
-
- case 0x99: // array (two-byte uint16_t for n follow)
- {
- return get_cbor_array(get_number<uint16_t>());
- }
-
- case 0x9A: // array (four-byte uint32_t for n follow)
- {
- return get_cbor_array(get_number<uint32_t>());
- }
-
- case 0x9B: // array (eight-byte uint64_t for n follow)
- {
- return get_cbor_array(get_number<uint64_t>());
- }
-
- case 0x9F: // array (indefinite length)
- {
- BasicJsonType result = value_t::array;
- while (get() != 0xFF)
- {
- result.push_back(parse_cbor_internal(false));
- }
- return result;
- }
-
- // map (0x00..0x17 pairs of data items follow)
- case 0xA0:
- case 0xA1:
- case 0xA2:
- case 0xA3:
- case 0xA4:
- case 0xA5:
- case 0xA6:
- case 0xA7:
- case 0xA8:
- case 0xA9:
- case 0xAA:
- case 0xAB:
- case 0xAC:
- case 0xAD:
- case 0xAE:
- case 0xAF:
- case 0xB0:
- case 0xB1:
- case 0xB2:
- case 0xB3:
- case 0xB4:
- case 0xB5:
- case 0xB6:
- case 0xB7:
- {
- return get_cbor_object(current & 0x1F);
- }
-
- case 0xB8: // map (one-byte uint8_t for n follows)
- {
- return get_cbor_object(get_number<uint8_t>());
- }
-
- case 0xB9: // map (two-byte uint16_t for n follow)
- {
- return get_cbor_object(get_number<uint16_t>());
- }
-
- case 0xBA: // map (four-byte uint32_t for n follow)
- {
- return get_cbor_object(get_number<uint32_t>());
- }
-
- case 0xBB: // map (eight-byte uint64_t for n follow)
- {
- return get_cbor_object(get_number<uint64_t>());
- }
-
- case 0xBF: // map (indefinite length)
- {
- BasicJsonType result = value_t::object;
- while (get() != 0xFF)
- {
- auto key = get_cbor_string();
- result[key] = parse_cbor_internal();
- }
- return result;
- }
-
- case 0xF4: // false
- {
- return false;
- }
-
- case 0xF5: // true
- {
- return true;
- }
-
- case 0xF6: // null
- {
- return value_t::null;
- }
-
- case 0xF9: // Half-Precision Float (two-byte IEEE 754)
- {
- const int byte1 = get();
- unexpect_eof();
- const int byte2 = get();
- unexpect_eof();
-
- // code from RFC 7049, Appendix D, Figure 3:
- // As half-precision floating-point numbers were only added
- // to IEEE 754 in 2008, today's programming platforms often
- // still only have limited support for them. It is very
- // easy to include at least decoding support for them even
- // without such support. An example of a small decoder for
- // half-precision floating-point numbers in the C language
- // is shown in Fig. 3.
- const int half = (byte1 << 8) + byte2;
- const int exp = (half >> 10) & 0x1F;
- const int mant = half & 0x3FF;
- double val;
- if (exp == 0)
- {
- val = std::ldexp(mant, -24);
- }
- else if (exp != 31)
- {
- val = std::ldexp(mant + 1024, exp - 25);
- }
- else
- {
- val = (mant == 0) ? std::numeric_limits<double>::infinity()
- : std::numeric_limits<double>::quiet_NaN();
- }
- return (half & 0x8000) != 0 ? -val : val;
- }
-
- case 0xFA: // Single-Precision Float (four-byte IEEE 754)
- {
- return get_number<float>();
- }
-
- case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
- {
- return get_number<double>();
- }
-
- default: // anything else (0xFF is handled inside the other types)
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str()));
- }
- }
- }
-
- BasicJsonType parse_msgpack_internal()
- {
- switch (get())
- {
- // EOF
- case std::char_traits<char>::eof():
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
- // positive fixint
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x08:
- case 0x09:
- case 0x0A:
- case 0x0B:
- case 0x0C:
- case 0x0D:
- case 0x0E:
- case 0x0F:
- case 0x10:
- case 0x11:
- case 0x12:
- case 0x13:
- case 0x14:
- case 0x15:
- case 0x16:
- case 0x17:
- case 0x18:
- case 0x19:
- case 0x1A:
- case 0x1B:
- case 0x1C:
- case 0x1D:
- case 0x1E:
- case 0x1F:
- case 0x20:
- case 0x21:
- case 0x22:
- case 0x23:
- case 0x24:
- case 0x25:
- case 0x26:
- case 0x27:
- case 0x28:
- case 0x29:
- case 0x2A:
- case 0x2B:
- case 0x2C:
- case 0x2D:
- case 0x2E:
- case 0x2F:
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- case 0x38:
- case 0x39:
- case 0x3A:
- case 0x3B:
- case 0x3C:
- case 0x3D:
- case 0x3E:
- case 0x3F:
- case 0x40:
- case 0x41:
- case 0x42:
- case 0x43:
- case 0x44:
- case 0x45:
- case 0x46:
- case 0x47:
- case 0x48:
- case 0x49:
- case 0x4A:
- case 0x4B:
- case 0x4C:
- case 0x4D:
- case 0x4E:
- case 0x4F:
- case 0x50:
- case 0x51:
- case 0x52:
- case 0x53:
- case 0x54:
- case 0x55:
- case 0x56:
- case 0x57:
- case 0x58:
- case 0x59:
- case 0x5A:
- case 0x5B:
- case 0x5C:
- case 0x5D:
- case 0x5E:
- case 0x5F:
- case 0x60:
- case 0x61:
- case 0x62:
- case 0x63:
- case 0x64:
- case 0x65:
- case 0x66:
- case 0x67:
- case 0x68:
- case 0x69:
- case 0x6A:
- case 0x6B:
- case 0x6C:
- case 0x6D:
- case 0x6E:
- case 0x6F:
- case 0x70:
- case 0x71:
- case 0x72:
- case 0x73:
- case 0x74:
- case 0x75:
- case 0x76:
- case 0x77:
- case 0x78:
- case 0x79:
- case 0x7A:
- case 0x7B:
- case 0x7C:
- case 0x7D:
- case 0x7E:
- case 0x7F:
- return static_cast<number_unsigned_t>(current);
-
- // fixmap
- case 0x80:
- case 0x81:
- case 0x82:
- case 0x83:
- case 0x84:
- case 0x85:
- case 0x86:
- case 0x87:
- case 0x88:
- case 0x89:
- case 0x8A:
- case 0x8B:
- case 0x8C:
- case 0x8D:
- case 0x8E:
- case 0x8F:
- {
- return get_msgpack_object(current & 0x0F);
- }
-
- // fixarray
- case 0x90:
- case 0x91:
- case 0x92:
- case 0x93:
- case 0x94:
- case 0x95:
- case 0x96:
- case 0x97:
- case 0x98:
- case 0x99:
- case 0x9A:
- case 0x9B:
- case 0x9C:
- case 0x9D:
- case 0x9E:
- case 0x9F:
- {
- return get_msgpack_array(current & 0x0F);
- }
-
- // fixstr
- case 0xA0:
- case 0xA1:
- case 0xA2:
- case 0xA3:
- case 0xA4:
- case 0xA5:
- case 0xA6:
- case 0xA7:
- case 0xA8:
- case 0xA9:
- case 0xAA:
- case 0xAB:
- case 0xAC:
- case 0xAD:
- case 0xAE:
- case 0xAF:
- case 0xB0:
- case 0xB1:
- case 0xB2:
- case 0xB3:
- case 0xB4:
- case 0xB5:
- case 0xB6:
- case 0xB7:
- case 0xB8:
- case 0xB9:
- case 0xBA:
- case 0xBB:
- case 0xBC:
- case 0xBD:
- case 0xBE:
- case 0xBF:
- return get_msgpack_string();
-
- case 0xC0: // nil
- return value_t::null;
-
- case 0xC2: // false
- return false;
-
- case 0xC3: // true
- return true;
-
- case 0xCA: // float 32
- return get_number<float>();
-
- case 0xCB: // float 64
- return get_number<double>();
-
- case 0xCC: // uint 8
- return get_number<uint8_t>();
-
- case 0xCD: // uint 16
- return get_number<uint16_t>();
-
- case 0xCE: // uint 32
- return get_number<uint32_t>();
-
- case 0xCF: // uint 64
- return get_number<uint64_t>();
-
- case 0xD0: // int 8
- return get_number<int8_t>();
-
- case 0xD1: // int 16
- return get_number<int16_t>();
-
- case 0xD2: // int 32
- return get_number<int32_t>();
-
- case 0xD3: // int 64
- return get_number<int64_t>();
-
- case 0xD9: // str 8
- case 0xDA: // str 16
- case 0xDB: // str 32
- return get_msgpack_string();
-
- case 0xDC: // array 16
- {
- return get_msgpack_array(get_number<uint16_t>());
- }
-
- case 0xDD: // array 32
- {
- return get_msgpack_array(get_number<uint32_t>());
- }
-
- case 0xDE: // map 16
- {
- return get_msgpack_object(get_number<uint16_t>());
- }
-
- case 0xDF: // map 32
- {
- return get_msgpack_object(get_number<uint32_t>());
- }
-
- // positive fixint
- case 0xE0:
- case 0xE1:
- case 0xE2:
- case 0xE3:
- case 0xE4:
- case 0xE5:
- case 0xE6:
- case 0xE7:
- case 0xE8:
- case 0xE9:
- case 0xEA:
- case 0xEB:
- case 0xEC:
- case 0xED:
- case 0xEE:
- case 0xEF:
- case 0xF0:
- case 0xF1:
- case 0xF2:
- case 0xF3:
- case 0xF4:
- case 0xF5:
- case 0xF6:
- case 0xF7:
- case 0xF8:
- case 0xF9:
- case 0xFA:
- case 0xFB:
- case 0xFC:
- case 0xFD:
- case 0xFE:
- case 0xFF:
- return static_cast<int8_t>(current);
-
- default: // anything else
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(112, chars_read,
- "error reading MessagePack; last byte: 0x" + ss.str()));
- }
- }
- }
-
- /*!
- @param[in] get_char whether a new character should be retrieved from the
- input (true, default) or whether the last read
- character should be considered instead
- */
- BasicJsonType parse_ubjson_internal(const bool get_char = true)
- {
- return get_ubjson_value(get_char ? get_ignore_noop() : current);
- }
-
- /*!
- @brief get next character from the input
-
- This function provides the interface to the used input adapter. It does
- not throw in case the input reached EOF, but returns a -'ve valued
- `std::char_traits<char>::eof()` in that case.
-
- @return character read from the input
- */
- int get()
- {
- ++chars_read;
- return (current = ia->get_character());
- }
-
- /*!
- @return character read from the input after ignoring all 'N' entries
- */
- int get_ignore_noop()
- {
- do
- {
- get();
- }
- while (current == 'N');
-
- return current;
- }
-
- /*
- @brief read a number from the input
-
- @tparam NumberType the type of the number
-
- @return number of type @a NumberType
-
- @note This function needs to respect the system's endianess, because
- bytes in CBOR and MessagePack are stored in network order (big
- endian) and therefore need reordering on little endian systems.
-
- @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes
- */
- template<typename NumberType> NumberType get_number()
- {
- // step 1: read input into array with system's byte order
- std::array<uint8_t, sizeof(NumberType)> vec;
- for (std::size_t i = 0; i < sizeof(NumberType); ++i)
- {
- get();
- unexpect_eof();
-
- // reverse byte order prior to conversion if necessary
- if (is_little_endian)
- {
- vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current);
- }
- else
- {
- vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE
- }
- }
-
- // step 2: convert array into number of type T and return
- NumberType result;
- std::memcpy(&result, vec.data(), sizeof(NumberType));
- return result;
- }
-
- /*!
- @brief create a string by reading characters from the input
-
- @param[in] len number of bytes to read
-
- @note We can not reserve @a len bytes for the result, because @a len
- may be too large. Usually, @ref unexpect_eof() detects the end of
- the input before we run out of string memory.
-
- @return string created by reading @a len bytes
-
- @throw parse_error.110 if input has less than @a len bytes
- */
- template<typename NumberType>
- string_t get_string(const NumberType len)
- {
- string_t result;
- std::generate_n(std::back_inserter(result), len, [this]()
- {
- get();
- unexpect_eof();
- return static_cast<char>(current);
- });
- return result;
- }
-
- /*!
- @brief reads a CBOR string
-
- This function first reads starting bytes to determine the expected
- string length and then copies this number of bytes into a string.
- Additionally, CBOR's strings with indefinite lengths are supported.
-
- @return string
-
- @throw parse_error.110 if input ended
- @throw parse_error.113 if an unexpected byte is read
- */
- string_t get_cbor_string()
- {
- unexpect_eof();
-
- switch (current)
- {
- // UTF-8 string (0x00..0x17 bytes follow)
- case 0x60:
- case 0x61:
- case 0x62:
- case 0x63:
- case 0x64:
- case 0x65:
- case 0x66:
- case 0x67:
- case 0x68:
- case 0x69:
- case 0x6A:
- case 0x6B:
- case 0x6C:
- case 0x6D:
- case 0x6E:
- case 0x6F:
- case 0x70:
- case 0x71:
- case 0x72:
- case 0x73:
- case 0x74:
- case 0x75:
- case 0x76:
- case 0x77:
- {
- return get_string(current & 0x1F);
- }
-
- case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
- {
- return get_string(get_number<uint8_t>());
- }
-
- case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
- {
- return get_string(get_number<uint16_t>());
- }
-
- case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
- {
- return get_string(get_number<uint32_t>());
- }
-
- case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
- {
- return get_string(get_number<uint64_t>());
- }
-
- case 0x7F: // UTF-8 string (indefinite length)
- {
- string_t result;
- while (get() != 0xFF)
- {
- result.append(get_cbor_string());
- }
- return result;
- }
-
- default:
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str()));
- }
- }
- }
-
- template<typename NumberType>
- BasicJsonType get_cbor_array(const NumberType len)
- {
- BasicJsonType result = value_t::array;
- std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
- {
- return parse_cbor_internal();
- });
- return result;
- }
-
- template<typename NumberType>
- BasicJsonType get_cbor_object(const NumberType len)
- {
- BasicJsonType result = value_t::object;
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- len, [this]()
- {
- get();
- auto key = get_cbor_string();
- auto val = parse_cbor_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
- return result;
- }
-
- /*!
- @brief reads a MessagePack string
-
- This function first reads starting bytes to determine the expected
- string length and then copies this number of bytes into a string.
-
- @return string
-
- @throw parse_error.110 if input ended
- @throw parse_error.113 if an unexpected byte is read
- */
- string_t get_msgpack_string()
- {
- unexpect_eof();
-
- switch (current)
- {
- // fixstr
- case 0xA0:
- case 0xA1:
- case 0xA2:
- case 0xA3:
- case 0xA4:
- case 0xA5:
- case 0xA6:
- case 0xA7:
- case 0xA8:
- case 0xA9:
- case 0xAA:
- case 0xAB:
- case 0xAC:
- case 0xAD:
- case 0xAE:
- case 0xAF:
- case 0xB0:
- case 0xB1:
- case 0xB2:
- case 0xB3:
- case 0xB4:
- case 0xB5:
- case 0xB6:
- case 0xB7:
- case 0xB8:
- case 0xB9:
- case 0xBA:
- case 0xBB:
- case 0xBC:
- case 0xBD:
- case 0xBE:
- case 0xBF:
- {
- return get_string(current & 0x1F);
- }
-
- case 0xD9: // str 8
- {
- return get_string(get_number<uint8_t>());
- }
-
- case 0xDA: // str 16
- {
- return get_string(get_number<uint16_t>());
- }
-
- case 0xDB: // str 32
- {
- return get_string(get_number<uint32_t>());
- }
-
- default:
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(113, chars_read,
- "expected a MessagePack string; last byte: 0x" + ss.str()));
- }
- }
- }
-
- template<typename NumberType>
- BasicJsonType get_msgpack_array(const NumberType len)
- {
- BasicJsonType result = value_t::array;
- std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
- {
- return parse_msgpack_internal();
- });
- return result;
- }
-
- template<typename NumberType>
- BasicJsonType get_msgpack_object(const NumberType len)
- {
- BasicJsonType result = value_t::object;
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- len, [this]()
- {
- get();
- auto key = get_msgpack_string();
- auto val = parse_msgpack_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
- return result;
- }
-
- /*!
- @brief reads a UBJSON string
-
- This function is either called after reading the 'S' byte explicitly
- indicating a string, or in case of an object key where the 'S' byte can be
- left out.
-
- @param[in] get_char whether a new character should be retrieved from the
- input (true, default) or whether the last read
- character should be considered instead
-
- @return string
-
- @throw parse_error.110 if input ended
- @throw parse_error.113 if an unexpected byte is read
- */
- string_t get_ubjson_string(const bool get_char = true)
- {
- if (get_char)
- {
- get(); // TODO: may we ignore N here?
- }
-
- unexpect_eof();
-
- switch (current)
- {
- case 'U':
- return get_string(get_number<uint8_t>());
- case 'i':
- return get_string(get_number<int8_t>());
- case 'I':
- return get_string(get_number<int16_t>());
- case 'l':
- return get_string(get_number<int32_t>());
- case 'L':
- return get_string(get_number<int64_t>());
- default:
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(113, chars_read,
- "expected a UBJSON string; last byte: 0x" + ss.str()));
- }
- }
-
- /*!
- @brief determine the type and size for a container
-
- In the optimized UBJSON format, a type and a size can be provided to allow
- for a more compact representation.
-
- @return pair of the size and the type
- */
- std::pair<std::size_t, int> get_ubjson_size_type()
- {
- std::size_t sz = string_t::npos;
- int tc = 0;
-
- get_ignore_noop();
-
- if (current == '$')
- {
- tc = get(); // must not ignore 'N', because 'N' maybe the type
- unexpect_eof();
-
- get_ignore_noop();
- if (current != '#')
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(112, chars_read,
- "expected '#' after UBJSON type information; last byte: 0x" + ss.str()));
- }
- sz = parse_ubjson_internal();
- }
- else if (current == '#')
- {
- sz = parse_ubjson_internal();
- }
-
- return std::make_pair(sz, tc);
- }
-
- BasicJsonType get_ubjson_value(const int prefix)
- {
- switch (prefix)
- {
- case std::char_traits<char>::eof(): // EOF
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
- case 'T': // true
- return true;
- case 'F': // false
- return false;
-
- case 'Z': // null
- return nullptr;
-
- case 'U':
- return get_number<uint8_t>();
- case 'i':
- return get_number<int8_t>();
- case 'I':
- return get_number<int16_t>();
- case 'l':
- return get_number<int32_t>();
- case 'L':
- return get_number<int64_t>();
- case 'd':
- return get_number<float>();
- case 'D':
- return get_number<double>();
-
- case 'C': // char
- {
- get();
- unexpect_eof();
- if (JSON_UNLIKELY(current > 127))
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(113, chars_read,
- "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str()));
- }
- return string_t(1, static_cast<char>(current));
- }
-
- case 'S': // string
- return get_ubjson_string();
-
- case '[': // array
- return get_ubjson_array();
-
- case '{': // object
- return get_ubjson_object();
-
- default: // anything else
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current;
- JSON_THROW(parse_error::create(112, chars_read,
- "error reading UBJSON; last byte: 0x" + ss.str()));
- }
- }
-
- BasicJsonType get_ubjson_array()
- {
- BasicJsonType result = value_t::array;
- const auto size_and_type = get_ubjson_size_type();
-
- if (size_and_type.first != string_t::npos)
- {
- if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
- {
- JSON_THROW(out_of_range::create(408,
- "excessive array size: " + std::to_string(size_and_type.first)));
- }
-
- if (size_and_type.second != 0)
- {
- if (size_and_type.second != 'N')
- {
- std::generate_n(std::back_inserter(*result.m_value.array),
- size_and_type.first, [this, size_and_type]()
- {
- return get_ubjson_value(size_and_type.second);
- });
- }
- }
- else
- {
- std::generate_n(std::back_inserter(*result.m_value.array),
- size_and_type.first, [this]()
- {
- return parse_ubjson_internal();
- });
- }
- }
- else
- {
- while (current != ']')
- {
- result.push_back(parse_ubjson_internal(false));
- get_ignore_noop();
- }
- }
-
- return result;
- }
-
- BasicJsonType get_ubjson_object()
- {
- BasicJsonType result = value_t::object;
- const auto size_and_type = get_ubjson_size_type();
-
- if (size_and_type.first != string_t::npos)
- {
- if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
- {
- JSON_THROW(out_of_range::create(408,
- "excessive object size: " + std::to_string(size_and_type.first)));
- }
-
- if (size_and_type.second != 0)
- {
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- size_and_type.first, [this, size_and_type]()
- {
- auto key = get_ubjson_string();
- auto val = get_ubjson_value(size_and_type.second);
- return std::make_pair(std::move(key), std::move(val));
- });
- }
- else
- {
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- size_and_type.first, [this]()
- {
- auto key = get_ubjson_string();
- auto val = parse_ubjson_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
- }
- }
- else
- {
- while (current != '}')
- {
- auto key = get_ubjson_string(false);
- result[std::move(key)] = parse_ubjson_internal();
- get_ignore_noop();
- }
- }
-
- return result;
- }
-
- /*!
- @brief throw if end of input is not reached
- @throw parse_error.110 if input not ended
- */
- void expect_eof() const
- {
- if (JSON_UNLIKELY(current != std::char_traits<char>::eof()))
- {
- JSON_THROW(parse_error::create(110, chars_read, "expected end of input"));
- }
- }
-
- /*!
- @briefthrow if end of input is reached
- @throw parse_error.110 if input ended
- */
- void unexpect_eof() const
- {
- if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
- {
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
- }
- }
-
- private:
- /// input adapter
- input_adapter_t ia = nullptr;
-
- /// the current character
- int current = std::char_traits<char>::eof();
-
- /// the number of characters read
- std::size_t chars_read = 0;
-
- /// whether we can assume little endianess
- const bool is_little_endian = little_endianess();
-};
-}
-}
-
-// #include <nlohmann/detail/output/binary_writer.hpp>
-
-
-#include <algorithm> // reverse
-#include <array> // array
-#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
-#include <cstring> // memcpy
-#include <limits> // numeric_limits
-
-// #include <nlohmann/detail/input/binary_reader.hpp>
-
-// #include <nlohmann/detail/output/output_adapters.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-///////////////////
-// binary writer //
-///////////////////
-
-/*!
-@brief serialization to CBOR and MessagePack values
-*/
-template<typename BasicJsonType, typename CharType>
-class binary_writer
-{
- public:
- /*!
- @brief create a binary writer
-
- @param[in] adapter output adapter to write to
- */
- explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter)
- {
- assert(oa);
- }
-
- /*!
- @brief[in] j JSON value to serialize
- */
- void write_cbor(const BasicJsonType& j)
- {
- switch (j.type())
- {
- case value_t::null:
- {
- oa->write_character(static_cast<CharType>(0xF6));
- break;
- }
-
- case value_t::boolean:
- {
- oa->write_character(j.m_value.boolean
- ? static_cast<CharType>(0xF5)
- : static_cast<CharType>(0xF4));
- break;
- }
-
- case value_t::number_integer:
- {
- if (j.m_value.number_integer >= 0)
- {
- // CBOR does not differentiate between positive signed
- // integers and unsigned integers. Therefore, we used the
- // code from the value_t::number_unsigned case here.
- if (j.m_value.number_integer <= 0x17)
- {
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x18));
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x19));
- write_number(static_cast<uint16_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x1A));
- write_number(static_cast<uint32_t>(j.m_value.number_integer));
- }
- else
- {
- oa->write_character(static_cast<CharType>(0x1B));
- write_number(static_cast<uint64_t>(j.m_value.number_integer));
- }
- }
- else
- {
- // The conversions below encode the sign in the first
- // byte, and the value is converted to a positive number.
- const auto positive_number = -1 - j.m_value.number_integer;
- if (j.m_value.number_integer >= -24)
- {
- write_number(static_cast<uint8_t>(0x20 + positive_number));
- }
- else if (positive_number <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x38));
- write_number(static_cast<uint8_t>(positive_number));
- }
- else if (positive_number <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x39));
- write_number(static_cast<uint16_t>(positive_number));
- }
- else if (positive_number <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x3A));
- write_number(static_cast<uint32_t>(positive_number));
- }
- else
- {
- oa->write_character(static_cast<CharType>(0x3B));
- write_number(static_cast<uint64_t>(positive_number));
- }
- }
- break;
- }
-
- case value_t::number_unsigned:
- {
- if (j.m_value.number_unsigned <= 0x17)
- {
- write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x18));
- write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x19));
- write_number(static_cast<uint16_t>(j.m_value.number_unsigned));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x1A));
- write_number(static_cast<uint32_t>(j.m_value.number_unsigned));
- }
- else
- {
- oa->write_character(static_cast<CharType>(0x1B));
- write_number(static_cast<uint64_t>(j.m_value.number_unsigned));
- }
- break;
- }
-
- case value_t::number_float: // Double-Precision Float
- {
- oa->write_character(static_cast<CharType>(0xFB));
- write_number(j.m_value.number_float);
- break;
- }
-
- case value_t::string:
- {
- // step 1: write control byte and the string length
- const auto N = j.m_value.string->size();
- if (N <= 0x17)
- {
- write_number(static_cast<uint8_t>(0x60 + N));
- }
- else if (N <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x78));
- write_number(static_cast<uint8_t>(N));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x79));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x7A));
- write_number(static_cast<uint32_t>(N));
- }
- // LCOV_EXCL_START
- else if (N <= (std::numeric_limits<uint64_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x7B));
- write_number(static_cast<uint64_t>(N));
- }
- // LCOV_EXCL_STOP
-
- // step 2: write the string
- oa->write_characters(
- reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
- j.m_value.string->size());
- break;
- }
-
- case value_t::array:
- {
- // step 1: write control byte and the array size
- const auto N = j.m_value.array->size();
- if (N <= 0x17)
- {
- write_number(static_cast<uint8_t>(0x80 + N));
- }
- else if (N <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x98));
- write_number(static_cast<uint8_t>(N));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x99));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x9A));
- write_number(static_cast<uint32_t>(N));
- }
- // LCOV_EXCL_START
- else if (N <= (std::numeric_limits<uint64_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0x9B));
- write_number(static_cast<uint64_t>(N));
- }
- // LCOV_EXCL_STOP
-
- // step 2: write each element
- for (const auto& el : *j.m_value.array)
- {
- write_cbor(el);
- }
- break;
- }
-
- case value_t::object:
- {
- // step 1: write control byte and the object size
- const auto N = j.m_value.object->size();
- if (N <= 0x17)
- {
- write_number(static_cast<uint8_t>(0xA0 + N));
- }
- else if (N <= (std::numeric_limits<uint8_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0xB8));
- write_number(static_cast<uint8_t>(N));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0xB9));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0xBA));
- write_number(static_cast<uint32_t>(N));
- }
- // LCOV_EXCL_START
- else if (N <= (std::numeric_limits<uint64_t>::max)())
- {
- oa->write_character(static_cast<CharType>(0xBB));
- write_number(static_cast<uint64_t>(N));
- }
- // LCOV_EXCL_STOP
-
- // step 2: write each element
- for (const auto& el : *j.m_value.object)
- {
- write_cbor(el.first);
- write_cbor(el.second);
- }
- break;
- }
-
- default:
- break;
- }
- }
-
- /*!
- @brief[in] j JSON value to serialize
- */
- void write_msgpack(const BasicJsonType& j)
- {
- switch (j.type())
- {
- case value_t::null: // nil
- {
- oa->write_character(static_cast<CharType>(0xC0));
- break;
- }
-
- case value_t::boolean: // true and false
- {
- oa->write_character(j.m_value.boolean
- ? static_cast<CharType>(0xC3)
- : static_cast<CharType>(0xC2));
- break;
- }
-
- case value_t::number_integer:
- {
- if (j.m_value.number_integer >= 0)
- {
- // MessagePack does not differentiate between positive
- // signed integers and unsigned integers. Therefore, we used
- // the code from the value_t::number_unsigned case here.
- if (j.m_value.number_unsigned < 128)
- {
- // positive fixnum
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
- {
- // uint 8
- oa->write_character(static_cast<CharType>(0xCC));
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
- {
- // uint 16
- oa->write_character(static_cast<CharType>(0xCD));
- write_number(static_cast<uint16_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
- {
- // uint 32
- oa->write_character(static_cast<CharType>(0xCE));
- write_number(static_cast<uint32_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
- {
- // uint 64
- oa->write_character(static_cast<CharType>(0xCF));
- write_number(static_cast<uint64_t>(j.m_value.number_integer));
- }
- }
- else
- {
- if (j.m_value.number_integer >= -32)
- {
- // negative fixnum
- write_number(static_cast<int8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and
- j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
- {
- // int 8
- oa->write_character(static_cast<CharType>(0xD0));
- write_number(static_cast<int8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and
- j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
- {
- // int 16
- oa->write_character(static_cast<CharType>(0xD1));
- write_number(static_cast<int16_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and
- j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
- {
- // int 32
- oa->write_character(static_cast<CharType>(0xD2));
- write_number(static_cast<int32_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and
- j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)())
- {
- // int 64
- oa->write_character(static_cast<CharType>(0xD3));
- write_number(static_cast<int64_t>(j.m_value.number_integer));
- }
- }
- break;
- }
-
- case value_t::number_unsigned:
- {
- if (j.m_value.number_unsigned < 128)
- {
- // positive fixnum
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
- {
- // uint 8
- oa->write_character(static_cast<CharType>(0xCC));
- write_number(static_cast<uint8_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
- {
- // uint 16
- oa->write_character(static_cast<CharType>(0xCD));
- write_number(static_cast<uint16_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
- {
- // uint 32
- oa->write_character(static_cast<CharType>(0xCE));
- write_number(static_cast<uint32_t>(j.m_value.number_integer));
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
- {
- // uint 64
- oa->write_character(static_cast<CharType>(0xCF));
- write_number(static_cast<uint64_t>(j.m_value.number_integer));
- }
- break;
- }
-
- case value_t::number_float: // float 64
- {
- oa->write_character(static_cast<CharType>(0xCB));
- write_number(j.m_value.number_float);
- break;
- }
-
- case value_t::string:
- {
- // step 1: write control byte and the string length
- const auto N = j.m_value.string->size();
- if (N <= 31)
- {
- // fixstr
- write_number(static_cast<uint8_t>(0xA0 | N));
- }
- else if (N <= (std::numeric_limits<uint8_t>::max)())
- {
- // str 8
- oa->write_character(static_cast<CharType>(0xD9));
- write_number(static_cast<uint8_t>(N));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- // str 16
- oa->write_character(static_cast<CharType>(0xDA));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- // str 32
- oa->write_character(static_cast<CharType>(0xDB));
- write_number(static_cast<uint32_t>(N));
- }
-
- // step 2: write the string
- oa->write_characters(
- reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
- j.m_value.string->size());
- break;
- }
-
- case value_t::array:
- {
- // step 1: write control byte and the array size
- const auto N = j.m_value.array->size();
- if (N <= 15)
- {
- // fixarray
- write_number(static_cast<uint8_t>(0x90 | N));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- // array 16
- oa->write_character(static_cast<CharType>(0xDC));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- // array 32
- oa->write_character(static_cast<CharType>(0xDD));
- write_number(static_cast<uint32_t>(N));
- }
-
- // step 2: write each element
- for (const auto& el : *j.m_value.array)
- {
- write_msgpack(el);
- }
- break;
- }
-
- case value_t::object:
- {
- // step 1: write control byte and the object size
- const auto N = j.m_value.object->size();
- if (N <= 15)
- {
- // fixmap
- write_number(static_cast<uint8_t>(0x80 | (N & 0xF)));
- }
- else if (N <= (std::numeric_limits<uint16_t>::max)())
- {
- // map 16
- oa->write_character(static_cast<CharType>(0xDE));
- write_number(static_cast<uint16_t>(N));
- }
- else if (N <= (std::numeric_limits<uint32_t>::max)())
- {
- // map 32
- oa->write_character(static_cast<CharType>(0xDF));
- write_number(static_cast<uint32_t>(N));
- }
-
- // step 2: write each element
- for (const auto& el : *j.m_value.object)
- {
- write_msgpack(el.first);
- write_msgpack(el.second);
- }
- break;
- }
-
- default:
- break;
- }
- }
-
- /*!
- @param[in] j JSON value to serialize
- @param[in] use_count whether to use '#' prefixes (optimized format)
- @param[in] use_type whether to use '$' prefixes (optimized format)
- @param[in] add_prefix whether prefixes need to be used for this value
- */
- void write_ubjson(const BasicJsonType& j, const bool use_count,
- const bool use_type, const bool add_prefix = true)
- {
- switch (j.type())
- {
- case value_t::null:
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('Z'));
- }
- break;
- }
-
- case value_t::boolean:
- {
- if (add_prefix)
- oa->write_character(j.m_value.boolean
- ? static_cast<CharType>('T')
- : static_cast<CharType>('F'));
- break;
- }
-
- case value_t::number_integer:
- {
- write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix);
- break;
- }
-
- case value_t::number_unsigned:
- {
- write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix);
- break;
- }
-
- case value_t::number_float:
- {
- write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix);
- break;
- }
-
- case value_t::string:
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('S'));
- }
- write_number_with_ubjson_prefix(j.m_value.string->size(), true);
- oa->write_characters(
- reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
- j.m_value.string->size());
- break;
- }
-
- case value_t::array:
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('['));
- }
-
- bool prefix_required = true;
- if (use_type and not j.m_value.array->empty())
- {
- assert(use_count);
- const char first_prefix = ubjson_prefix(j.front());
- const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
- [this, first_prefix](const BasicJsonType & v)
- {
- return ubjson_prefix(v) == first_prefix;
- });
-
- if (same_prefix)
- {
- prefix_required = false;
- oa->write_character(static_cast<CharType>('$'));
- oa->write_character(static_cast<CharType>(first_prefix));
- }
- }
-
- if (use_count)
- {
- oa->write_character(static_cast<CharType>('#'));
- write_number_with_ubjson_prefix(j.m_value.array->size(), true);
- }
-
- for (const auto& el : *j.m_value.array)
- {
- write_ubjson(el, use_count, use_type, prefix_required);
- }
-
- if (not use_count)
- {
- oa->write_character(static_cast<CharType>(']'));
- }
-
- break;
- }
-
- case value_t::object:
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('{'));
- }
-
- bool prefix_required = true;
- if (use_type and not j.m_value.object->empty())
- {
- assert(use_count);
- const char first_prefix = ubjson_prefix(j.front());
- const bool same_prefix = std::all_of(j.begin(), j.end(),
- [this, first_prefix](const BasicJsonType & v)
- {
- return ubjson_prefix(v) == first_prefix;
- });
-
- if (same_prefix)
- {
- prefix_required = false;
- oa->write_character(static_cast<CharType>('$'));
- oa->write_character(static_cast<CharType>(first_prefix));
- }
- }
-
- if (use_count)
- {
- oa->write_character(static_cast<CharType>('#'));
- write_number_with_ubjson_prefix(j.m_value.object->size(), true);
- }
-
- for (const auto& el : *j.m_value.object)
- {
- write_number_with_ubjson_prefix(el.first.size(), true);
- oa->write_characters(
- reinterpret_cast<const CharType*>(el.first.c_str()),
- el.first.size());
- write_ubjson(el.second, use_count, use_type, prefix_required);
- }
-
- if (not use_count)
- {
- oa->write_character(static_cast<CharType>('}'));
- }
-
- break;
- }
-
- default:
- break;
- }
- }
-
- private:
- /*
- @brief write a number to output input
-
- @param[in] n number of type @a NumberType
- @tparam NumberType the type of the number
-
- @note This function needs to respect the system's endianess, because bytes
- in CBOR, MessagePack, and UBJSON are stored in network order (big
- endian) and therefore need reordering on little endian systems.
- */
- template<typename NumberType>
- void write_number(const NumberType n)
- {
- // step 1: write number to array of length NumberType
- std::array<CharType, sizeof(NumberType)> vec;
- std::memcpy(vec.data(), &n, sizeof(NumberType));
-
- // step 2: write array to output (with possible reordering)
- if (is_little_endian)
- {
- // reverse byte order prior to conversion if necessary
- std::reverse(vec.begin(), vec.end());
- }
-
- oa->write_characters(vec.data(), sizeof(NumberType));
- }
-
- // UBJSON: write number (floating point)
- template<typename NumberType, typename std::enable_if<
- std::is_floating_point<NumberType>::value, int>::type = 0>
- void write_number_with_ubjson_prefix(const NumberType n,
- const bool add_prefix)
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('D')); // float64
- }
- write_number(n);
- }
-
- // UBJSON: write number (unsigned integer)
- template<typename NumberType, typename std::enable_if<
- std::is_unsigned<NumberType>::value, int>::type = 0>
- void write_number_with_ubjson_prefix(const NumberType n,
- const bool add_prefix)
- {
- if (n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('i')); // int8
- }
- write_number(static_cast<uint8_t>(n));
- }
- else if (n <= (std::numeric_limits<uint8_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('U')); // uint8
- }
- write_number(static_cast<uint8_t>(n));
- }
- else if (n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('I')); // int16
- }
- write_number(static_cast<int16_t>(n));
- }
- else if (n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('l')); // int32
- }
- write_number(static_cast<int32_t>(n));
- }
- else if (n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('L')); // int64
- }
- write_number(static_cast<int64_t>(n));
- }
- else
- {
- JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n)));
- }
- }
-
- // UBJSON: write number (signed integer)
- template<typename NumberType, typename std::enable_if<
- std::is_signed<NumberType>::value and
- not std::is_floating_point<NumberType>::value, int>::type = 0>
- void write_number_with_ubjson_prefix(const NumberType n,
- const bool add_prefix)
- {
- if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('i')); // int8
- }
- write_number(static_cast<int8_t>(n));
- }
- else if (static_cast<int64_t>((std::numeric_limits<uint8_t>::min)()) <= n and n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max)()))
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('U')); // uint8
- }
- write_number(static_cast<uint8_t>(n));
- }
- else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('I')); // int16
- }
- write_number(static_cast<int16_t>(n));
- }
- else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('l')); // int32
- }
- write_number(static_cast<int32_t>(n));
- }
- else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('L')); // int64
- }
- write_number(static_cast<int64_t>(n));
- }
- // LCOV_EXCL_START
- else
- {
- JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n)));
- }
- // LCOV_EXCL_STOP
- }
-
- /*!
- @brief determine the type prefix of container values
-
- @note This function does not need to be 100% accurate when it comes to
- integer limits. In case a number exceeds the limits of int64_t,
- this will be detected by a later call to function
- write_number_with_ubjson_prefix. Therefore, we return 'L' for any
- value that does not fit the previous limits.
- */
- char ubjson_prefix(const BasicJsonType& j) const noexcept
- {
- switch (j.type())
- {
- case value_t::null:
- return 'Z';
-
- case value_t::boolean:
- return j.m_value.boolean ? 'T' : 'F';
-
- case value_t::number_integer:
- {
- if ((std::numeric_limits<int8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
- {
- return 'i';
- }
- else if ((std::numeric_limits<uint8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
- {
- return 'U';
- }
- else if ((std::numeric_limits<int16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
- {
- return 'I';
- }
- else if ((std::numeric_limits<int32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
- {
- return 'l';
- }
- else // no check and assume int64_t (see note above)
- {
- return 'L';
- }
- }
-
- case value_t::number_unsigned:
- {
- if (j.m_value.number_unsigned <= (std::numeric_limits<int8_t>::max)())
- {
- return 'i';
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
- {
- return 'U';
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<int16_t>::max)())
- {
- return 'I';
- }
- else if (j.m_value.number_unsigned <= (std::numeric_limits<int32_t>::max)())
- {
- return 'l';
- }
- else // no check and assume int64_t (see note above)
- {
- return 'L';
- }
- }
-
- case value_t::number_float:
- return 'D';
-
- case value_t::string:
- return 'S';
-
- case value_t::array:
- return '[';
-
- case value_t::object:
- return '{';
-
- default: // discarded values
- return 'N';
- }
- }
-
- private:
- /// whether we can assume little endianess
- const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess();
-
- /// the output
- output_adapter_t<CharType> oa = nullptr;
-};
-}
-}
-
-// #include <nlohmann/detail/output/serializer.hpp>
-
-
-#include <algorithm> // reverse, remove, fill, find, none_of
-#include <array> // array
-#include <cassert> // assert
-#include <ciso646> // and, or
-#include <clocale> // localeconv, lconv
-#include <cmath> // labs, isfinite, isnan, signbit
-#include <cstddef> // size_t, ptrdiff_t
-#include <cstdint> // uint8_t
-#include <cstdio> // snprintf
-#include <iomanip> // setfill
-#include <iterator> // next
-#include <limits> // numeric_limits
-#include <string> // string
-#include <sstream> // stringstream
-#include <type_traits> // is_same
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/conversions/to_chars.hpp>
-
-
-#include <cassert> // assert
-#include <ciso646> // or, and, not
-#include <cmath> // signbit, isfinite
-#include <cstdint> // intN_t, uintN_t
-#include <cstring> // memcpy, memmove
-
-namespace nlohmann
-{
-namespace detail
-{
-
-/*!
-@brief implements the Grisu2 algorithm for binary to decimal floating-point
-conversion.
-
-This implementation is a slightly modified version of the reference
-implementation which may be obtained from
-http://florian.loitsch.com/publications (bench.tar.gz).
-
-The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.
-
-For a detailed description of the algorithm see:
-
-[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with
- Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming
- Language Design and Implementation, PLDI 2010
-[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately",
- Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language
- Design and Implementation, PLDI 1996
-*/
-namespace dtoa_impl
-{
-
-template <typename Target, typename Source>
-Target reinterpret_bits(const Source source)
-{
- static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
-
- Target target;
- std::memcpy(&target, &source, sizeof(Source));
- return target;
-}
-
-struct diyfp // f * 2^e
-{
- static constexpr int kPrecision = 64; // = q
-
- uint64_t f;
- int e;
-
- constexpr diyfp() noexcept : f(0), e(0) {}
- constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {}
-
- /*!
- @brief returns x - y
- @pre x.e == y.e and x.f >= y.f
- */
- static diyfp sub(const diyfp& x, const diyfp& y) noexcept
- {
- assert(x.e == y.e);
- assert(x.f >= y.f);
-
- return diyfp(x.f - y.f, x.e);
- }
-
- /*!
- @brief returns x * y
- @note The result is rounded. (Only the upper q bits are returned.)
- */
- static diyfp mul(const diyfp& x, const diyfp& y) noexcept
- {
- static_assert(kPrecision == 64, "internal error");
-
- // Computes:
- // f = round((x.f * y.f) / 2^q)
- // e = x.e + y.e + q
-
- // Emulate the 64-bit * 64-bit multiplication:
- //
- // p = u * v
- // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)
- // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi )
- // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 )
- // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 )
- // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3)
- // = (p0_lo ) + 2^32 (Q ) + 2^64 (H )
- // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H )
- //
- // (Since Q might be larger than 2^32 - 1)
- //
- // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)
- //
- // (Q_hi + H does not overflow a 64-bit int)
- //
- // = p_lo + 2^64 p_hi
-
- const uint64_t u_lo = x.f & 0xFFFFFFFF;
- const uint64_t u_hi = x.f >> 32;
- const uint64_t v_lo = y.f & 0xFFFFFFFF;
- const uint64_t v_hi = y.f >> 32;
-
- const uint64_t p0 = u_lo * v_lo;
- const uint64_t p1 = u_lo * v_hi;
- const uint64_t p2 = u_hi * v_lo;
- const uint64_t p3 = u_hi * v_hi;
-
- const uint64_t p0_hi = p0 >> 32;
- const uint64_t p1_lo = p1 & 0xFFFFFFFF;
- const uint64_t p1_hi = p1 >> 32;
- const uint64_t p2_lo = p2 & 0xFFFFFFFF;
- const uint64_t p2_hi = p2 >> 32;
-
- uint64_t Q = p0_hi + p1_lo + p2_lo;
-
- // The full product might now be computed as
- //
- // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)
- // p_lo = p0_lo + (Q << 32)
- //
- // But in this particular case here, the full p_lo is not required.
- // Effectively we only need to add the highest bit in p_lo to p_hi (and
- // Q_hi + 1 does not overflow).
-
- Q += uint64_t{1} << (64 - 32 - 1); // round, ties up
-
- const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32);
-
- return diyfp(h, x.e + y.e + 64);
- }
-
- /*!
- @brief normalize x such that the significand is >= 2^(q-1)
- @pre x.f != 0
- */
- static diyfp normalize(diyfp x) noexcept
- {
- assert(x.f != 0);
-
- while ((x.f >> 63) == 0)
- {
- x.f <<= 1;
- x.e--;
- }
-
- return x;
- }
-
- /*!
- @brief normalize x such that the result has the exponent E
- @pre e >= x.e and the upper e - x.e bits of x.f must be zero.
- */
- static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept
- {
- const int delta = x.e - target_exponent;
-
- assert(delta >= 0);
- assert(((x.f << delta) >> delta) == x.f);
-
- return diyfp(x.f << delta, target_exponent);
- }
-};
-
-struct boundaries
-{
- diyfp w;
- diyfp minus;
- diyfp plus;
-};
-
-/*!
-Compute the (normalized) diyfp representing the input number 'value' and its
-boundaries.
-
-@pre value must be finite and positive
-*/
-template <typename FloatType>
-boundaries compute_boundaries(FloatType value)
-{
- assert(std::isfinite(value));
- assert(value > 0);
-
- // Convert the IEEE representation into a diyfp.
- //
- // If v is denormal:
- // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1))
- // If v is normalized:
- // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
-
- static_assert(std::numeric_limits<FloatType>::is_iec559,
- "internal error: dtoa_short requires an IEEE-754 floating-point implementation");
-
- constexpr int kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)
- constexpr int kBias = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);
- constexpr int kMinExp = 1 - kBias;
- constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1)
-
- using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type;
-
- const uint64_t bits = reinterpret_bits<bits_type>(value);
- const uint64_t E = bits >> (kPrecision - 1);
- const uint64_t F = bits & (kHiddenBit - 1);
-
- const bool is_denormal = (E == 0);
- const diyfp v = is_denormal
- ? diyfp(F, kMinExp)
- : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);
-
- // Compute the boundaries m- and m+ of the floating-point value
- // v = f * 2^e.
- //
- // Determine v- and v+, the floating-point predecessor and successor if v,
- // respectively.
- //
- // v- = v - 2^e if f != 2^(p-1) or e == e_min (A)
- // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B)
- //
- // v+ = v + 2^e
- //
- // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_
- // between m- and m+ round to v, regardless of how the input rounding
- // algorithm breaks ties.
- //
- // ---+-------------+-------------+-------------+-------------+--- (A)
- // v- m- v m+ v+
- //
- // -----------------+------+------+-------------+-------------+--- (B)
- // v- m- v m+ v+
-
- const bool lower_boundary_is_closer = (F == 0 and E > 1);
- const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);
- const diyfp m_minus = lower_boundary_is_closer
- ? diyfp(4 * v.f - 1, v.e - 2) // (B)
- : diyfp(2 * v.f - 1, v.e - 1); // (A)
-
- // Determine the normalized w+ = m+.
- const diyfp w_plus = diyfp::normalize(m_plus);
-
- // Determine w- = m- such that e_(w-) = e_(w+).
- const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);
-
- return {diyfp::normalize(v), w_minus, w_plus};
-}
-
-// Given normalized diyfp w, Grisu needs to find a (normalized) cached
-// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies
-// within a certain range [alpha, gamma] (Definition 3.2 from [1])
-//
-// alpha <= e = e_c + e_w + q <= gamma
-//
-// or
-//
-// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q
-// <= f_c * f_w * 2^gamma
-//
-// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies
-//
-// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma
-//
-// or
-//
-// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma)
-//
-// The choice of (alpha,gamma) determines the size of the table and the form of
-// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well
-// in practice:
-//
-// The idea is to cut the number c * w = f * 2^e into two parts, which can be
-// processed independently: An integral part p1, and a fractional part p2:
-//
-// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e
-// = (f div 2^-e) + (f mod 2^-e) * 2^e
-// = p1 + p2 * 2^e
-//
-// The conversion of p1 into decimal form requires a series of divisions and
-// modulos by (a power of) 10. These operations are faster for 32-bit than for
-// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be
-// achieved by choosing
-//
-// -e >= 32 or e <= -32 := gamma
-//
-// In order to convert the fractional part
-//
-// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...
-//
-// into decimal form, the fraction is repeatedly multiplied by 10 and the digits
-// d[-i] are extracted in order:
-//
-// (10 * p2) div 2^-e = d[-1]
-// (10 * p2) mod 2^-e = d[-2] / 10^1 + ...
-//
-// The multiplication by 10 must not overflow. It is sufficient to choose
-//
-// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.
-//
-// Since p2 = f mod 2^-e < 2^-e,
-//
-// -e <= 60 or e >= -60 := alpha
-
-constexpr int kAlpha = -60;
-constexpr int kGamma = -32;
-
-struct cached_power // c = f * 2^e ~= 10^k
-{
- uint64_t f;
- int e;
- int k;
-};
-
-/*!
-For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached
-power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c
-satisfies (Definition 3.2 from [1])
-
- alpha <= e_c + e + q <= gamma.
-*/
-inline cached_power get_cached_power_for_binary_exponent(int e)
-{
- // Now
- //
- // alpha <= e_c + e + q <= gamma (1)
- // ==> f_c * 2^alpha <= c * 2^e * 2^q
- //
- // and since the c's are normalized, 2^(q-1) <= f_c,
- //
- // ==> 2^(q - 1 + alpha) <= c * 2^(e + q)
- // ==> 2^(alpha - e - 1) <= c
- //
- // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as
- //
- // k = ceil( log_10( 2^(alpha - e - 1) ) )
- // = ceil( (alpha - e - 1) * log_10(2) )
- //
- // From the paper:
- // "In theory the result of the procedure could be wrong since c is rounded,
- // and the computation itself is approximated [...]. In practice, however,
- // this simple function is sufficient."
- //
- // For IEEE double precision floating-point numbers converted into
- // normalized diyfp's w = f * 2^e, with q = 64,
- //
- // e >= -1022 (min IEEE exponent)
- // -52 (p - 1)
- // -52 (p - 1, possibly normalize denormal IEEE numbers)
- // -11 (normalize the diyfp)
- // = -1137
- //
- // and
- //
- // e <= +1023 (max IEEE exponent)
- // -52 (p - 1)
- // -11 (normalize the diyfp)
- // = 960
- //
- // This binary exponent range [-1137,960] results in a decimal exponent
- // range [-307,324]. One does not need to store a cached power for each
- // k in this range. For each such k it suffices to find a cached power
- // such that the exponent of the product lies in [alpha,gamma].
- // This implies that the difference of the decimal exponents of adjacent
- // table entries must be less than or equal to
- //
- // floor( (gamma - alpha) * log_10(2) ) = 8.
- //
- // (A smaller distance gamma-alpha would require a larger table.)
-
- // NB:
- // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.
-
- constexpr int kCachedPowersSize = 79;
- constexpr int kCachedPowersMinDecExp = -300;
- constexpr int kCachedPowersDecStep = 8;
-
- static constexpr cached_power kCachedPowers[] =
- {
- { 0xAB70FE17C79AC6CA, -1060, -300 },
- { 0xFF77B1FCBEBCDC4F, -1034, -292 },
- { 0xBE5691EF416BD60C, -1007, -284 },
- { 0x8DD01FAD907FFC3C, -980, -276 },
- { 0xD3515C2831559A83, -954, -268 },
- { 0x9D71AC8FADA6C9B5, -927, -260 },
- { 0xEA9C227723EE8BCB, -901, -252 },
- { 0xAECC49914078536D, -874, -244 },
- { 0x823C12795DB6CE57, -847, -236 },
- { 0xC21094364DFB5637, -821, -228 },
- { 0x9096EA6F3848984F, -794, -220 },
- { 0xD77485CB25823AC7, -768, -212 },
- { 0xA086CFCD97BF97F4, -741, -204 },
- { 0xEF340A98172AACE5, -715, -196 },
- { 0xB23867FB2A35B28E, -688, -188 },
- { 0x84C8D4DFD2C63F3B, -661, -180 },
- { 0xC5DD44271AD3CDBA, -635, -172 },
- { 0x936B9FCEBB25C996, -608, -164 },
- { 0xDBAC6C247D62A584, -582, -156 },
- { 0xA3AB66580D5FDAF6, -555, -148 },
- { 0xF3E2F893DEC3F126, -529, -140 },
- { 0xB5B5ADA8AAFF80B8, -502, -132 },
- { 0x87625F056C7C4A8B, -475, -124 },
- { 0xC9BCFF6034C13053, -449, -116 },
- { 0x964E858C91BA2655, -422, -108 },
- { 0xDFF9772470297EBD, -396, -100 },
- { 0xA6DFBD9FB8E5B88F, -369, -92 },
- { 0xF8A95FCF88747D94, -343, -84 },
- { 0xB94470938FA89BCF, -316, -76 },
- { 0x8A08F0F8BF0F156B, -289, -68 },
- { 0xCDB02555653131B6, -263, -60 },
- { 0x993FE2C6D07B7FAC, -236, -52 },
- { 0xE45C10C42A2B3B06, -210, -44 },
- { 0xAA242499697392D3, -183, -36 },
- { 0xFD87B5F28300CA0E, -157, -28 },
- { 0xBCE5086492111AEB, -130, -20 },
- { 0x8CBCCC096F5088CC, -103, -12 },
- { 0xD1B71758E219652C, -77, -4 },
- { 0x9C40000000000000, -50, 4 },
- { 0xE8D4A51000000000, -24, 12 },
- { 0xAD78EBC5AC620000, 3, 20 },
- { 0x813F3978F8940984, 30, 28 },
- { 0xC097CE7BC90715B3, 56, 36 },
- { 0x8F7E32CE7BEA5C70, 83, 44 },
- { 0xD5D238A4ABE98068, 109, 52 },
- { 0x9F4F2726179A2245, 136, 60 },
- { 0xED63A231D4C4FB27, 162, 68 },
- { 0xB0DE65388CC8ADA8, 189, 76 },
- { 0x83C7088E1AAB65DB, 216, 84 },
- { 0xC45D1DF942711D9A, 242, 92 },
- { 0x924D692CA61BE758, 269, 100 },
- { 0xDA01EE641A708DEA, 295, 108 },
- { 0xA26DA3999AEF774A, 322, 116 },
- { 0xF209787BB47D6B85, 348, 124 },
- { 0xB454E4A179DD1877, 375, 132 },
- { 0x865B86925B9BC5C2, 402, 140 },
- { 0xC83553C5C8965D3D, 428, 148 },
- { 0x952AB45CFA97A0B3, 455, 156 },
- { 0xDE469FBD99A05FE3, 481, 164 },
- { 0xA59BC234DB398C25, 508, 172 },
- { 0xF6C69A72A3989F5C, 534, 180 },
- { 0xB7DCBF5354E9BECE, 561, 188 },
- { 0x88FCF317F22241E2, 588, 196 },
- { 0xCC20CE9BD35C78A5, 614, 204 },
- { 0x98165AF37B2153DF, 641, 212 },
- { 0xE2A0B5DC971F303A, 667, 220 },
- { 0xA8D9D1535CE3B396, 694, 228 },
- { 0xFB9B7CD9A4A7443C, 720, 236 },
- { 0xBB764C4CA7A44410, 747, 244 },
- { 0x8BAB8EEFB6409C1A, 774, 252 },
- { 0xD01FEF10A657842C, 800, 260 },
- { 0x9B10A4E5E9913129, 827, 268 },
- { 0xE7109BFBA19C0C9D, 853, 276 },
- { 0xAC2820D9623BF429, 880, 284 },
- { 0x80444B5E7AA7CF85, 907, 292 },
- { 0xBF21E44003ACDD2D, 933, 300 },
- { 0x8E679C2F5E44FF8F, 960, 308 },
- { 0xD433179D9C8CB841, 986, 316 },
- { 0x9E19DB92B4E31BA9, 1013, 324 },
- };
-
- // This computation gives exactly the same results for k as
- // k = ceil((kAlpha - e - 1) * 0.30102999566398114)
- // for |e| <= 1500, but doesn't require floating-point operations.
- // NB: log_10(2) ~= 78913 / 2^18
- assert(e >= -1500);
- assert(e <= 1500);
- const int f = kAlpha - e - 1;
- const int k = (f * 78913) / (1 << 18) + (f > 0);
-
- const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;
- assert(index >= 0);
- assert(index < kCachedPowersSize);
- static_cast<void>(kCachedPowersSize); // Fix warning.
-
- const cached_power cached = kCachedPowers[index];
- assert(kAlpha <= cached.e + e + 64);
- assert(kGamma >= cached.e + e + 64);
-
- return cached;
-}
-
-/*!
-For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.
-For n == 0, returns 1 and sets pow10 := 1.
-*/
-inline int find_largest_pow10(const uint32_t n, uint32_t& pow10)
-{
- // LCOV_EXCL_START
- if (n >= 1000000000)
- {
- pow10 = 1000000000;
- return 10;
- }
- // LCOV_EXCL_STOP
- else if (n >= 100000000)
- {
- pow10 = 100000000;
- return 9;
- }
- else if (n >= 10000000)
- {
- pow10 = 10000000;
- return 8;
- }
- else if (n >= 1000000)
- {
- pow10 = 1000000;
- return 7;
- }
- else if (n >= 100000)
- {
- pow10 = 100000;
- return 6;
- }
- else if (n >= 10000)
- {
- pow10 = 10000;
- return 5;
- }
- else if (n >= 1000)
- {
- pow10 = 1000;
- return 4;
- }
- else if (n >= 100)
- {
- pow10 = 100;
- return 3;
- }
- else if (n >= 10)
- {
- pow10 = 10;
- return 2;
- }
- else
- {
- pow10 = 1;
- return 1;
- }
-}
-
-inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta,
- uint64_t rest, uint64_t ten_k)
-{
- assert(len >= 1);
- assert(dist <= delta);
- assert(rest <= delta);
- assert(ten_k > 0);
-
- // <--------------------------- delta ---->
- // <---- dist --------->
- // --------------[------------------+-------------------]--------------
- // M- w M+
- //
- // ten_k
- // <------>
- // <---- rest ---->
- // --------------[------------------+----+--------------]--------------
- // w V
- // = buf * 10^k
- //
- // ten_k represents a unit-in-the-last-place in the decimal representation
- // stored in buf.
- // Decrement buf by ten_k while this takes buf closer to w.
-
- // The tests are written in this order to avoid overflow in unsigned
- // integer arithmetic.
-
- while (rest < dist
- and delta - rest >= ten_k
- and (rest + ten_k < dist or dist - rest > rest + ten_k - dist))
- {
- assert(buf[len - 1] != '0');
- buf[len - 1]--;
- rest += ten_k;
- }
-}
-
-/*!
-Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.
-M- and M+ must be normalized and share the same exponent -60 <= e <= -32.
-*/
-inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,
- diyfp M_minus, diyfp w, diyfp M_plus)
-{
- static_assert(kAlpha >= -60, "internal error");
- static_assert(kGamma <= -32, "internal error");
-
- // Generates the digits (and the exponent) of a decimal floating-point
- // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's
- // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.
- //
- // <--------------------------- delta ---->
- // <---- dist --------->
- // --------------[------------------+-------------------]--------------
- // M- w M+
- //
- // Grisu2 generates the digits of M+ from left to right and stops as soon as
- // V is in [M-,M+].
-
- assert(M_plus.e >= kAlpha);
- assert(M_plus.e <= kGamma);
-
- uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)
- uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e)
-
- // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):
- //
- // M+ = f * 2^e
- // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e
- // = ((p1 ) * 2^-e + (p2 )) * 2^e
- // = p1 + p2 * 2^e
-
- const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e);
-
- uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
- uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e
-
- // 1)
- //
- // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]
-
- assert(p1 > 0);
-
- uint32_t pow10;
- const int k = find_largest_pow10(p1, pow10);
-
- // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)
- //
- // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))
- // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1))
- //
- // M+ = p1 + p2 * 2^e
- // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e
- // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e
- // = d[k-1] * 10^(k-1) + ( rest) * 2^e
- //
- // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)
- //
- // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]
- //
- // but stop as soon as
- //
- // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e
-
- int n = k;
- while (n > 0)
- {
- // Invariants:
- // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k)
- // pow10 = 10^(n-1) <= p1 < 10^n
- //
- const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1)
- const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1)
- //
- // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e
- // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)
- //
- assert(d <= 9);
- buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
- //
- // M+ = buffer * 10^(n-1) + (r + p2 * 2^e)
- //
- p1 = r;
- n--;
- //
- // M+ = buffer * 10^n + (p1 + p2 * 2^e)
- // pow10 = 10^n
- //
-
- // Now check if enough digits have been generated.
- // Compute
- //
- // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e
- //
- // Note:
- // Since rest and delta share the same exponent e, it suffices to
- // compare the significands.
- const uint64_t rest = (uint64_t{p1} << -one.e) + p2;
- if (rest <= delta)
- {
- // V = buffer * 10^n, with M- <= V <= M+.
-
- decimal_exponent += n;
-
- // We may now just stop. But instead look if the buffer could be
- // decremented to bring V closer to w.
- //
- // pow10 = 10^n is now 1 ulp in the decimal representation V.
- // The rounding procedure works with diyfp's with an implicit
- // exponent of e.
- //
- // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e
- //
- const uint64_t ten_n = uint64_t{pow10} << -one.e;
- grisu2_round(buffer, length, dist, delta, rest, ten_n);
-
- return;
- }
-
- pow10 /= 10;
- //
- // pow10 = 10^(n-1) <= p1 < 10^n
- // Invariants restored.
- }
-
- // 2)
- //
- // The digits of the integral part have been generated:
- //
- // M+ = d[k-1]...d[1]d[0] + p2 * 2^e
- // = buffer + p2 * 2^e
- //
- // Now generate the digits of the fractional part p2 * 2^e.
- //
- // Note:
- // No decimal point is generated: the exponent is adjusted instead.
- //
- // p2 actually represents the fraction
- //
- // p2 * 2^e
- // = p2 / 2^-e
- // = d[-1] / 10^1 + d[-2] / 10^2 + ...
- //
- // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)
- //
- // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m
- // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)
- //
- // using
- //
- // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)
- // = ( d) * 2^-e + ( r)
- //
- // or
- // 10^m * p2 * 2^e = d + r * 2^e
- //
- // i.e.
- //
- // M+ = buffer + p2 * 2^e
- // = buffer + 10^-m * (d + r * 2^e)
- // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e
- //
- // and stop as soon as 10^-m * r * 2^e <= delta * 2^e
-
- assert(p2 > delta);
-
- int m = 0;
- for (;;)
- {
- // Invariant:
- // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e
- // = buffer * 10^-m + 10^-m * (p2 ) * 2^e
- // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e
- // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e
- //
- assert(p2 <= UINT64_MAX / 10);
- p2 *= 10;
- const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e
- const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e
- //
- // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e
- // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))
- // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e
- //
- assert(d <= 9);
- buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
- //
- // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e
- //
- p2 = r;
- m++;
- //
- // M+ = buffer * 10^-m + 10^-m * p2 * 2^e
- // Invariant restored.
-
- // Check if enough digits have been generated.
- //
- // 10^-m * p2 * 2^e <= delta * 2^e
- // p2 * 2^e <= 10^m * delta * 2^e
- // p2 <= 10^m * delta
- delta *= 10;
- dist *= 10;
- if (p2 <= delta)
- {
- break;
- }
- }
-
- // V = buffer * 10^-m, with M- <= V <= M+.
-
- decimal_exponent -= m;
-
- // 1 ulp in the decimal representation is now 10^-m.
- // Since delta and dist are now scaled by 10^m, we need to do the
- // same with ulp in order to keep the units in sync.
- //
- // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e
- //
- const uint64_t ten_m = one.f;
- grisu2_round(buffer, length, dist, delta, p2, ten_m);
-
- // By construction this algorithm generates the shortest possible decimal
- // number (Loitsch, Theorem 6.2) which rounds back to w.
- // For an input number of precision p, at least
- //
- // N = 1 + ceil(p * log_10(2))
- //
- // decimal digits are sufficient to identify all binary floating-point
- // numbers (Matula, "In-and-Out conversions").
- // This implies that the algorithm does not produce more than N decimal
- // digits.
- //
- // N = 17 for p = 53 (IEEE double precision)
- // N = 9 for p = 24 (IEEE single precision)
-}
-
-/*!
-v = buf * 10^decimal_exponent
-len is the length of the buffer (number of decimal digits)
-The buffer must be large enough, i.e. >= max_digits10.
-*/
-inline void grisu2(char* buf, int& len, int& decimal_exponent,
- diyfp m_minus, diyfp v, diyfp m_plus)
-{
- assert(m_plus.e == m_minus.e);
- assert(m_plus.e == v.e);
-
- // --------(-----------------------+-----------------------)-------- (A)
- // m- v m+
- //
- // --------------------(-----------+-----------------------)-------- (B)
- // m- v m+
- //
- // First scale v (and m- and m+) such that the exponent is in the range
- // [alpha, gamma].
-
- const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);
-
- const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k
-
- // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]
- const diyfp w = diyfp::mul(v, c_minus_k);
- const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);
- const diyfp w_plus = diyfp::mul(m_plus, c_minus_k);
-
- // ----(---+---)---------------(---+---)---------------(---+---)----
- // w- w w+
- // = c*m- = c*v = c*m+
- //
- // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and
- // w+ are now off by a small amount.
- // In fact:
- //
- // w - v * 10^k < 1 ulp
- //
- // To account for this inaccuracy, add resp. subtract 1 ulp.
- //
- // --------+---[---------------(---+---)---------------]---+--------
- // w- M- w M+ w+
- //
- // Now any number in [M-, M+] (bounds included) will round to w when input,
- // regardless of how the input rounding algorithm breaks ties.
- //
- // And digit_gen generates the shortest possible such number in [M-, M+].
- // Note that this does not mean that Grisu2 always generates the shortest
- // possible number in the interval (m-, m+).
- const diyfp M_minus(w_minus.f + 1, w_minus.e);
- const diyfp M_plus (w_plus.f - 1, w_plus.e );
-
- decimal_exponent = -cached.k; // = -(-k) = k
-
- grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);
-}
-
-/*!
-v = buf * 10^decimal_exponent
-len is the length of the buffer (number of decimal digits)
-The buffer must be large enough, i.e. >= max_digits10.
-*/
-template <typename FloatType>
-void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
-{
- static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
- "internal error: not enough precision");
-
- assert(std::isfinite(value));
- assert(value > 0);
-
- // If the neighbors (and boundaries) of 'value' are always computed for double-precision
- // numbers, all float's can be recovered using strtod (and strtof). However, the resulting
- // decimal representations are not exactly "short".
- //
- // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars)
- // says "value is converted to a string as if by std::sprintf in the default ("C") locale"
- // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars'
- // does.
- // On the other hand, the documentation for 'std::to_chars' requires that "parsing the
- // representation using the corresponding std::from_chars function recovers value exactly". That
- // indicates that single precision floating-point numbers should be recovered using
- // 'std::strtof'.
- //
- // NB: If the neighbors are computed for single-precision numbers, there is a single float
- // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision
- // value is off by 1 ulp.
-#if 0
- const boundaries w = compute_boundaries(static_cast<double>(value));
-#else
- const boundaries w = compute_boundaries(value);
-#endif
-
- grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
-}
-
-/*!
-@brief appends a decimal representation of e to buf
-@return a pointer to the element following the exponent.
-@pre -1000 < e < 1000
-*/
-inline char* append_exponent(char* buf, int e)
-{
- assert(e > -1000);
- assert(e < 1000);
-
- if (e < 0)
- {
- e = -e;
- *buf++ = '-';
- }
- else
- {
- *buf++ = '+';
- }
-
- uint32_t k = static_cast<uint32_t>(e);
- if (k < 10)
- {
- // Always print at least two digits in the exponent.
- // This is for compatibility with printf("%g").
- *buf++ = '0';
- *buf++ = static_cast<char>('0' + k);
- }
- else if (k < 100)
- {
- *buf++ = static_cast<char>('0' + k / 10);
- k %= 10;
- *buf++ = static_cast<char>('0' + k);
- }
- else
- {
- *buf++ = static_cast<char>('0' + k / 100);
- k %= 100;
- *buf++ = static_cast<char>('0' + k / 10);
- k %= 10;
- *buf++ = static_cast<char>('0' + k);
- }
-
- return buf;
-}
-
-/*!
-@brief prettify v = buf * 10^decimal_exponent
-
-If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point
-notation. Otherwise it will be printed in exponential notation.
-
-@pre min_exp < 0
-@pre max_exp > 0
-*/
-inline char* format_buffer(char* buf, int len, int decimal_exponent,
- int min_exp, int max_exp)
-{
- assert(min_exp < 0);
- assert(max_exp > 0);
-
- const int k = len;
- const int n = len + decimal_exponent;
-
- // v = buf * 10^(n-k)
- // k is the length of the buffer (number of decimal digits)
- // n is the position of the decimal point relative to the start of the buffer.
-
- if (k <= n and n <= max_exp)
- {
- // digits[000]
- // len <= max_exp + 2
-
- std::memset(buf + k, '0', static_cast<size_t>(n - k));
- // Make it look like a floating-point number (#362, #378)
- buf[n + 0] = '.';
- buf[n + 1] = '0';
- return buf + (n + 2);
- }
-
- if (0 < n and n <= max_exp)
- {
- // dig.its
- // len <= max_digits10 + 1
-
- assert(k > n);
-
- std::memmove(buf + (n + 1), buf + n, static_cast<size_t>(k - n));
- buf[n] = '.';
- return buf + (k + 1);
- }
-
- if (min_exp < n and n <= 0)
- {
- // 0.[000]digits
- // len <= 2 + (-min_exp - 1) + max_digits10
-
- std::memmove(buf + (2 + -n), buf, static_cast<size_t>(k));
- buf[0] = '0';
- buf[1] = '.';
- std::memset(buf + 2, '0', static_cast<size_t>(-n));
- return buf + (2 + (-n) + k);
- }
-
- if (k == 1)
- {
- // dE+123
- // len <= 1 + 5
-
- buf += 1;
- }
- else
- {
- // d.igitsE+123
- // len <= max_digits10 + 1 + 5
-
- std::memmove(buf + 2, buf + 1, static_cast<size_t>(k - 1));
- buf[1] = '.';
- buf += 1 + k;
- }
-
- *buf++ = 'e';
- return append_exponent(buf, n - 1);
-}
-
-} // namespace dtoa_impl
-
-/*!
-@brief generates a decimal representation of the floating-point number value in [first, last).
-
-The format of the resulting decimal representation is similar to printf's %g
-format. Returns an iterator pointing past-the-end of the decimal representation.
-
-@note The input number must be finite, i.e. NaN's and Inf's are not supported.
-@note The buffer must be large enough.
-@note The result is NOT null-terminated.
-*/
-template <typename FloatType>
-char* to_chars(char* first, char* last, FloatType value)
-{
- static_cast<void>(last); // maybe unused - fix warning
- assert(std::isfinite(value));
-
- // Use signbit(value) instead of (value < 0) since signbit works for -0.
- if (std::signbit(value))
- {
- value = -value;
- *first++ = '-';
- }
-
- if (value == 0) // +-0
- {
- *first++ = '0';
- // Make it look like a floating-point number (#362, #378)
- *first++ = '.';
- *first++ = '0';
- return first;
- }
-
- assert(last - first >= std::numeric_limits<FloatType>::max_digits10);
-
- // Compute v = buffer * 10^decimal_exponent.
- // The decimal digits are stored in the buffer, which needs to be interpreted
- // as an unsigned decimal integer.
- // len is the length of the buffer, i.e. the number of decimal digits.
- int len = 0;
- int decimal_exponent = 0;
- dtoa_impl::grisu2(first, len, decimal_exponent, value);
-
- assert(len <= std::numeric_limits<FloatType>::max_digits10);
-
- // Format the buffer like printf("%.*g", prec, value)
- constexpr int kMinExp = -4;
- // Use digits10 here to increase compatibility with version 2.
- constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
-
- assert(last - first >= kMaxExp + 2);
- assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
- assert(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
-
- return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
-}
-
-} // namespace detail
-} // namespace nlohmann
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/meta.hpp>
-
-// #include <nlohmann/detail/output/output_adapters.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-///////////////////
-// serialization //
-///////////////////
-
-template<typename BasicJsonType>
-class serializer
-{
- using string_t = typename BasicJsonType::string_t;
- using number_float_t = typename BasicJsonType::number_float_t;
- using number_integer_t = typename BasicJsonType::number_integer_t;
- using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
- static constexpr uint8_t UTF8_ACCEPT = 0;
- static constexpr uint8_t UTF8_REJECT = 1;
-
- public:
- /*!
- @param[in] s output stream to serialize to
- @param[in] ichar indentation character to use
- */
- serializer(output_adapter_t<char> s, const char ichar)
- : o(std::move(s)), loc(std::localeconv()),
- thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)),
- decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)),
- indent_char(ichar), indent_string(512, indent_char)
- {}
-
- // delete because of pointer members
- serializer(const serializer&) = delete;
- serializer& operator=(const serializer&) = delete;
-
- /*!
- @brief internal implementation of the serialization function
-
- This function is called by the public member function dump and organizes
- the serialization internally. The indentation level is propagated as
- additional parameter. In case of arrays and objects, the function is
- called recursively.
-
- - strings and object keys are escaped using `escape_string()`
- - integer numbers are converted implicitly via `operator<<`
- - floating-point numbers are converted to a string using `"%g"` format
-
- @param[in] val value to serialize
- @param[in] pretty_print whether the output shall be pretty-printed
- @param[in] indent_step the indent level
- @param[in] current_indent the current indent level (only used internally)
- */
- void dump(const BasicJsonType& val, const bool pretty_print,
- const bool ensure_ascii,
- const unsigned int indent_step,
- const unsigned int current_indent = 0)
- {
- switch (val.m_type)
- {
- case value_t::object:
- {
- if (val.m_value.object->empty())
- {
- o->write_characters("{}", 2);
- return;
- }
-
- if (pretty_print)
- {
- o->write_characters("{\n", 2);
-
- // variable to hold indentation for recursive calls
- const auto new_indent = current_indent + indent_step;
- if (JSON_UNLIKELY(indent_string.size() < new_indent))
- {
- indent_string.resize(indent_string.size() * 2, ' ');
- }
-
- // first n-1 elements
- auto i = val.m_value.object->cbegin();
- for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
- {
- o->write_characters(indent_string.c_str(), new_indent);
- o->write_character('\"');
- dump_escaped(i->first, ensure_ascii);
- o->write_characters("\": ", 3);
- dump(i->second, true, ensure_ascii, indent_step, new_indent);
- o->write_characters(",\n", 2);
- }
-
- // last element
- assert(i != val.m_value.object->cend());
- assert(std::next(i) == val.m_value.object->cend());
- o->write_characters(indent_string.c_str(), new_indent);
- o->write_character('\"');
- dump_escaped(i->first, ensure_ascii);
- o->write_characters("\": ", 3);
- dump(i->second, true, ensure_ascii, indent_step, new_indent);
-
- o->write_character('\n');
- o->write_characters(indent_string.c_str(), current_indent);
- o->write_character('}');
- }
- else
- {
- o->write_character('{');
-
- // first n-1 elements
- auto i = val.m_value.object->cbegin();
- for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
- {
- o->write_character('\"');
- dump_escaped(i->first, ensure_ascii);
- o->write_characters("\":", 2);
- dump(i->second, false, ensure_ascii, indent_step, current_indent);
- o->write_character(',');
- }
-
- // last element
- assert(i != val.m_value.object->cend());
- assert(std::next(i) == val.m_value.object->cend());
- o->write_character('\"');
- dump_escaped(i->first, ensure_ascii);
- o->write_characters("\":", 2);
- dump(i->second, false, ensure_ascii, indent_step, current_indent);
-
- o->write_character('}');
- }
-
- return;
- }
-
- case value_t::array:
- {
- if (val.m_value.array->empty())
- {
- o->write_characters("[]", 2);
- return;
- }
-
- if (pretty_print)
- {
- o->write_characters("[\n", 2);
-
- // variable to hold indentation for recursive calls
- const auto new_indent = current_indent + indent_step;
- if (JSON_UNLIKELY(indent_string.size() < new_indent))
- {
- indent_string.resize(indent_string.size() * 2, ' ');
- }
-
- // first n-1 elements
- for (auto i = val.m_value.array->cbegin();
- i != val.m_value.array->cend() - 1; ++i)
- {
- o->write_characters(indent_string.c_str(), new_indent);
- dump(*i, true, ensure_ascii, indent_step, new_indent);
- o->write_characters(",\n", 2);
- }
-
- // last element
- assert(not val.m_value.array->empty());
- o->write_characters(indent_string.c_str(), new_indent);
- dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
-
- o->write_character('\n');
- o->write_characters(indent_string.c_str(), current_indent);
- o->write_character(']');
- }
- else
- {
- o->write_character('[');
-
- // first n-1 elements
- for (auto i = val.m_value.array->cbegin();
- i != val.m_value.array->cend() - 1; ++i)
- {
- dump(*i, false, ensure_ascii, indent_step, current_indent);
- o->write_character(',');
- }
-
- // last element
- assert(not val.m_value.array->empty());
- dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
-
- o->write_character(']');
- }
-
- return;
- }
-
- case value_t::string:
- {
- o->write_character('\"');
- dump_escaped(*val.m_value.string, ensure_ascii);
- o->write_character('\"');
- return;
- }
-
- case value_t::boolean:
- {
- if (val.m_value.boolean)
- {
- o->write_characters("true", 4);
- }
- else
- {
- o->write_characters("false", 5);
- }
- return;
- }
-
- case value_t::number_integer:
- {
- dump_integer(val.m_value.number_integer);
- return;
- }
-
- case value_t::number_unsigned:
- {
- dump_integer(val.m_value.number_unsigned);
- return;
- }
-
- case value_t::number_float:
- {
- dump_float(val.m_value.number_float);
- return;
- }
-
- case value_t::discarded:
- {
- o->write_characters("<discarded>", 11);
- return;
- }
-
- case value_t::null:
- {
- o->write_characters("null", 4);
- return;
- }
- }
- }
-
- private:
- /*!
- @brief dump escaped string
-
- Escape a string by replacing certain special characters by a sequence of an
- escape character (backslash) and another character and other control
- characters by a sequence of "\u" followed by a four-digit hex
- representation. The escaped string is written to output stream @a o.
-
- @param[in] s the string to escape
- @param[in] ensure_ascii whether to escape non-ASCII characters with
- \uXXXX sequences
-
- @complexity Linear in the length of string @a s.
- */
- void dump_escaped(const string_t& s, const bool ensure_ascii)
- {
- uint32_t codepoint;
- uint8_t state = UTF8_ACCEPT;
- std::size_t bytes = 0; // number of bytes written to string_buffer
-
- for (std::size_t i = 0; i < s.size(); ++i)
- {
- const auto byte = static_cast<uint8_t>(s[i]);
-
- switch (decode(state, codepoint, byte))
- {
- case UTF8_ACCEPT: // decode found a new code point
- {
- switch (codepoint)
- {
- case 0x08: // backspace
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = 'b';
- break;
- }
-
- case 0x09: // horizontal tab
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = 't';
- break;
- }
-
- case 0x0A: // newline
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = 'n';
- break;
- }
-
- case 0x0C: // formfeed
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = 'f';
- break;
- }
-
- case 0x0D: // carriage return
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = 'r';
- break;
- }
-
- case 0x22: // quotation mark
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = '\"';
- break;
- }
-
- case 0x5C: // reverse solidus
- {
- string_buffer[bytes++] = '\\';
- string_buffer[bytes++] = '\\';
- break;
- }
-
- default:
- {
- // escape control characters (0x00..0x1F) or, if
- // ensure_ascii parameter is used, non-ASCII characters
- if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
- {
- if (codepoint <= 0xFFFF)
- {
- std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x",
- static_cast<uint16_t>(codepoint));
- bytes += 6;
- }
- else
- {
- std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
- static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)),
- static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
- bytes += 12;
- }
- }
- else
- {
- // copy byte to buffer (all previous bytes
- // been copied have in default case above)
- string_buffer[bytes++] = s[i];
- }
- break;
- }
- }
-
- // write buffer and reset index; there must be 13 bytes
- // left, as this is the maximal number of bytes to be
- // written ("\uxxxx\uxxxx\0") for one code point
- if (string_buffer.size() - bytes < 13)
- {
- o->write_characters(string_buffer.data(), bytes);
- bytes = 0;
- }
- break;
- }
-
- case UTF8_REJECT: // decode found invalid UTF-8 byte
- {
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast<int>(byte);
- JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str()));
- }
-
- default: // decode found yet incomplete multi-byte code point
- {
- if (not ensure_ascii)
- {
- // code point will not be escaped - copy byte to buffer
- string_buffer[bytes++] = s[i];
- }
- break;
- }
- }
- }
-
- if (JSON_LIKELY(state == UTF8_ACCEPT))
- {
- // write buffer
- if (bytes > 0)
- {
- o->write_characters(string_buffer.data(), bytes);
- }
- }
- else
- {
- // we finish reading, but do not accept: string was incomplete
- std::stringstream ss;
- ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast<int>(static_cast<uint8_t>(s.back()));
- JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str()));
- }
- }
-
- /*!
- @brief dump an integer
-
- Dump a given integer to output stream @a o. Works internally with
- @a number_buffer.
-
- @param[in] x integer number (signed or unsigned) to dump
- @tparam NumberType either @a number_integer_t or @a number_unsigned_t
- */
- template<typename NumberType, detail::enable_if_t<
- std::is_same<NumberType, number_unsigned_t>::value or
- std::is_same<NumberType, number_integer_t>::value,
- int> = 0>
- void dump_integer(NumberType x)
- {
- // special case for "0"
- if (x == 0)
- {
- o->write_character('0');
- return;
- }
-
- const bool is_negative = (x <= 0) and (x != 0); // see issue #755
- std::size_t i = 0;
-
- while (x != 0)
- {
- // spare 1 byte for '\0'
- assert(i < number_buffer.size() - 1);
-
- const auto digit = std::labs(static_cast<long>(x % 10));
- number_buffer[i++] = static_cast<char>('0' + digit);
- x /= 10;
- }
-
- if (is_negative)
- {
- // make sure there is capacity for the '-'
- assert(i < number_buffer.size() - 2);
- number_buffer[i++] = '-';
- }
-
- std::reverse(number_buffer.begin(), number_buffer.begin() + i);
- o->write_characters(number_buffer.data(), i);
- }
-
- /*!
- @brief dump a floating-point number
-
- Dump a given floating-point number to output stream @a o. Works internally
- with @a number_buffer.
-
- @param[in] x floating-point number to dump
- */
- void dump_float(number_float_t x)
- {
- // NaN / inf
- if (not std::isfinite(x))
- {
- o->write_characters("null", 4);
- return;
- }
-
- // If number_float_t is an IEEE-754 single or double precision number,
- // use the Grisu2 algorithm to produce short numbers which are
- // guaranteed to round-trip, using strtof and strtod, resp.
- //
- // NB: The test below works if <long double> == <double>.
- static constexpr bool is_ieee_single_or_double
- = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and std::numeric_limits<number_float_t>::max_exponent == 128) or
- (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and std::numeric_limits<number_float_t>::max_exponent == 1024);
-
- dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
- }
-
- void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
- {
- char* begin = number_buffer.data();
- char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
-
- o->write_characters(begin, static_cast<size_t>(end - begin));
- }
-
- void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
- {
- // get number of digits for a float -> text -> float round-trip
- static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
-
- // the actual conversion
- std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
-
- // negative value indicates an error
- assert(len > 0);
- // check if buffer was large enough
- assert(static_cast<std::size_t>(len) < number_buffer.size());
-
- // erase thousands separator
- if (thousands_sep != '\0')
- {
- const auto end = std::remove(number_buffer.begin(),
- number_buffer.begin() + len, thousands_sep);
- std::fill(end, number_buffer.end(), '\0');
- assert((end - number_buffer.begin()) <= len);
- len = (end - number_buffer.begin());
- }
-
- // convert decimal point to '.'
- if (decimal_point != '\0' and decimal_point != '.')
- {
- const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
- if (dec_pos != number_buffer.end())
- {
- *dec_pos = '.';
- }
- }
-
- o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
-
- // determine if need to append ".0"
- const bool value_is_int_like =
- std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
- [](char c)
- {
- return (c == '.' or c == 'e');
- });
-
- if (value_is_int_like)
- {
- o->write_characters(".0", 2);
- }
- }
-
- /*!
- @brief check whether a string is UTF-8 encoded
-
- The function checks each byte of a string whether it is UTF-8 encoded. The
- result of the check is stored in the @a state parameter. The function must
- be called initially with state 0 (accept). State 1 means the string must
- be rejected, because the current byte is not allowed. If the string is
- completely processed, but the state is non-zero, the string ended
- prematurely; that is, the last byte indicated more bytes should have
- followed.
-
- @param[in,out] state the state of the decoding
- @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
- @param[in] byte next byte to decode
- @return new state
-
- @note The function has been edited: a std::array is used.
-
- @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
- @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
- */
- static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
- {
- static const std::array<uint8_t, 400> utf8d =
- {
- {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
- 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
- 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
- 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
- 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
- 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
- 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
- 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
- }
- };
-
- const uint8_t type = utf8d[byte];
-
- codep = (state != UTF8_ACCEPT)
- ? (byte & 0x3fu) | (codep << 6)
- : static_cast<uint32_t>(0xff >> type) & (byte);
-
- state = utf8d[256u + state * 16u + type];
- return state;
- }
-
- private:
- /// the output of the serializer
- output_adapter_t<char> o = nullptr;
-
- /// a (hopefully) large enough character buffer
- std::array<char, 64> number_buffer{{}};
-
- /// the locale
- const std::lconv* loc = nullptr;
- /// the locale's thousand separator character
- const char thousands_sep = '\0';
- /// the locale's decimal point character
- const char decimal_point = '\0';
-
- /// string buffer
- std::array<char, 512> string_buffer{{}};
-
- /// the indentation character
- const char indent_char;
- /// the indentation string
- string_t indent_string;
-};
-}
-}
-
-// #include <nlohmann/detail/json_ref.hpp>
-
-
-#include <initializer_list>
-#include <utility>
-
-namespace nlohmann
-{
-namespace detail
-{
-template<typename BasicJsonType>
-class json_ref
-{
- public:
- using value_type = BasicJsonType;
-
- json_ref(value_type&& value)
- : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true)
- {}
-
- json_ref(const value_type& value)
- : value_ref(const_cast<value_type*>(&value)), is_rvalue(false)
- {}
-
- json_ref(std::initializer_list<json_ref> init)
- : owned_value(init), value_ref(&owned_value), is_rvalue(true)
- {}
-
- template<class... Args>
- json_ref(Args&& ... args)
- : owned_value(std::forward<Args>(args)...), value_ref(&owned_value), is_rvalue(true)
- {}
-
- // class should be movable only
- json_ref(json_ref&&) = default;
- json_ref(const json_ref&) = delete;
- json_ref& operator=(const json_ref&) = delete;
-
- value_type moved_or_copied() const
- {
- if (is_rvalue)
- {
- return std::move(*value_ref);
- }
- return *value_ref;
- }
-
- value_type const& operator*() const
- {
- return *static_cast<value_type const*>(value_ref);
- }
-
- value_type const* operator->() const
- {
- return static_cast<value_type const*>(value_ref);
- }
-
- private:
- mutable value_type owned_value = nullptr;
- value_type* value_ref = nullptr;
- const bool is_rvalue;
-};
-}
-}
-
-// #include <nlohmann/detail/json_pointer.hpp>
-
-
-#include <cassert> // assert
-#include <numeric> // accumulate
-#include <string> // string
-#include <vector> // vector
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
-// #include <nlohmann/detail/exceptions.hpp>
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-template<typename BasicJsonType>
-class json_pointer
-{
- // allow basic_json to access private members
- NLOHMANN_BASIC_JSON_TPL_DECLARATION
- friend class basic_json;
-
- public:
- /*!
- @brief create JSON pointer
-
- Create a JSON pointer according to the syntax described in
- [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
-
- @param[in] s string representing the JSON pointer; if omitted, the empty
- string is assumed which references the whole JSON value
-
- @throw parse_error.107 if the given JSON pointer @a s is nonempty and does
- not begin with a slash (`/`); see example below
-
- @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is
- not followed by `0` (representing `~`) or `1` (representing `/`); see
- example below
-
- @liveexample{The example shows the construction several valid JSON pointers
- as well as the exceptional behavior.,json_pointer}
-
- @since version 2.0.0
- */
- explicit json_pointer(const std::string& s = "")
- : reference_tokens(split(s))
- {}
-
- /*!
- @brief return a string representation of the JSON pointer
-
- @invariant For each JSON pointer `ptr`, it holds:
- @code {.cpp}
- ptr == json_pointer(ptr.to_string());
- @endcode
-
- @return a string representation of the JSON pointer
-
- @liveexample{The example shows the result of `to_string`.,
- json_pointer__to_string}
-
- @since version 2.0.0
- */
- std::string to_string() const noexcept
- {
- return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
- std::string{},
- [](const std::string & a, const std::string & b)
- {
- return a + "/" + escape(b);
- });
- }
-
- /// @copydoc to_string()
- operator std::string() const
- {
- return to_string();
- }
-
- /*!
- @param[in] s reference token to be converted into an array index
-
- @return integer representation of @a s
-
- @throw out_of_range.404 if string @a s could not be converted to an integer
- */
- static int array_index(const std::string& s)
- {
- std::size_t processed_chars = 0;
- const int res = std::stoi(s, &processed_chars);
-
- // check if the string was completely read
- if (JSON_UNLIKELY(processed_chars != s.size()))
- {
- JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
- }
-
- return res;
- }
-
- private:
- /*!
- @brief remove and return last reference pointer
- @throw out_of_range.405 if JSON pointer has no parent
- */
- std::string pop_back()
- {
- if (JSON_UNLIKELY(is_root()))
- {
- JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
- }
-
- auto last = reference_tokens.back();
- reference_tokens.pop_back();
- return last;
- }
-
- /// return whether pointer points to the root document
- bool is_root() const
- {
- return reference_tokens.empty();
- }
-
- json_pointer top() const
- {
- if (JSON_UNLIKELY(is_root()))
- {
- JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
- }
-
- json_pointer result = *this;
- result.reference_tokens = {reference_tokens[0]};
- return result;
- }
-
- /*!
- @brief create and return a reference to the pointed to value
-
- @complexity Linear in the number of reference tokens.
-
- @throw parse_error.109 if array index is not a number
- @throw type_error.313 if value cannot be unflattened
- */
- BasicJsonType& get_and_create(BasicJsonType& j) const
- {
- using size_type = typename BasicJsonType::size_type;
- auto result = &j;
-
- // in case no reference tokens exist, return a reference to the JSON value
- // j which will be overwritten by a primitive value
- for (const auto& reference_token : reference_tokens)
- {
- switch (result->m_type)
- {
- case detail::value_t::null:
- {
- if (reference_token == "0")
- {
- // start a new array if reference token is 0
- result = &result->operator[](0);
- }
- else
- {
- // start a new object otherwise
- result = &result->operator[](reference_token);
- }
- break;
- }
-
- case detail::value_t::object:
- {
- // create an entry in the object
- result = &result->operator[](reference_token);
- break;
- }
-
- case detail::value_t::array:
- {
- // create an entry in the array
- JSON_TRY
- {
- result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
- }
- JSON_CATCH(std::invalid_argument&)
- {
- JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
- }
- break;
- }
-
- /*
- The following code is only reached if there exists a reference
- token _and_ the current value is primitive. In this case, we have
- an error situation, because primitive values may only occur as
- single value; that is, with an empty list of reference tokens.
- */
- default:
- JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
- }
- }
-
- return *result;
- }
-
- /*!
- @brief return a reference to the pointed to value
-
- @note This version does not throw if a value is not present, but tries to
- create nested values instead. For instance, calling this function
- with pointer `"/this/that"` on a null value is equivalent to calling
- `operator[]("this").operator[]("that")` on that value, effectively
- changing the null value to an object.
-
- @param[in] ptr a JSON value
-
- @return reference to the JSON value pointed to by the JSON pointer
-
- @complexity Linear in the length of the JSON pointer.
-
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.404 if the JSON pointer can not be resolved
- */
- BasicJsonType& get_unchecked(BasicJsonType* ptr) const
- {
- using size_type = typename BasicJsonType::size_type;
- for (const auto& reference_token : reference_tokens)
- {
- // convert null values to arrays or objects before continuing
- if (ptr->m_type == detail::value_t::null)
- {
- // check if reference token is a number
- const bool nums =
- std::all_of(reference_token.begin(), reference_token.end(),
- [](const char x)
- {
- return (x >= '0' and x <= '9');
- });
-
- // change value to array for numbers or "-" or to object otherwise
- *ptr = (nums or reference_token == "-")
- ? detail::value_t::array
- : detail::value_t::object;
- }
-
- switch (ptr->m_type)
- {
- case detail::value_t::object:
- {
- // use unchecked object access
- ptr = &ptr->operator[](reference_token);
- break;
- }
-
- case detail::value_t::array:
- {
- // error condition (cf. RFC 6901, Sect. 4)
- if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
- {
- JSON_THROW(detail::parse_error::create(106, 0,
- "array index '" + reference_token +
- "' must not begin with '0'"));
- }
-
- if (reference_token == "-")
- {
- // explicitly treat "-" as index beyond the end
- ptr = &ptr->operator[](ptr->m_value.array->size());
- }
- else
- {
- // convert array index to number; unchecked access
- JSON_TRY
- {
- ptr = &ptr->operator[](
- static_cast<size_type>(array_index(reference_token)));
- }
- JSON_CATCH(std::invalid_argument&)
- {
- JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
- }
- }
- break;
- }
-
- default:
- JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
- }
- }
-
- return *ptr;
- }
-
- /*!
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.402 if the array index '-' is used
- @throw out_of_range.404 if the JSON pointer can not be resolved
- */
- BasicJsonType& get_checked(BasicJsonType* ptr) const
- {
- using size_type = typename BasicJsonType::size_type;
- for (const auto& reference_token : reference_tokens)
- {
- switch (ptr->m_type)
- {
- case detail::value_t::object:
- {
- // note: at performs range check
- ptr = &ptr->at(reference_token);
- break;
- }
-
- case detail::value_t::array:
- {
- if (JSON_UNLIKELY(reference_token == "-"))
- {
- // "-" always fails the range check
- JSON_THROW(detail::out_of_range::create(402,
- "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
- ") is out of range"));
- }
-
- // error condition (cf. RFC 6901, Sect. 4)
- if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
- {
- JSON_THROW(detail::parse_error::create(106, 0,
- "array index '" + reference_token +
- "' must not begin with '0'"));
- }
-
- // note: at performs range check
- JSON_TRY
- {
- ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
- }
- JSON_CATCH(std::invalid_argument&)
- {
- JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
- }
- break;
- }
-
- default:
- JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
- }
- }
-
- return *ptr;
- }
-
- /*!
- @brief return a const reference to the pointed to value
-
- @param[in] ptr a JSON value
-
- @return const reference to the JSON value pointed to by the JSON
- pointer
-
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.402 if the array index '-' is used
- @throw out_of_range.404 if the JSON pointer can not be resolved
- */
- const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
- {
- using size_type = typename BasicJsonType::size_type;
- for (const auto& reference_token : reference_tokens)
- {
- switch (ptr->m_type)
- {
- case detail::value_t::object:
- {
- // use unchecked object access
- ptr = &ptr->operator[](reference_token);
- break;
- }
-
- case detail::value_t::array:
- {
- if (JSON_UNLIKELY(reference_token == "-"))
- {
- // "-" cannot be used for const access
- JSON_THROW(detail::out_of_range::create(402,
- "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
- ") is out of range"));
- }
-
- // error condition (cf. RFC 6901, Sect. 4)
- if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
- {
- JSON_THROW(detail::parse_error::create(106, 0,
- "array index '" + reference_token +
- "' must not begin with '0'"));
- }
-
- // use unchecked array access
- JSON_TRY
- {
- ptr = &ptr->operator[](
- static_cast<size_type>(array_index(reference_token)));
- }
- JSON_CATCH(std::invalid_argument&)
- {
- JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
- }
- break;
- }
-
- default:
- JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
- }
- }
-
- return *ptr;
- }
-
- /*!
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.402 if the array index '-' is used
- @throw out_of_range.404 if the JSON pointer can not be resolved
- */
- const BasicJsonType& get_checked(const BasicJsonType* ptr) const
- {
- using size_type = typename BasicJsonType::size_type;
- for (const auto& reference_token : reference_tokens)
- {
- switch (ptr->m_type)
- {
- case detail::value_t::object:
- {
- // note: at performs range check
- ptr = &ptr->at(reference_token);
- break;
- }
-
- case detail::value_t::array:
- {
- if (JSON_UNLIKELY(reference_token == "-"))
- {
- // "-" always fails the range check
- JSON_THROW(detail::out_of_range::create(402,
- "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
- ") is out of range"));
- }
-
- // error condition (cf. RFC 6901, Sect. 4)
- if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
- {
- JSON_THROW(detail::parse_error::create(106, 0,
- "array index '" + reference_token +
- "' must not begin with '0'"));
- }
-
- // note: at performs range check
- JSON_TRY
- {
- ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
- }
- JSON_CATCH(std::invalid_argument&)
- {
- JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
- }
- break;
- }
-
- default:
- JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
- }
- }
-
- return *ptr;
- }
-
- /*!
- @brief split the string input to reference tokens
-
- @note This function is only called by the json_pointer constructor.
- All exceptions below are documented there.
-
- @throw parse_error.107 if the pointer is not empty or begins with '/'
- @throw parse_error.108 if character '~' is not followed by '0' or '1'
- */
- static std::vector<std::string> split(const std::string& reference_string)
- {
- std::vector<std::string> result;
-
- // special case: empty reference string -> no reference tokens
- if (reference_string.empty())
- {
- return result;
- }
-
- // check if nonempty reference string begins with slash
- if (JSON_UNLIKELY(reference_string[0] != '/'))
- {
- JSON_THROW(detail::parse_error::create(107, 1,
- "JSON pointer must be empty or begin with '/' - was: '" +
- reference_string + "'"));
- }
-
- // extract the reference tokens:
- // - slash: position of the last read slash (or end of string)
- // - start: position after the previous slash
- for (
- // search for the first slash after the first character
- std::size_t slash = reference_string.find_first_of('/', 1),
- // set the beginning of the first reference token
- start = 1;
- // we can stop if start == string::npos+1 = 0
- start != 0;
- // set the beginning of the next reference token
- // (will eventually be 0 if slash == std::string::npos)
- start = slash + 1,
- // find next slash
- slash = reference_string.find_first_of('/', start))
- {
- // use the text between the beginning of the reference token
- // (start) and the last slash (slash).
- auto reference_token = reference_string.substr(start, slash - start);
-
- // check reference tokens are properly escaped
- for (std::size_t pos = reference_token.find_first_of('~');
- pos != std::string::npos;
- pos = reference_token.find_first_of('~', pos + 1))
- {
- assert(reference_token[pos] == '~');
-
- // ~ must be followed by 0 or 1
- if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
- (reference_token[pos + 1] != '0' and
- reference_token[pos + 1] != '1')))
- {
- JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
- }
- }
-
- // finally, store the reference token
- unescape(reference_token);
- result.push_back(reference_token);
- }
-
- return result;
- }
-
- /*!
- @brief replace all occurrences of a substring by another string
-
- @param[in,out] s the string to manipulate; changed so that all
- occurrences of @a f are replaced with @a t
- @param[in] f the substring to replace with @a t
- @param[in] t the string to replace @a f
-
- @pre The search string @a f must not be empty. **This precondition is
- enforced with an assertion.**
-
- @since version 2.0.0
- */
- static void replace_substring(std::string& s, const std::string& f,
- const std::string& t)
- {
- assert(not f.empty());
- for (auto pos = s.find(f); // find first occurrence of f
- pos != std::string::npos; // make sure f was found
- s.replace(pos, f.size(), t), // replace with t, and
- pos = s.find(f, pos + t.size())) // find next occurrence of f
- {}
- }
-
- /// escape "~"" to "~0" and "/" to "~1"
- static std::string escape(std::string s)
- {
- replace_substring(s, "~", "~0");
- replace_substring(s, "/", "~1");
- return s;
- }
-
- /// unescape "~1" to tilde and "~0" to slash (order is important!)
- static void unescape(std::string& s)
- {
- replace_substring(s, "~1", "/");
- replace_substring(s, "~0", "~");
- }
-
- /*!
- @param[in] reference_string the reference string to the current value
- @param[in] value the value to consider
- @param[in,out] result the result object to insert values to
-
- @note Empty objects or arrays are flattened to `null`.
- */
- static void flatten(const std::string& reference_string,
- const BasicJsonType& value,
- BasicJsonType& result)
- {
- switch (value.m_type)
- {
- case detail::value_t::array:
- {
- if (value.m_value.array->empty())
- {
- // flatten empty array as null
- result[reference_string] = nullptr;
- }
- else
- {
- // iterate array and use index as reference string
- for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
- {
- flatten(reference_string + "/" + std::to_string(i),
- value.m_value.array->operator[](i), result);
- }
- }
- break;
- }
-
- case detail::value_t::object:
- {
- if (value.m_value.object->empty())
- {
- // flatten empty object as null
- result[reference_string] = nullptr;
- }
- else
- {
- // iterate object and use keys as reference string
- for (const auto& element : *value.m_value.object)
- {
- flatten(reference_string + "/" + escape(element.first), element.second, result);
- }
- }
- break;
- }
-
- default:
- {
- // add primitive value with its reference string
- result[reference_string] = value;
- break;
- }
- }
- }
-
- /*!
- @param[in] value flattened JSON
-
- @return unflattened JSON
-
- @throw parse_error.109 if array index is not a number
- @throw type_error.314 if value is not an object
- @throw type_error.315 if object values are not primitive
- @throw type_error.313 if value cannot be unflattened
- */
- static BasicJsonType
- unflatten(const BasicJsonType& value)
- {
- if (JSON_UNLIKELY(not value.is_object()))
- {
- JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
- }
-
- BasicJsonType result;
-
- // iterate the JSON object values
- for (const auto& element : *value.m_value.object)
- {
- if (JSON_UNLIKELY(not element.second.is_primitive()))
- {
- JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
- }
-
- // assign value to reference pointed to by JSON pointer; Note that if
- // the JSON pointer is "" (i.e., points to the whole value), function
- // get_and_create returns a reference to result itself. An assignment
- // will then create a primitive value.
- json_pointer(element.first).get_and_create(result) = element.second;
- }
-
- return result;
- }
-
- friend bool operator==(json_pointer const& lhs,
- json_pointer const& rhs) noexcept
- {
- return (lhs.reference_tokens == rhs.reference_tokens);
- }
-
- friend bool operator!=(json_pointer const& lhs,
- json_pointer const& rhs) noexcept
- {
- return not (lhs == rhs);
- }
-
- /// the reference tokens
- std::vector<std::string> reference_tokens;
-};
-}
-
-// #include <nlohmann/adl_serializer.hpp>
-
-
-#include <utility>
-
-// #include <nlohmann/detail/conversions/from_json.hpp>
-
-// #include <nlohmann/detail/conversions/to_json.hpp>
-
-
-namespace nlohmann
-{
-template<typename, typename>
-struct adl_serializer
-{
- /*!
- @brief convert a JSON value to any value type
-
- This function is usually called by the `get()` function of the
- @ref basic_json class (either explicit or via conversion operators).
-
- @param[in] j JSON value to read from
- @param[in,out] val value to write to
- */
- template<typename BasicJsonType, typename ValueType>
- static void from_json(BasicJsonType&& j, ValueType& val) noexcept(
- noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
- {
- ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
- }
-
- /*!
- @brief convert any value type to a JSON value
-
- This function is usually called by the constructors of the @ref basic_json
- class.
-
- @param[in,out] j JSON value to write to
- @param[in] val value to read from
- */
- template<typename BasicJsonType, typename ValueType>
- static void to_json(BasicJsonType& j, ValueType&& val) noexcept(
- noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val))))
- {
- ::nlohmann::to_json(j, std::forward<ValueType>(val));
- }
-};
-}
-
-
-/*!
-@brief namespace for Niels Lohmann
-@see https://github.com/nlohmann
-@since version 1.0.0
-*/
-namespace nlohmann
-{
-
-/*!
-@brief a class to store JSON values
-
-@tparam ObjectType type for JSON objects (`std::map` by default; will be used
-in @ref object_t)
-@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used
-in @ref array_t)
-@tparam StringType type for JSON strings and object keys (`std::string` by
-default; will be used in @ref string_t)
-@tparam BooleanType type for JSON booleans (`bool` by default; will be used
-in @ref boolean_t)
-@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by
-default; will be used in @ref number_integer_t)
-@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c
-`uint64_t` by default; will be used in @ref number_unsigned_t)
-@tparam NumberFloatType type for JSON floating-point numbers (`double` by
-default; will be used in @ref number_float_t)
-@tparam AllocatorType type of the allocator to use (`std::allocator` by
-default)
-@tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
-and `from_json()` (@ref adl_serializer by default)
-
-@requirement The class satisfies the following concept requirements:
-- Basic
- - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible):
- JSON values can be default constructed. The result will be a JSON null
- value.
- - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible):
- A JSON value can be constructed from an rvalue argument.
- - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible):
- A JSON value can be copy-constructed from an lvalue expression.
- - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable):
- A JSON value van be assigned from an rvalue argument.
- - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable):
- A JSON value can be copy-assigned from an lvalue expression.
- - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible):
- JSON values can be destructed.
-- Layout
- - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType):
- JSON values have
- [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout):
- All non-static data members are private and standard layout types, the
- class has no virtual functions or (virtual) base classes.
-- Library-wide
- - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable):
- JSON values can be compared with `==`, see @ref
- operator==(const_reference,const_reference).
- - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable):
- JSON values can be compared with `<`, see @ref
- operator<(const_reference,const_reference).
- - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable):
- Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of
- other compatible types, using unqualified function call @ref swap().
- - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer):
- JSON values can be compared against `std::nullptr_t` objects which are used
- to model the `null` value.
-- Container
- - [Container](http://en.cppreference.com/w/cpp/concept/Container):
- JSON values can be used like STL containers and provide iterator access.
- - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer);
- JSON values can be used like STL containers and provide reverse iterator
- access.
-
-@invariant The member variables @a m_value and @a m_type have the following
-relationship:
-- If `m_type == value_t::object`, then `m_value.object != nullptr`.
-- If `m_type == value_t::array`, then `m_value.array != nullptr`.
-- If `m_type == value_t::string`, then `m_value.string != nullptr`.
-The invariants are checked by member function assert_invariant().
-
-@internal
-@note ObjectType trick from http://stackoverflow.com/a/9860911
-@endinternal
-
-@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange
-Format](http://rfc7159.net/rfc7159)
-
-@since version 1.0.0
-
-@nosubgrouping
-*/
-NLOHMANN_BASIC_JSON_TPL_DECLARATION
-class basic_json
-{
- private:
- template<detail::value_t> friend struct detail::external_constructor;
- friend ::nlohmann::json_pointer<basic_json>;
- friend ::nlohmann::detail::parser<basic_json>;
- friend ::nlohmann::detail::serializer<basic_json>;
- template<typename BasicJsonType>
- friend class ::nlohmann::detail::iter_impl;
- template<typename BasicJsonType, typename CharType>
- friend class ::nlohmann::detail::binary_writer;
- template<typename BasicJsonType>
- friend class ::nlohmann::detail::binary_reader;
-
- /// workaround type for MSVC
- using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
-
- // convenience aliases for types residing in namespace detail;
- using lexer = ::nlohmann::detail::lexer<basic_json>;
- using parser = ::nlohmann::detail::parser<basic_json>;
-
- using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;
- template<typename BasicJsonType>
- using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;
- template<typename BasicJsonType>
- using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;
- template<typename Iterator>
- using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;
- template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>;
-
- template<typename CharType>
- using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;
-
- using binary_reader = ::nlohmann::detail::binary_reader<basic_json>;
- template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;
-
- using serializer = ::nlohmann::detail::serializer<basic_json>;
-
- public:
- using value_t = detail::value_t;
- /// @copydoc nlohmann::json_pointer
- using json_pointer = ::nlohmann::json_pointer<basic_json>;
- template<typename T, typename SFINAE>
- using json_serializer = JSONSerializer<T, SFINAE>;
- /// helper type for initializer lists of basic_json values
- using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
-
- ////////////////
- // exceptions //
- ////////////////
-
- /// @name exceptions
- /// Classes to implement user-defined exceptions.
- /// @{
-
- /// @copydoc detail::exception
- using exception = detail::exception;
- /// @copydoc detail::parse_error
- using parse_error = detail::parse_error;
- /// @copydoc detail::invalid_iterator
- using invalid_iterator = detail::invalid_iterator;
- /// @copydoc detail::type_error
- using type_error = detail::type_error;
- /// @copydoc detail::out_of_range
- using out_of_range = detail::out_of_range;
- /// @copydoc detail::other_error
- using other_error = detail::other_error;
-
- /// @}
-
-
- /////////////////////
- // container types //
- /////////////////////
-
- /// @name container types
- /// The canonic container types to use @ref basic_json like any other STL
- /// container.
- /// @{
-
- /// the type of elements in a basic_json container
- using value_type = basic_json;
-
- /// the type of an element reference
- using reference = value_type&;
- /// the type of an element const reference
- using const_reference = const value_type&;
-
- /// a type to represent differences between iterators
- using difference_type = std::ptrdiff_t;
- /// a type to represent container sizes
- using size_type = std::size_t;
-
- /// the allocator type
- using allocator_type = AllocatorType<basic_json>;
-
- /// the type of an element pointer
- using pointer = typename std::allocator_traits<allocator_type>::pointer;
- /// the type of an element const pointer
- using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
-
- /// an iterator for a basic_json container
- using iterator = iter_impl<basic_json>;
- /// a const iterator for a basic_json container
- using const_iterator = iter_impl<const basic_json>;
- /// a reverse iterator for a basic_json container
- using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;
- /// a const reverse iterator for a basic_json container
- using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;
-
- /// @}
-
-
- /*!
- @brief returns the allocator associated with the container
- */
- static allocator_type get_allocator()
- {
- return allocator_type();
- }
-
- /*!
- @brief returns version information on the library
-
- This function returns a JSON object with information about the library,
- including the version number and information on the platform and compiler.
-
- @return JSON object holding version information
- key | description
- ----------- | ---------------
- `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version).
- `copyright` | The copyright line for the library as string.
- `name` | The name of the library as string.
- `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`.
- `url` | The URL of the project as string.
- `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string).
-
- @liveexample{The following code shows an example output of the `meta()`
- function.,meta}
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @complexity Constant.
-
- @since 2.1.0
- */
- static basic_json meta()
- {
- basic_json result;
-
- result["copyright"] = "(C) 2013-2017 Niels Lohmann";
- result["name"] = "JSON for Modern C++";
- result["url"] = "https://github.com/nlohmann/json";
- result["version"]["string"] =
- std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." +
- std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." +
- std::to_string(NLOHMANN_JSON_VERSION_PATCH);
- result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
- result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
- result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
-
-#ifdef _WIN32
- result["platform"] = "win32";
-#elif defined __linux__
- result["platform"] = "linux";
-#elif defined __APPLE__
- result["platform"] = "apple";
-#elif defined __unix__
- result["platform"] = "unix";
-#else
- result["platform"] = "unknown";
-#endif
-
-#if defined(__ICC) || defined(__INTEL_COMPILER)
- result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}};
-#elif defined(__clang__)
- result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
-#elif defined(__GNUC__) || defined(__GNUG__)
- result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}};
-#elif defined(__HP_cc) || defined(__HP_aCC)
- result["compiler"] = "hp"
-#elif defined(__IBMCPP__)
- result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
-#elif defined(_MSC_VER)
- result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
-#elif defined(__PGI)
- result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
-#elif defined(__SUNPRO_CC)
- result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
-#else
- result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
-#endif
-
-#ifdef __cplusplus
- result["compiler"]["c++"] = std::to_string(__cplusplus);
-#else
- result["compiler"]["c++"] = "unknown";
-#endif
- return result;
- }
-
-
- ///////////////////////////
- // JSON value data types //
- ///////////////////////////
-
- /// @name JSON value data types
- /// The data types to store a JSON value. These types are derived from
- /// the template arguments passed to class @ref basic_json.
- /// @{
-
-#if defined(JSON_HAS_CPP_14)
- // Use transparent comparator if possible, combined with perfect forwarding
- // on find() and count() calls prevents unnecessary string construction.
- using object_comparator_t = std::less<>;
-#else
- using object_comparator_t = std::less<StringType>;
-#endif
-
- /*!
- @brief a type for an object
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows:
- > An object is an unordered collection of zero or more name/value pairs,
- > where a name is a string and a value is a string, number, boolean, null,
- > object, or array.
-
- To store objects in C++, a type is defined by the template parameters
- described below.
-
- @tparam ObjectType the container to store objects (e.g., `std::map` or
- `std::unordered_map`)
- @tparam StringType the type of the keys or names (e.g., `std::string`).
- The comparison function `std::less<StringType>` is used to order elements
- inside the container.
- @tparam AllocatorType the allocator to use for objects (e.g.,
- `std::allocator`)
-
- #### Default type
-
- With the default values for @a ObjectType (`std::map`), @a StringType
- (`std::string`), and @a AllocatorType (`std::allocator`), the default
- value for @a object_t is:
-
- @code {.cpp}
- std::map<
- std::string, // key_type
- basic_json, // value_type
- std::less<std::string>, // key_compare
- std::allocator<std::pair<const std::string, basic_json>> // allocator_type
- >
- @endcode
-
- #### Behavior
-
- The choice of @a object_t influences the behavior of the JSON class. With
- the default type, objects have the following behavior:
-
- - When all names are unique, objects will be interoperable in the sense
- that all software implementations receiving that object will agree on
- the name-value mappings.
- - When the names within an object are not unique, it is unspecified which
- one of the values for a given key will be chosen. For instance,
- `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or
- `{"key": 2}`.
- - Internally, name/value pairs are stored in lexicographical order of the
- names. Objects will also be serialized (see @ref dump) in this order.
- For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored
- and serialized as `{"a": 2, "b": 1}`.
- - When comparing objects, the order of the name/value pairs is irrelevant.
- This makes objects interoperable in the sense that they will not be
- affected by these differences. For instance, `{"b": 1, "a": 2}` and
- `{"a": 2, "b": 1}` will be treated as equal.
-
- #### Limits
-
- [RFC 7159](http://rfc7159.net/rfc7159) specifies:
- > An implementation may set limits on the maximum depth of nesting.
-
- In this class, the object's limit of nesting is not explicitly constrained.
- However, a maximum depth of nesting may be introduced by the compiler or
- runtime environment. A theoretical limit can be queried by calling the
- @ref max_size function of a JSON object.
-
- #### Storage
-
- Objects are stored as pointers in a @ref basic_json type. That is, for any
- access to object values, a pointer of type `object_t*` must be
- dereferenced.
-
- @sa @ref array_t -- type for an array value
-
- @since version 1.0.0
-
- @note The order name/value pairs are added to the object is *not*
- preserved by the library. Therefore, iterating an object may return
- name/value pairs in a different order than they were originally stored. In
- fact, keys will be traversed in alphabetical order as `std::map` with
- `std::less` is used by default. Please note this behavior conforms to [RFC
- 7159](http://rfc7159.net/rfc7159), because any order implements the
- specified "unordered" nature of JSON objects.
- */
- using object_t = ObjectType<StringType,
- basic_json,
- object_comparator_t,
- AllocatorType<std::pair<const StringType,
- basic_json>>>;
-
- /*!
- @brief a type for an array
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows:
- > An array is an ordered sequence of zero or more values.
-
- To store objects in C++, a type is defined by the template parameters
- explained below.
-
- @tparam ArrayType container type to store arrays (e.g., `std::vector` or
- `std::list`)
- @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
-
- #### Default type
-
- With the default values for @a ArrayType (`std::vector`) and @a
- AllocatorType (`std::allocator`), the default value for @a array_t is:
-
- @code {.cpp}
- std::vector<
- basic_json, // value_type
- std::allocator<basic_json> // allocator_type
- >
- @endcode
-
- #### Limits
-
- [RFC 7159](http://rfc7159.net/rfc7159) specifies:
- > An implementation may set limits on the maximum depth of nesting.
-
- In this class, the array's limit of nesting is not explicitly constrained.
- However, a maximum depth of nesting may be introduced by the compiler or
- runtime environment. A theoretical limit can be queried by calling the
- @ref max_size function of a JSON array.
-
- #### Storage
-
- Arrays are stored as pointers in a @ref basic_json type. That is, for any
- access to array values, a pointer of type `array_t*` must be dereferenced.
-
- @sa @ref object_t -- type for an object value
-
- @since version 1.0.0
- */
- using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
-
- /*!
- @brief a type for a string
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows:
- > A string is a sequence of zero or more Unicode characters.
-
- To store objects in C++, a type is defined by the template parameter
- described below. Unicode values are split by the JSON class into
- byte-sized characters during deserialization.
-
- @tparam StringType the container to store strings (e.g., `std::string`).
- Note this container is used for keys/names in objects, see @ref object_t.
-
- #### Default type
-
- With the default values for @a StringType (`std::string`), the default
- value for @a string_t is:
-
- @code {.cpp}
- std::string
- @endcode
-
- #### Encoding
-
- Strings are stored in UTF-8 encoding. Therefore, functions like
- `std::string::size()` or `std::string::length()` return the number of
- bytes in the string rather than the number of characters or glyphs.
-
- #### String comparison
-
- [RFC 7159](http://rfc7159.net/rfc7159) states:
- > Software implementations are typically required to test names of object
- > members for equality. Implementations that transform the textual
- > representation into sequences of Unicode code units and then perform the
- > comparison numerically, code unit by code unit, are interoperable in the
- > sense that implementations will agree in all cases on equality or
- > inequality of two strings. For example, implementations that compare
- > strings with escaped characters unconverted may incorrectly find that
- > `"a\\b"` and `"a\u005Cb"` are not equal.
-
- This implementation is interoperable as it does compare strings code unit
- by code unit.
-
- #### Storage
-
- String values are stored as pointers in a @ref basic_json type. That is,
- for any access to string values, a pointer of type `string_t*` must be
- dereferenced.
-
- @since version 1.0.0
- */
- using string_t = StringType;
-
- /*!
- @brief a type for a boolean
-
- [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a
- type which differentiates the two literals `true` and `false`.
-
- To store objects in C++, a type is defined by the template parameter @a
- BooleanType which chooses the type to use.
-
- #### Default type
-
- With the default values for @a BooleanType (`bool`), the default value for
- @a boolean_t is:
-
- @code {.cpp}
- bool
- @endcode
-
- #### Storage
-
- Boolean values are stored directly inside a @ref basic_json type.
-
- @since version 1.0.0
- */
- using boolean_t = BooleanType;
-
- /*!
- @brief a type for a number (integer)
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
- > The representation of numbers is similar to that used in most
- > programming languages. A number is represented in base 10 using decimal
- > digits. It contains an integer component that may be prefixed with an
- > optional minus sign, which may be followed by a fraction part and/or an
- > exponent part. Leading zeros are not allowed. (...) Numeric values that
- > cannot be represented in the grammar below (such as Infinity and NaN)
- > are not permitted.
-
- This description includes both integer and floating-point numbers.
- However, C++ allows more precise storage if it is known whether the number
- is a signed integer, an unsigned integer or a floating-point number.
- Therefore, three different types, @ref number_integer_t, @ref
- number_unsigned_t and @ref number_float_t are used.
-
- To store integer numbers in C++, a type is defined by the template
- parameter @a NumberIntegerType which chooses the type to use.
-
- #### Default type
-
- With the default values for @a NumberIntegerType (`int64_t`), the default
- value for @a number_integer_t is:
-
- @code {.cpp}
- int64_t
- @endcode
-
- #### Default behavior
-
- - The restrictions about leading zeros is not enforced in C++. Instead,
- leading zeros in integer literals lead to an interpretation as octal
- number. Internally, the value will be stored as decimal number. For
- instance, the C++ integer literal `010` will be serialized to `8`.
- During deserialization, leading zeros yield an error.
- - Not-a-number (NaN) values will be serialized to `null`.
-
- #### Limits
-
- [RFC 7159](http://rfc7159.net/rfc7159) specifies:
- > An implementation may set limits on the range and precision of numbers.
-
- When the default type is used, the maximal integer number that can be
- stored is `9223372036854775807` (INT64_MAX) and the minimal integer number
- that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers
- that are out of range will yield over/underflow when used in a
- constructor. During deserialization, too large or small integer numbers
- will be automatically be stored as @ref number_unsigned_t or @ref
- number_float_t.
-
- [RFC 7159](http://rfc7159.net/rfc7159) further states:
- > Note that when such software is used, numbers that are integers and are
- > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
- > that implementations will agree exactly on their numeric values.
-
- As this range is a subrange of the exactly supported range [INT64_MIN,
- INT64_MAX], this class's integer type is interoperable.
-
- #### Storage
-
- Integer number values are stored directly inside a @ref basic_json type.
-
- @sa @ref number_float_t -- type for number values (floating-point)
-
- @sa @ref number_unsigned_t -- type for number values (unsigned integer)
-
- @since version 1.0.0
- */
- using number_integer_t = NumberIntegerType;
-
- /*!
- @brief a type for a number (unsigned)
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
- > The representation of numbers is similar to that used in most
- > programming languages. A number is represented in base 10 using decimal
- > digits. It contains an integer component that may be prefixed with an
- > optional minus sign, which may be followed by a fraction part and/or an
- > exponent part. Leading zeros are not allowed. (...) Numeric values that
- > cannot be represented in the grammar below (such as Infinity and NaN)
- > are not permitted.
-
- This description includes both integer and floating-point numbers.
- However, C++ allows more precise storage if it is known whether the number
- is a signed integer, an unsigned integer or a floating-point number.
- Therefore, three different types, @ref number_integer_t, @ref
- number_unsigned_t and @ref number_float_t are used.
-
- To store unsigned integer numbers in C++, a type is defined by the
- template parameter @a NumberUnsignedType which chooses the type to use.
-
- #### Default type
-
- With the default values for @a NumberUnsignedType (`uint64_t`), the
- default value for @a number_unsigned_t is:
-
- @code {.cpp}
- uint64_t
- @endcode
-
- #### Default behavior
-
- - The restrictions about leading zeros is not enforced in C++. Instead,
- leading zeros in integer literals lead to an interpretation as octal
- number. Internally, the value will be stored as decimal number. For
- instance, the C++ integer literal `010` will be serialized to `8`.
- During deserialization, leading zeros yield an error.
- - Not-a-number (NaN) values will be serialized to `null`.
-
- #### Limits
-
- [RFC 7159](http://rfc7159.net/rfc7159) specifies:
- > An implementation may set limits on the range and precision of numbers.
-
- When the default type is used, the maximal integer number that can be
- stored is `18446744073709551615` (UINT64_MAX) and the minimal integer
- number that can be stored is `0`. Integer numbers that are out of range
- will yield over/underflow when used in a constructor. During
- deserialization, too large or small integer numbers will be automatically
- be stored as @ref number_integer_t or @ref number_float_t.
-
- [RFC 7159](http://rfc7159.net/rfc7159) further states:
- > Note that when such software is used, numbers that are integers and are
- > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
- > that implementations will agree exactly on their numeric values.
-
- As this range is a subrange (when considered in conjunction with the
- number_integer_t type) of the exactly supported range [0, UINT64_MAX],
- this class's integer type is interoperable.
-
- #### Storage
-
- Integer number values are stored directly inside a @ref basic_json type.
-
- @sa @ref number_float_t -- type for number values (floating-point)
- @sa @ref number_integer_t -- type for number values (integer)
-
- @since version 2.0.0
- */
- using number_unsigned_t = NumberUnsignedType;
-
- /*!
- @brief a type for a number (floating-point)
-
- [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
- > The representation of numbers is similar to that used in most
- > programming languages. A number is represented in base 10 using decimal
- > digits. It contains an integer component that may be prefixed with an
- > optional minus sign, which may be followed by a fraction part and/or an
- > exponent part. Leading zeros are not allowed. (...) Numeric values that
- > cannot be represented in the grammar below (such as Infinity and NaN)
- > are not permitted.
-
- This description includes both integer and floating-point numbers.
- However, C++ allows more precise storage if it is known whether the number
- is a signed integer, an unsigned integer or a floating-point number.
- Therefore, three different types, @ref number_integer_t, @ref
- number_unsigned_t and @ref number_float_t are used.
-
- To store floating-point numbers in C++, a type is defined by the template
- parameter @a NumberFloatType which chooses the type to use.
-
- #### Default type
-
- With the default values for @a NumberFloatType (`double`), the default
- value for @a number_float_t is:
-
- @code {.cpp}
- double
- @endcode
-
- #### Default behavior
-
- - The restrictions about leading zeros is not enforced in C++. Instead,
- leading zeros in floating-point literals will be ignored. Internally,
- the value will be stored as decimal number. For instance, the C++
- floating-point literal `01.2` will be serialized to `1.2`. During
- deserialization, leading zeros yield an error.
- - Not-a-number (NaN) values will be serialized to `null`.
-
- #### Limits
-
- [RFC 7159](http://rfc7159.net/rfc7159) states:
- > This specification allows implementations to set limits on the range and
- > precision of numbers accepted. Since software that implements IEEE
- > 754-2008 binary64 (double precision) numbers is generally available and
- > widely used, good interoperability can be achieved by implementations
- > that expect no more precision or range than these provide, in the sense
- > that implementations will approximate JSON numbers within the expected
- > precision.
-
- This implementation does exactly follow this approach, as it uses double
- precision floating-point numbers. Note values smaller than
- `-1.79769313486232e+308` and values greater than `1.79769313486232e+308`
- will be stored as NaN internally and be serialized to `null`.
-
- #### Storage
-
- Floating-point number values are stored directly inside a @ref basic_json
- type.
-
- @sa @ref number_integer_t -- type for number values (integer)
-
- @sa @ref number_unsigned_t -- type for number values (unsigned integer)
-
- @since version 1.0.0
- */
- using number_float_t = NumberFloatType;
-
- /// @}
-
- private:
-
- /// helper for exception-safe object creation
- template<typename T, typename... Args>
- static T* create(Args&& ... args)
- {
- AllocatorType<T> alloc;
- using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;
-
- auto deleter = [&](T * object)
- {
- AllocatorTraits::deallocate(alloc, object, 1);
- };
- std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter);
- AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...);
- assert(object != nullptr);
- return object.release();
- }
-
- ////////////////////////
- // JSON value storage //
- ////////////////////////
-
- /*!
- @brief a JSON value
-
- The actual storage for a JSON value of the @ref basic_json class. This
- union combines the different storage types for the JSON value types
- defined in @ref value_t.
-
- JSON type | value_t type | used type
- --------- | --------------- | ------------------------
- object | object | pointer to @ref object_t
- array | array | pointer to @ref array_t
- string | string | pointer to @ref string_t
- boolean | boolean | @ref boolean_t
- number | number_integer | @ref number_integer_t
- number | number_unsigned | @ref number_unsigned_t
- number | number_float | @ref number_float_t
- null | null | *no value is stored*
-
- @note Variable-length types (objects, arrays, and strings) are stored as
- pointers. The size of the union should not exceed 64 bits if the default
- value types are used.
-
- @since version 1.0.0
- */
- union json_value
- {
- /// object (stored with pointer to save storage)
- object_t* object;
- /// array (stored with pointer to save storage)
- array_t* array;
- /// string (stored with pointer to save storage)
- string_t* string;
- /// boolean
- boolean_t boolean;
- /// number (integer)
- number_integer_t number_integer;
- /// number (unsigned integer)
- number_unsigned_t number_unsigned;
- /// number (floating-point)
- number_float_t number_float;
-
- /// default constructor (for null values)
- json_value() = default;
- /// constructor for booleans
- json_value(boolean_t v) noexcept : boolean(v) {}
- /// constructor for numbers (integer)
- json_value(number_integer_t v) noexcept : number_integer(v) {}
- /// constructor for numbers (unsigned)
- json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
- /// constructor for numbers (floating-point)
- json_value(number_float_t v) noexcept : number_float(v) {}
- /// constructor for empty values of a given type
- json_value(value_t t)
- {
- switch (t)
- {
- case value_t::object:
- {
- object = create<object_t>();
- break;
- }
-
- case value_t::array:
- {
- array = create<array_t>();
- break;
- }
-
- case value_t::string:
- {
- string = create<string_t>("");
- break;
- }
-
- case value_t::boolean:
- {
- boolean = boolean_t(false);
- break;
- }
-
- case value_t::number_integer:
- {
- number_integer = number_integer_t(0);
- break;
- }
-
- case value_t::number_unsigned:
- {
- number_unsigned = number_unsigned_t(0);
- break;
- }
-
- case value_t::number_float:
- {
- number_float = number_float_t(0.0);
- break;
- }
-
- case value_t::null:
- {
- object = nullptr; // silence warning, see #821
- break;
- }
-
- default:
- {
- object = nullptr; // silence warning, see #821
- if (JSON_UNLIKELY(t == value_t::null))
- {
- JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); // LCOV_EXCL_LINE
- }
- break;
- }
- }
- }
-
- /// constructor for strings
- json_value(const string_t& value)
- {
- string = create<string_t>(value);
- }
-
- /// constructor for rvalue strings
- json_value(string_t&& value)
- {
- string = create<string_t>(std::move(value));
- }
-
- /// constructor for objects
- json_value(const object_t& value)
- {
- object = create<object_t>(value);
- }
-
- /// constructor for rvalue objects
- json_value(object_t&& value)
- {
- object = create<object_t>(std::move(value));
- }
-
- /// constructor for arrays
- json_value(const array_t& value)
- {
- array = create<array_t>(value);
- }
-
- /// constructor for rvalue arrays
- json_value(array_t&& value)
- {
- array = create<array_t>(std::move(value));
- }
-
- void destroy(value_t t) noexcept
- {
- switch (t)
- {
- case value_t::object:
- {
- AllocatorType<object_t> alloc;
- std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
- std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
- break;
- }
-
- case value_t::array:
- {
- AllocatorType<array_t> alloc;
- std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
- std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
- break;
- }
-
- case value_t::string:
- {
- AllocatorType<string_t> alloc;
- std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
- std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
- break;
- }
-
- default:
- {
- break;
- }
- }
- }
- };
-
- /*!
- @brief checks the class invariants
-
- This function asserts the class invariants. It needs to be called at the
- end of every constructor to make sure that created objects respect the
- invariant. Furthermore, it has to be called each time the type of a JSON
- value is changed, because the invariant expresses a relationship between
- @a m_type and @a m_value.
- */
- void assert_invariant() const noexcept
- {
- assert(m_type != value_t::object or m_value.object != nullptr);
- assert(m_type != value_t::array or m_value.array != nullptr);
- assert(m_type != value_t::string or m_value.string != nullptr);
- }
-
- public:
- //////////////////////////
- // JSON parser callback //
- //////////////////////////
-
- /*!
- @brief parser event types
-
- The parser callback distinguishes the following events:
- - `object_start`: the parser read `{` and started to process a JSON object
- - `key`: the parser read a key of a value in an object
- - `object_end`: the parser read `}` and finished processing a JSON object
- - `array_start`: the parser read `[` and started to process a JSON array
- - `array_end`: the parser read `]` and finished processing a JSON array
- - `value`: the parser finished reading a JSON value
-
- @image html callback_events.png "Example when certain parse events are triggered"
-
- @sa @ref parser_callback_t for more information and examples
- */
- using parse_event_t = typename parser::parse_event_t;
-
- /*!
- @brief per-element parser callback type
-
- With a parser callback function, the result of parsing a JSON text can be
- influenced. When passed to @ref parse, it is called on certain events
- (passed as @ref parse_event_t via parameter @a event) with a set recursion
- depth @a depth and context JSON value @a parsed. The return value of the
- callback function is a boolean indicating whether the element that emitted
- the callback shall be kept or not.
-
- We distinguish six scenarios (determined by the event type) in which the
- callback function can be called. The following table describes the values
- of the parameters @a depth, @a event, and @a parsed.
-
- parameter @a event | description | parameter @a depth | parameter @a parsed
- ------------------ | ----------- | ------------------ | -------------------
- parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded
- parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key
- parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object
- parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded
- parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array
- parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value
-
- @image html callback_events.png "Example when certain parse events are triggered"
-
- Discarding a value (i.e., returning `false`) has different effects
- depending on the context in which function was called:
-
- - Discarded values in structured types are skipped. That is, the parser
- will behave as if the discarded value was never read.
- - In case a value outside a structured type is skipped, it is replaced
- with `null`. This case happens if the top-level element is skipped.
-
- @param[in] depth the depth of the recursion during parsing
-
- @param[in] event an event of type parse_event_t indicating the context in
- the callback function has been called
-
- @param[in,out] parsed the current intermediate parse result; note that
- writing to this value has no effect for parse_event_t::key events
-
- @return Whether the JSON value which called the function during parsing
- should be kept (`true`) or not (`false`). In the latter case, it is either
- skipped completely or replaced by an empty discarded object.
-
- @sa @ref parse for examples
-
- @since version 1.0.0
- */
- using parser_callback_t = typename parser::parser_callback_t;
-
-
- //////////////////
- // constructors //
- //////////////////
-
- /// @name constructors and destructors
- /// Constructors of class @ref basic_json, copy/move constructor, copy
- /// assignment, static functions creating objects, and the destructor.
- /// @{
-
- /*!
- @brief create an empty value with a given type
-
- Create an empty JSON value with a given type. The value will be default
- initialized with an empty value which depends on the type:
-
- Value type | initial value
- ----------- | -------------
- null | `null`
- boolean | `false`
- string | `""`
- number | `0`
- object | `{}`
- array | `[]`
-
- @param[in] v the type of the value to create
-
- @complexity Constant.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The following code shows the constructor for different @ref
- value_t values,basic_json__value_t}
-
- @sa @ref clear() -- restores the postcondition of this constructor
-
- @since version 1.0.0
- */
- basic_json(const value_t v)
- : m_type(v), m_value(v)
- {
- assert_invariant();
- }
-
- /*!
- @brief create a null object
-
- Create a `null` JSON value. It either takes a null pointer as parameter
- (explicitly creating `null`) or no parameter (implicitly creating `null`).
- The passed null pointer itself is not read -- it is only used to choose
- the right constructor.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this constructor never throws
- exceptions.
-
- @liveexample{The following code shows the constructor with and without a
- null pointer parameter.,basic_json__nullptr_t}
-
- @since version 1.0.0
- */
- basic_json(std::nullptr_t = nullptr) noexcept
- : basic_json(value_t::null)
- {
- assert_invariant();
- }
-
- /*!
- @brief create a JSON value
-
- This is a "catch all" constructor for all compatible JSON types; that is,
- types for which a `to_json()` method exists. The constructor forwards the
- parameter @a val to that method (to `json_serializer<U>::to_json` method
- with `U = uncvref_t<CompatibleType>`, to be exact).
-
- Template type @a CompatibleType includes, but is not limited to, the
- following types:
- - **arrays**: @ref array_t and all kinds of compatible containers such as
- `std::vector`, `std::deque`, `std::list`, `std::forward_list`,
- `std::array`, `std::valarray`, `std::set`, `std::unordered_set`,
- `std::multiset`, and `std::unordered_multiset` with a `value_type` from
- which a @ref basic_json value can be constructed.
- - **objects**: @ref object_t and all kinds of compatible associative
- containers such as `std::map`, `std::unordered_map`, `std::multimap`,
- and `std::unordered_multimap` with a `key_type` compatible to
- @ref string_t and a `value_type` from which a @ref basic_json value can
- be constructed.
- - **strings**: @ref string_t, string literals, and all compatible string
- containers can be used.
- - **numbers**: @ref number_integer_t, @ref number_unsigned_t,
- @ref number_float_t, and all convertible number types such as `int`,
- `size_t`, `int64_t`, `float` or `double` can be used.
- - **boolean**: @ref boolean_t / `bool` can be used.
-
- See the examples below.
-
- @tparam CompatibleType a type such that:
- - @a CompatibleType is not derived from `std::istream`,
- - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
- constructors),
- - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments)
- - @a CompatibleType is not a @ref basic_json nested type (e.g.,
- @ref json_pointer, @ref iterator, etc ...)
- - @ref @ref json_serializer<U> has a
- `to_json(basic_json_t&, CompatibleType&&)` method
-
- @tparam U = `uncvref_t<CompatibleType>`
-
- @param[in] val the value to be forwarded to the respective constructor
-
- @complexity Usually linear in the size of the passed @a val, also
- depending on the implementation of the called `to_json()`
- method.
-
- @exceptionsafety Depends on the called constructor. For types directly
- supported by the library (i.e., all types for which no `to_json()` function
- was provided), strong guarantee holds: if an exception is thrown, there are
- no changes to any JSON value.
-
- @liveexample{The following code shows the constructor with several
- compatible types.,basic_json__CompatibleType}
-
- @since version 2.1.0
- */
- template <typename CompatibleType,
- typename U = detail::uncvref_t<CompatibleType>,
- detail::enable_if_t<
- detail::is_compatible_type<basic_json_t, U>::value, int> = 0>
- basic_json(CompatibleType && val) noexcept(noexcept(
- JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
- std::forward<CompatibleType>(val))))
- {
- JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
- assert_invariant();
- }
-
- /*!
- @brief create a JSON value from an existing one
-
- This is a constructor for existing @ref basic_json types.
- It does not hijack copy/move constructors, since the parameter has different
- template arguments than the current ones.
-
- The constructor tries to convert the internal @ref m_value of the parameter.
-
- @tparam BasicJsonType a type such that:
- - @a BasicJsonType is a @ref basic_json type.
- - @a BasicJsonType has different template arguments than @ref basic_json_t.
-
- @param[in] val the @ref basic_json value to be converted.
-
- @complexity Usually linear in the size of the passed @a val, also
- depending on the implementation of the called `to_json()`
- method.
-
- @exceptionsafety Depends on the called constructor. For types directly
- supported by the library (i.e., all types for which no `to_json()` function
- was provided), strong guarantee holds: if an exception is thrown, there are
- no changes to any JSON value.
-
- @since version 3.1.2
- */
- template <typename BasicJsonType,
- detail::enable_if_t<
- detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0>
- basic_json(const BasicJsonType& val)
- {
- using other_boolean_t = typename BasicJsonType::boolean_t;
- using other_number_float_t = typename BasicJsonType::number_float_t;
- using other_number_integer_t = typename BasicJsonType::number_integer_t;
- using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
- using other_string_t = typename BasicJsonType::string_t;
- using other_object_t = typename BasicJsonType::object_t;
- using other_array_t = typename BasicJsonType::array_t;
-
- switch (val.type())
- {
- case value_t::boolean:
- JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
- break;
- case value_t::number_float:
- JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
- break;
- case value_t::number_integer:
- JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
- break;
- case value_t::number_unsigned:
- JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
- break;
- case value_t::string:
- JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
- break;
- case value_t::object:
- JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
- break;
- case value_t::array:
- JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
- break;
- case value_t::null:
- *this = nullptr;
- break;
- case value_t::discarded:
- m_type = value_t::discarded;
- break;
- }
- assert_invariant();
- }
-
- /*!
- @brief create a container (array or object) from an initializer list
-
- Creates a JSON value of type array or object from the passed initializer
- list @a init. In case @a type_deduction is `true` (default), the type of
- the JSON value to be created is deducted from the initializer list @a init
- according to the following rules:
-
- 1. If the list is empty, an empty JSON object value `{}` is created.
- 2. If the list consists of pairs whose first element is a string, a JSON
- object value is created where the first elements of the pairs are
- treated as keys and the second elements are as values.
- 3. In all other cases, an array is created.
-
- The rules aim to create the best fit between a C++ initializer list and
- JSON values. The rationale is as follows:
-
- 1. The empty initializer list is written as `{}` which is exactly an empty
- JSON object.
- 2. C++ has no way of describing mapped types other than to list a list of
- pairs. As JSON requires that keys must be of type string, rule 2 is the
- weakest constraint one can pose on initializer lists to interpret them
- as an object.
- 3. In all other cases, the initializer list could not be interpreted as
- JSON object type, so interpreting it as JSON array type is safe.
-
- With the rules described above, the following JSON values cannot be
- expressed by an initializer list:
-
- - the empty array (`[]`): use @ref array(initializer_list_t)
- with an empty initializer list in this case
- - arrays whose elements satisfy rule 2: use @ref
- array(initializer_list_t) with the same initializer list
- in this case
-
- @note When used without parentheses around an empty initializer list, @ref
- basic_json() is called instead of this function, yielding the JSON null
- value.
-
- @param[in] init initializer list with JSON values
-
- @param[in] type_deduction internal parameter; when set to `true`, the type
- of the JSON value is deducted from the initializer list @a init; when set
- to `false`, the type provided via @a manual_type is forced. This mode is
- used by the functions @ref array(initializer_list_t) and
- @ref object(initializer_list_t).
-
- @param[in] manual_type internal parameter; when @a type_deduction is set
- to `false`, the created JSON value will use the provided type (only @ref
- value_t::array and @ref value_t::object are valid); when @a type_deduction
- is set to `true`, this parameter has no effect
-
- @throw type_error.301 if @a type_deduction is `false`, @a manual_type is
- `value_t::object`, but @a init contains an element which is not a pair
- whose first element is a string. In this case, the constructor could not
- create an object. If @a type_deduction would have be `true`, an array
- would have been created. See @ref object(initializer_list_t)
- for an example.
-
- @complexity Linear in the size of the initializer list @a init.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The example below shows how JSON values are created from
- initializer lists.,basic_json__list_init_t}
-
- @sa @ref array(initializer_list_t) -- create a JSON array
- value from an initializer list
- @sa @ref object(initializer_list_t) -- create a JSON object
- value from an initializer list
-
- @since version 1.0.0
- */
- basic_json(initializer_list_t init,
- bool type_deduction = true,
- value_t manual_type = value_t::array)
- {
- // check if each element is an array with two elements whose first
- // element is a string
- bool is_an_object = std::all_of(init.begin(), init.end(),
- [](const detail::json_ref<basic_json>& element_ref)
- {
- return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string());
- });
-
- // adjust type if type deduction is not wanted
- if (not type_deduction)
- {
- // if array is wanted, do not create an object though possible
- if (manual_type == value_t::array)
- {
- is_an_object = false;
- }
-
- // if object is wanted but impossible, throw an exception
- if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object))
- {
- JSON_THROW(type_error::create(301, "cannot create object from initializer list"));
- }
- }
-
- if (is_an_object)
- {
- // the initializer list is a list of pairs -> create object
- m_type = value_t::object;
- m_value = value_t::object;
-
- std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json>& element_ref)
- {
- auto element = element_ref.moved_or_copied();
- m_value.object->emplace(
- std::move(*((*element.m_value.array)[0].m_value.string)),
- std::move((*element.m_value.array)[1]));
- });
- }
- else
- {
- // the initializer list describes an array -> create array
- m_type = value_t::array;
- m_value.array = create<array_t>(init.begin(), init.end());
- }
-
- assert_invariant();
- }
-
- /*!
- @brief explicitly create an array from an initializer list
-
- Creates a JSON array value from a given initializer list. That is, given a
- list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the
- initializer list is empty, the empty array `[]` is created.
-
- @note This function is only needed to express two edge cases that cannot
- be realized with the initializer list constructor (@ref
- basic_json(initializer_list_t, bool, value_t)). These cases
- are:
- 1. creating an array whose elements are all pairs whose first element is a
- string -- in this case, the initializer list constructor would create an
- object, taking the first elements as keys
- 2. creating an empty array -- passing the empty initializer list to the
- initializer list constructor yields an empty object
-
- @param[in] init initializer list with JSON values to create an array from
- (optional)
-
- @return JSON array value
-
- @complexity Linear in the size of @a init.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The following code shows an example for the `array`
- function.,array}
-
- @sa @ref basic_json(initializer_list_t, bool, value_t) --
- create a JSON value from an initializer list
- @sa @ref object(initializer_list_t) -- create a JSON object
- value from an initializer list
-
- @since version 1.0.0
- */
- static basic_json array(initializer_list_t init = {})
- {
- return basic_json(init, false, value_t::array);
- }
-
- /*!
- @brief explicitly create an object from an initializer list
-
- Creates a JSON object value from a given initializer list. The initializer
- lists elements must be pairs, and their first elements must be strings. If
- the initializer list is empty, the empty object `{}` is created.
-
- @note This function is only added for symmetry reasons. In contrast to the
- related function @ref array(initializer_list_t), there are
- no cases which can only be expressed by this function. That is, any
- initializer list @a init can also be passed to the initializer list
- constructor @ref basic_json(initializer_list_t, bool, value_t).
-
- @param[in] init initializer list to create an object from (optional)
-
- @return JSON object value
-
- @throw type_error.301 if @a init is not a list of pairs whose first
- elements are strings. In this case, no object can be created. When such a
- value is passed to @ref basic_json(initializer_list_t, bool, value_t),
- an array would have been created from the passed initializer list @a init.
- See example below.
-
- @complexity Linear in the size of @a init.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The following code shows an example for the `object`
- function.,object}
-
- @sa @ref basic_json(initializer_list_t, bool, value_t) --
- create a JSON value from an initializer list
- @sa @ref array(initializer_list_t) -- create a JSON array
- value from an initializer list
-
- @since version 1.0.0
- */
- static basic_json object(initializer_list_t init = {})
- {
- return basic_json(init, false, value_t::object);
- }
-
- /*!
- @brief construct an array with count copies of given value
-
- Constructs a JSON array value by creating @a cnt copies of a passed value.
- In case @a cnt is `0`, an empty array is created.
-
- @param[in] cnt the number of JSON copies of @a val to create
- @param[in] val the JSON value to copy
-
- @post `std::distance(begin(),end()) == cnt` holds.
-
- @complexity Linear in @a cnt.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The following code shows examples for the @ref
- basic_json(size_type\, const basic_json&)
- constructor.,basic_json__size_type_basic_json}
-
- @since version 1.0.0
- */
- basic_json(size_type cnt, const basic_json& val)
- : m_type(value_t::array)
- {
- m_value.array = create<array_t>(cnt, val);
- assert_invariant();
- }
-
- /*!
- @brief construct a JSON container given an iterator range
-
- Constructs the JSON value with the contents of the range `[first, last)`.
- The semantics depends on the different types a JSON value can have:
- - In case of a null type, invalid_iterator.206 is thrown.
- - In case of other primitive types (number, boolean, or string), @a first
- must be `begin()` and @a last must be `end()`. In this case, the value is
- copied. Otherwise, invalid_iterator.204 is thrown.
- - In case of structured types (array, object), the constructor behaves as
- similar versions for `std::vector` or `std::map`; that is, a JSON array
- or object is constructed from the values in the range.
-
- @tparam InputIT an input iterator type (@ref iterator or @ref
- const_iterator)
-
- @param[in] first begin of the range to copy from (included)
- @param[in] last end of the range to copy from (excluded)
-
- @pre Iterators @a first and @a last must be initialized. **This
- precondition is enforced with an assertion (see warning).** If
- assertions are switched off, a violation of this precondition yields
- undefined behavior.
-
- @pre Range `[first, last)` is valid. Usually, this precondition cannot be
- checked efficiently. Only certain edge cases are detected; see the
- description of the exceptions below. A violation of this precondition
- yields undefined behavior.
-
- @warning A precondition is enforced with a runtime assertion that will
- result in calling `std::abort` if this precondition is not met.
- Assertions can be disabled by defining `NDEBUG` at compile time.
- See http://en.cppreference.com/w/cpp/error/assert for more
- information.
-
- @throw invalid_iterator.201 if iterators @a first and @a last are not
- compatible (i.e., do not belong to the same JSON value). In this case,
- the range `[first, last)` is undefined.
- @throw invalid_iterator.204 if iterators @a first and @a last belong to a
- primitive type (number, boolean, or string), but @a first does not point
- to the first element any more. In this case, the range `[first, last)` is
- undefined. See example code below.
- @throw invalid_iterator.206 if iterators @a first and @a last belong to a
- null value. In this case, the range `[first, last)` is undefined.
-
- @complexity Linear in distance between @a first and @a last.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @liveexample{The example below shows several ways to create JSON values by
- specifying a subrange with iterators.,basic_json__InputIt_InputIt}
-
- @since version 1.0.0
- */
- template<class InputIT, typename std::enable_if<
- std::is_same<InputIT, typename basic_json_t::iterator>::value or
- std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0>
- basic_json(InputIT first, InputIT last)
- {
- assert(first.m_object != nullptr);
- assert(last.m_object != nullptr);
-
- // make sure iterator fits the current value
- if (JSON_UNLIKELY(first.m_object != last.m_object))
- {
- JSON_THROW(invalid_iterator::create(201, "iterators are not compatible"));
- }
-
- // copy type from first iterator
- m_type = first.m_object->m_type;
-
- // check if iterator range is complete for primitive values
- switch (m_type)
- {
- case value_t::boolean:
- case value_t::number_float:
- case value_t::number_integer:
- case value_t::number_unsigned:
- case value_t::string:
- {
- if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin()
- or not last.m_it.primitive_iterator.is_end()))
- {
- JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
- }
- break;
- }
-
- default:
- break;
- }
-
- switch (m_type)
- {
- case value_t::number_integer:
- {
- m_value.number_integer = first.m_object->m_value.number_integer;
- break;
- }
-
- case value_t::number_unsigned:
- {
- m_value.number_unsigned = first.m_object->m_value.number_unsigned;
- break;
- }
-
- case value_t::number_float:
- {
- m_value.number_float = first.m_object->m_value.number_float;
- break;
- }
-
- case value_t::boolean:
- {
- m_value.boolean = first.m_object->m_value.boolean;
- break;
- }
-
- case value_t::string:
- {
- m_value = *first.m_object->m_value.string;
- break;
- }
-
- case value_t::object:
- {
- m_value.object = create<object_t>(first.m_it.object_iterator,
- last.m_it.object_iterator);
- break;
- }
-
- case value_t::array:
- {
- m_value.array = create<array_t>(first.m_it.array_iterator,
- last.m_it.array_iterator);
- break;
- }
-
- default:
- JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " +
- std::string(first.m_object->type_name())));
- }
-
- assert_invariant();
- }
-
-
- ///////////////////////////////////////
- // other constructors and destructor //
- ///////////////////////////////////////
-
- /// @private
- basic_json(const detail::json_ref<basic_json>& ref)
- : basic_json(ref.moved_or_copied())
- {}
-
- /*!
- @brief copy constructor
-
- Creates a copy of a given JSON value.
-
- @param[in] other the JSON value to copy
-
- @post `*this == other`
-
- @complexity Linear in the size of @a other.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes to any JSON value.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is linear.
- - As postcondition, it holds: `other == basic_json(other)`.
-
- @liveexample{The following code shows an example for the copy
- constructor.,basic_json__basic_json}
-
- @since version 1.0.0
- */
- basic_json(const basic_json& other)
- : m_type(other.m_type)
- {
- // check of passed value is valid
- other.assert_invariant();
-
- switch (m_type)
- {
- case value_t::object:
- {
- m_value = *other.m_value.object;
- break;
- }
-
- case value_t::array:
- {
- m_value = *other.m_value.array;
- break;
- }
-
- case value_t::string:
- {
- m_value = *other.m_value.string;
- break;
- }
-
- case value_t::boolean:
- {
- m_value = other.m_value.boolean;
- break;
- }
-
- case value_t::number_integer:
- {
- m_value = other.m_value.number_integer;
- break;
- }
-
- case value_t::number_unsigned:
- {
- m_value = other.m_value.number_unsigned;
- break;
- }
-
- case value_t::number_float:
- {
- m_value = other.m_value.number_float;
- break;
- }
-
- default:
- break;
- }
-
- assert_invariant();
- }
-
- /*!
- @brief move constructor
-
- Move constructor. Constructs a JSON value with the contents of the given
- value @a other using move semantics. It "steals" the resources from @a
- other and leaves it as JSON null value.
-
- @param[in,out] other value to move to this object
-
- @post `*this` has the same value as @a other before the call.
- @post @a other is a JSON null value.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this constructor never throws
- exceptions.
-
- @requirement This function helps `basic_json` satisfying the
- [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible)
- requirements.
-
- @liveexample{The code below shows the move constructor explicitly called
- via std::move.,basic_json__moveconstructor}
-
- @since version 1.0.0
- */
- basic_json(basic_json&& other) noexcept
- : m_type(std::move(other.m_type)),
- m_value(std::move(other.m_value))
- {
- // check that passed value is valid
- other.assert_invariant();
-
- // invalidate payload
- other.m_type = value_t::null;
- other.m_value = {};
-
- assert_invariant();
- }
-
- /*!
- @brief copy assignment
-
- Copy assignment operator. Copies a JSON value via the "copy and swap"
- strategy: It is expressed in terms of the copy constructor, destructor,
- and the `swap()` member function.
-
- @param[in] other value to copy from
-
- @complexity Linear.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is linear.
-
- @liveexample{The code below shows and example for the copy assignment. It
- creates a copy of value `a` which is then swapped with `b`. Finally\, the
- copy of `a` (which is the null value after the swap) is
- destroyed.,basic_json__copyassignment}
-
- @since version 1.0.0
- */
- reference& operator=(basic_json other) noexcept (
- std::is_nothrow_move_constructible<value_t>::value and
- std::is_nothrow_move_assignable<value_t>::value and
- std::is_nothrow_move_constructible<json_value>::value and
- std::is_nothrow_move_assignable<json_value>::value
- )
- {
- // check that passed value is valid
- other.assert_invariant();
-
- using std::swap;
- swap(m_type, other.m_type);
- swap(m_value, other.m_value);
-
- assert_invariant();
- return *this;
- }
-
- /*!
- @brief destructor
-
- Destroys the JSON value and frees all allocated memory.
-
- @complexity Linear.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is linear.
- - All stored elements are destroyed and all memory is freed.
-
- @since version 1.0.0
- */
- ~basic_json() noexcept
- {
- assert_invariant();
- m_value.destroy(m_type);
- }
-
- /// @}
-
- public:
- ///////////////////////
- // object inspection //
- ///////////////////////
-
- /// @name object inspection
- /// Functions to inspect the type of a JSON value.
- /// @{
-
- /*!
- @brief serialization
-
- Serialization function for JSON values. The function tries to mimic
- Python's `json.dumps()` function, and currently supports its @a indent
- and @a ensure_ascii parameters.
-
- @param[in] indent If indent is nonnegative, then array elements and object
- members will be pretty-printed with that indent level. An indent level of
- `0` will only insert newlines. `-1` (the default) selects the most compact
- representation.
- @param[in] indent_char The character to use for indentation if @a indent is
- greater than `0`. The default is ` ` (space).
- @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters
- in the output are escaped with `\uXXXX` sequences, and the result consists
- of ASCII characters only.
-
- @return string containing the serialization of the JSON value
-
- @throw type_error.316 if a string stored inside the JSON value is not
- UTF-8 encoded
-
- @complexity Linear.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @liveexample{The following example shows the effect of different @a indent\,
- @a indent_char\, and @a ensure_ascii parameters to the result of the
- serialization.,dump}
-
- @see https://docs.python.org/2/library/json.html#json.dump
-
- @since version 1.0.0; indentation character @a indent_char, option
- @a ensure_ascii and exceptions added in version 3.0.0
- */
- string_t dump(const int indent = -1, const char indent_char = ' ',
- const bool ensure_ascii = false) const
- {
- string_t result;
- serializer s(detail::output_adapter<char, string_t>(result), indent_char);
-
- if (indent >= 0)
- {
- s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
- }
- else
- {
- s.dump(*this, false, ensure_ascii, 0);
- }
-
- return result;
- }
-
- /*!
- @brief return the type of the JSON value (explicit)
-
- Return the type of the JSON value as a value from the @ref value_t
- enumeration.
-
- @return the type of the JSON value
- Value type | return value
- ------------------------- | -------------------------
- null | value_t::null
- boolean | value_t::boolean
- string | value_t::string
- number (integer) | value_t::number_integer
- number (unsigned integer) | value_t::number_unsigned
- number (floating-point) | value_t::number_float
- object | value_t::object
- array | value_t::array
- discarded | value_t::discarded
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `type()` for all JSON
- types.,type}
-
- @sa @ref operator value_t() -- return the type of the JSON value (implicit)
- @sa @ref type_name() -- return the type as string
-
- @since version 1.0.0
- */
- constexpr value_t type() const noexcept
- {
- return m_type;
- }
-
- /*!
- @brief return whether type is primitive
-
- This function returns true if and only if the JSON type is primitive
- (string, number, boolean, or null).
-
- @return `true` if type is primitive (string, number, boolean, or null),
- `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_primitive()` for all JSON
- types.,is_primitive}
-
- @sa @ref is_structured() -- returns whether JSON value is structured
- @sa @ref is_null() -- returns whether JSON value is `null`
- @sa @ref is_string() -- returns whether JSON value is a string
- @sa @ref is_boolean() -- returns whether JSON value is a boolean
- @sa @ref is_number() -- returns whether JSON value is a number
-
- @since version 1.0.0
- */
- constexpr bool is_primitive() const noexcept
- {
- return is_null() or is_string() or is_boolean() or is_number();
- }
-
- /*!
- @brief return whether type is structured
-
- This function returns true if and only if the JSON type is structured
- (array or object).
-
- @return `true` if type is structured (array or object), `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_structured()` for all JSON
- types.,is_structured}
-
- @sa @ref is_primitive() -- returns whether value is primitive
- @sa @ref is_array() -- returns whether value is an array
- @sa @ref is_object() -- returns whether value is an object
-
- @since version 1.0.0
- */
- constexpr bool is_structured() const noexcept
- {
- return is_array() or is_object();
- }
-
- /*!
- @brief return whether value is null
-
- This function returns true if and only if the JSON value is null.
-
- @return `true` if type is null, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_null()` for all JSON
- types.,is_null}
-
- @since version 1.0.0
- */
- constexpr bool is_null() const noexcept
- {
- return (m_type == value_t::null);
- }
-
- /*!
- @brief return whether value is a boolean
-
- This function returns true if and only if the JSON value is a boolean.
-
- @return `true` if type is boolean, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_boolean()` for all JSON
- types.,is_boolean}
-
- @since version 1.0.0
- */
- constexpr bool is_boolean() const noexcept
- {
- return (m_type == value_t::boolean);
- }
-
- /*!
- @brief return whether value is a number
-
- This function returns true if and only if the JSON value is a number. This
- includes both integer (signed and unsigned) and floating-point values.
-
- @return `true` if type is number (regardless whether integer, unsigned
- integer or floating-type), `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_number()` for all JSON
- types.,is_number}
-
- @sa @ref is_number_integer() -- check if value is an integer or unsigned
- integer number
- @sa @ref is_number_unsigned() -- check if value is an unsigned integer
- number
- @sa @ref is_number_float() -- check if value is a floating-point number
-
- @since version 1.0.0
- */
- constexpr bool is_number() const noexcept
- {
- return is_number_integer() or is_number_float();
- }
-
- /*!
- @brief return whether value is an integer number
-
- This function returns true if and only if the JSON value is a signed or
- unsigned integer number. This excludes floating-point values.
-
- @return `true` if type is an integer or unsigned integer number, `false`
- otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_number_integer()` for all
- JSON types.,is_number_integer}
-
- @sa @ref is_number() -- check if value is a number
- @sa @ref is_number_unsigned() -- check if value is an unsigned integer
- number
- @sa @ref is_number_float() -- check if value is a floating-point number
-
- @since version 1.0.0
- */
- constexpr bool is_number_integer() const noexcept
- {
- return (m_type == value_t::number_integer or m_type == value_t::number_unsigned);
- }
-
- /*!
- @brief return whether value is an unsigned integer number
-
- This function returns true if and only if the JSON value is an unsigned
- integer number. This excludes floating-point and signed integer values.
-
- @return `true` if type is an unsigned integer number, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_number_unsigned()` for all
- JSON types.,is_number_unsigned}
-
- @sa @ref is_number() -- check if value is a number
- @sa @ref is_number_integer() -- check if value is an integer or unsigned
- integer number
- @sa @ref is_number_float() -- check if value is a floating-point number
-
- @since version 2.0.0
- */
- constexpr bool is_number_unsigned() const noexcept
- {
- return (m_type == value_t::number_unsigned);
- }
-
- /*!
- @brief return whether value is a floating-point number
-
- This function returns true if and only if the JSON value is a
- floating-point number. This excludes signed and unsigned integer values.
-
- @return `true` if type is a floating-point number, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_number_float()` for all
- JSON types.,is_number_float}
-
- @sa @ref is_number() -- check if value is number
- @sa @ref is_number_integer() -- check if value is an integer number
- @sa @ref is_number_unsigned() -- check if value is an unsigned integer
- number
-
- @since version 1.0.0
- */
- constexpr bool is_number_float() const noexcept
- {
- return (m_type == value_t::number_float);
- }
-
- /*!
- @brief return whether value is an object
-
- This function returns true if and only if the JSON value is an object.
-
- @return `true` if type is object, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_object()` for all JSON
- types.,is_object}
-
- @since version 1.0.0
- */
- constexpr bool is_object() const noexcept
- {
- return (m_type == value_t::object);
- }
-
- /*!
- @brief return whether value is an array
-
- This function returns true if and only if the JSON value is an array.
-
- @return `true` if type is array, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_array()` for all JSON
- types.,is_array}
-
- @since version 1.0.0
- */
- constexpr bool is_array() const noexcept
- {
- return (m_type == value_t::array);
- }
-
- /*!
- @brief return whether value is a string
-
- This function returns true if and only if the JSON value is a string.
-
- @return `true` if type is string, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_string()` for all JSON
- types.,is_string}
-
- @since version 1.0.0
- */
- constexpr bool is_string() const noexcept
- {
- return (m_type == value_t::string);
- }
-
- /*!
- @brief return whether value is discarded
-
- This function returns true if and only if the JSON value was discarded
- during parsing with a callback function (see @ref parser_callback_t).
-
- @note This function will always be `false` for JSON values after parsing.
- That is, discarded values can only occur during parsing, but will be
- removed when inside a structured value or replaced by null in other cases.
-
- @return `true` if type is discarded, `false` otherwise.
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies `is_discarded()` for all JSON
- types.,is_discarded}
-
- @since version 1.0.0
- */
- constexpr bool is_discarded() const noexcept
- {
- return (m_type == value_t::discarded);
- }
-
- /*!
- @brief return the type of the JSON value (implicit)
-
- Implicitly return the type of the JSON value as a value from the @ref
- value_t enumeration.
-
- @return the type of the JSON value
-
- @complexity Constant.
-
- @exceptionsafety No-throw guarantee: this member function never throws
- exceptions.
-
- @liveexample{The following code exemplifies the @ref value_t operator for
- all JSON types.,operator__value_t}
-
- @sa @ref type() -- return the type of the JSON value (explicit)
- @sa @ref type_name() -- return the type as string
-
- @since version 1.0.0
- */
- constexpr operator value_t() const noexcept
- {
- return m_type;
- }
-
- /// @}
-
- private:
- //////////////////
- // value access //
- //////////////////
-
- /// get a boolean (explicit)
- boolean_t get_impl(boolean_t* /*unused*/) const
- {
- if (JSON_LIKELY(is_boolean()))
- {
- return m_value.boolean;
- }
-
- JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name())));
- }
-
- /// get a pointer to the value (object)
- object_t* get_impl_ptr(object_t* /*unused*/) noexcept
- {
- return is_object() ? m_value.object : nullptr;
- }
-
- /// get a pointer to the value (object)
- constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept
- {
- return is_object() ? m_value.object : nullptr;
- }
-
- /// get a pointer to the value (array)
- array_t* get_impl_ptr(array_t* /*unused*/) noexcept
- {
- return is_array() ? m_value.array : nullptr;
- }
-
- /// get a pointer to the value (array)
- constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept
- {
- return is_array() ? m_value.array : nullptr;
- }
-
- /// get a pointer to the value (string)
- string_t* get_impl_ptr(string_t* /*unused*/) noexcept
- {
- return is_string() ? m_value.string : nullptr;
- }
-
- /// get a pointer to the value (string)
- constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept
- {
- return is_string() ? m_value.string : nullptr;
- }
-
- /// get a pointer to the value (boolean)
- boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept
- {
- return is_boolean() ? &m_value.boolean : nullptr;
- }
-
- /// get a pointer to the value (boolean)
- constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept
- {
- return is_boolean() ? &m_value.boolean : nullptr;
- }
-
- /// get a pointer to the value (integer number)
- number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept
- {
- return is_number_integer() ? &m_value.number_integer : nullptr;
- }
-
- /// get a pointer to the value (integer number)
- constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept
- {
- return is_number_integer() ? &m_value.number_integer : nullptr;
- }
-
- /// get a pointer to the value (unsigned number)
- number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
- {
- return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
- }
-
- /// get a pointer to the value (unsigned number)
- constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
- {
- return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
- }
-
- /// get a pointer to the value (floating-point number)
- number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept
- {
- return is_number_float() ? &m_value.number_float : nullptr;
- }
-
- /// get a pointer to the value (floating-point number)
- constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept
- {
- return is_number_float() ? &m_value.number_float : nullptr;
- }
-
- /*!
- @brief helper function to implement get_ref()
-
- This function helps to implement get_ref() without code duplication for
- const and non-const overloads
-
- @tparam ThisType will be deduced as `basic_json` or `const basic_json`
-
- @throw type_error.303 if ReferenceType does not match underlying value
- type of the current JSON
- */
- template<typename ReferenceType, typename ThisType>
- static ReferenceType get_ref_impl(ThisType& obj)
- {
- // delegate the call to get_ptr<>()
- auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>();
-
- if (JSON_LIKELY(ptr != nullptr))
- {
- return *ptr;
- }
-
- JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name())));
- }
-
- public:
- /// @name value access
- /// Direct access to the stored value of a JSON value.
- /// @{
-
- /*!
- @brief get special-case overload
-
- This overloads avoids a lot of template boilerplate, it can be seen as the
- identity method
-
- @tparam BasicJsonType == @ref basic_json
-
- @return a copy of *this
-
- @complexity Constant.
-
- @since version 2.1.0
- */
- template<typename BasicJsonType, detail::enable_if_t<
- std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value,
- int> = 0>
- basic_json get() const
- {
- return *this;
- }
-
- /*!
- @brief get special-case overload
-
- This overloads converts the current @ref basic_json in a different
- @ref basic_json type
-
- @tparam BasicJsonType == @ref basic_json
-
- @return a copy of *this, converted into @tparam BasicJsonType
-
- @complexity Depending on the implementation of the called `from_json()`
- method.
-
- @since version 3.1.2
- */
- template<typename BasicJsonType, detail::enable_if_t<
- not std::is_same<BasicJsonType, basic_json>::value and
- detail::is_basic_json<BasicJsonType>::value, int> = 0>
- BasicJsonType get() const
- {
- return *this;
- }
-
- /*!
- @brief get a value (explicit)
-
- Explicit type conversion between the JSON value and a compatible value
- which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
- and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
- The value is converted by calling the @ref json_serializer<ValueType>
- `from_json()` method.
-
- The function is equivalent to executing
- @code {.cpp}
- ValueType ret;
- JSONSerializer<ValueType>::from_json(*this, ret);
- return ret;
- @endcode
-
- This overloads is chosen if:
- - @a ValueType is not @ref basic_json,
- - @ref json_serializer<ValueType> has a `from_json()` method of the form
- `void from_json(const basic_json&, ValueType&)`, and
- - @ref json_serializer<ValueType> does not have a `from_json()` method of
- the form `ValueType from_json(const basic_json&)`
-
- @tparam ValueTypeCV the provided value type
- @tparam ValueType the returned value type
-
- @return copy of the JSON value, converted to @a ValueType
-
- @throw what @ref json_serializer<ValueType> `from_json()` method throws
-
- @liveexample{The example below shows several conversions from JSON values
- to other types. There a few things to note: (1) Floating-point numbers can
- be converted to integers\, (2) A JSON array can be converted to a standard
- `std::vector<short>`\, (3) A JSON object can be converted to C++
- associative containers such as `std::unordered_map<std::string\,
- json>`.,get__ValueType_const}
-
- @since version 2.1.0
- */
- template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
- detail::enable_if_t <
- not detail::is_basic_json<ValueType>::value and
- detail::has_from_json<basic_json_t, ValueType>::value and
- not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
- int> = 0>
- ValueType get() const noexcept(noexcept(
- JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
- {
- // we cannot static_assert on ValueTypeCV being non-const, because
- // there is support for get<const basic_json_t>(), which is why we
- // still need the uncvref
- static_assert(not std::is_reference<ValueTypeCV>::value,
- "get() cannot be used with reference types, you might want to use get_ref()");
- static_assert(std::is_default_constructible<ValueType>::value,
- "types must be DefaultConstructible when used with get()");
-
- ValueType ret;
- JSONSerializer<ValueType>::from_json(*this, ret);
- return ret;
- }
-
- /*!
- @brief get a value (explicit); special case
-
- Explicit type conversion between the JSON value and a compatible value
- which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
- and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
- The value is converted by calling the @ref json_serializer<ValueType>
- `from_json()` method.
-
- The function is equivalent to executing
- @code {.cpp}
- return JSONSerializer<ValueTypeCV>::from_json(*this);
- @endcode
-
- This overloads is chosen if:
- - @a ValueType is not @ref basic_json and
- - @ref json_serializer<ValueType> has a `from_json()` method of the form
- `ValueType from_json(const basic_json&)`
-
- @note If @ref json_serializer<ValueType> has both overloads of
- `from_json()`, this one is chosen.
-
- @tparam ValueTypeCV the provided value type
- @tparam ValueType the returned value type
-
- @return copy of the JSON value, converted to @a ValueType
-
- @throw what @ref json_serializer<ValueType> `from_json()` method throws
-
- @since version 2.1.0
- */
- template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
- detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and
- detail::has_non_default_from_json<basic_json_t, ValueType>::value,
- int> = 0>
- ValueType get() const noexcept(noexcept(
- JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
- {
- static_assert(not std::is_reference<ValueTypeCV>::value,
- "get() cannot be used with reference types, you might want to use get_ref()");
- return JSONSerializer<ValueTypeCV>::from_json(*this);
- }
-
- /*!
- @brief get a pointer value (explicit)
-
- Explicit pointer access to the internally stored JSON value. No copies are
- made.
-
- @warning The pointer becomes invalid if the underlying JSON object
- changes.
-
- @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
- object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
- @ref number_unsigned_t, or @ref number_float_t.
-
- @return pointer to the internally stored JSON value if the requested
- pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
-
- @complexity Constant.
-
- @liveexample{The example below shows how pointers to internal values of a
- JSON value can be requested. Note that no type conversions are made and a
- `nullptr` is returned if the value and the requested pointer type does not
- match.,get__PointerType}
-
- @sa @ref get_ptr() for explicit pointer-member access
-
- @since version 1.0.0
- */
- template<typename PointerType, typename std::enable_if<
- std::is_pointer<PointerType>::value, int>::type = 0>
- PointerType get() noexcept
- {
- // delegate the call to get_ptr
- return get_ptr<PointerType>();
- }
-
- /*!
- @brief get a pointer value (explicit)
- @copydoc get()
- */
- template<typename PointerType, typename std::enable_if<
- std::is_pointer<PointerType>::value, int>::type = 0>
- constexpr const PointerType get() const noexcept
- {
- // delegate the call to get_ptr
- return get_ptr<PointerType>();
- }
-
- /*!
- @brief get a pointer value (implicit)
-
- Implicit pointer access to the internally stored JSON value. No copies are
- made.
-
- @warning Writing data to the pointee of the result yields an undefined
- state.
-
- @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
- object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
- @ref number_unsigned_t, or @ref number_float_t. Enforced by a static
- assertion.
-
- @return pointer to the internally stored JSON value if the requested
- pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
-
- @complexity Constant.
-
- @liveexample{The example below shows how pointers to internal values of a
- JSON value can be requested. Note that no type conversions are made and a
- `nullptr` is returned if the value and the requested pointer type does not
- match.,get_ptr}
-
- @since version 1.0.0
- */
- template<typename PointerType, typename std::enable_if<
- std::is_pointer<PointerType>::value, int>::type = 0>
- PointerType get_ptr() noexcept
- {
- // get the type of the PointerType (remove pointer and const)
- using pointee_t = typename std::remove_const<typename
- std::remove_pointer<typename
- std::remove_const<PointerType>::type>::type>::type;
- // make sure the type matches the allowed types
- static_assert(
- std::is_same<object_t, pointee_t>::value
- or std::is_same<array_t, pointee_t>::value
- or std::is_same<string_t, pointee_t>::value
- or std::is_same<boolean_t, pointee_t>::value
- or std::is_same<number_integer_t, pointee_t>::value
- or std::is_same<number_unsigned_t, pointee_t>::value
- or std::is_same<number_float_t, pointee_t>::value
- , "incompatible pointer type");
-
- // delegate the call to get_impl_ptr<>()
- return get_impl_ptr(static_cast<PointerType>(nullptr));
- }
-
- /*!
- @brief get a pointer value (implicit)
- @copydoc get_ptr()
- */
- template<typename PointerType, typename std::enable_if<
- std::is_pointer<PointerType>::value and
- std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0>
- constexpr const PointerType get_ptr() const noexcept
- {
- // get the type of the PointerType (remove pointer and const)
- using pointee_t = typename std::remove_const<typename
- std::remove_pointer<typename
- std::remove_const<PointerType>::type>::type>::type;
- // make sure the type matches the allowed types
- static_assert(
- std::is_same<object_t, pointee_t>::value
- or std::is_same<array_t, pointee_t>::value
- or std::is_same<string_t, pointee_t>::value
- or std::is_same<boolean_t, pointee_t>::value
- or std::is_same<number_integer_t, pointee_t>::value
- or std::is_same<number_unsigned_t, pointee_t>::value
- or std::is_same<number_float_t, pointee_t>::value
- , "incompatible pointer type");
-
- // delegate the call to get_impl_ptr<>() const
- return get_impl_ptr(static_cast<PointerType>(nullptr));
- }
-
- /*!
- @brief get a reference value (implicit)
-
- Implicit reference access to the internally stored JSON value. No copies
- are made.
-
- @warning Writing data to the referee of the result yields an undefined
- state.
-
- @tparam ReferenceType reference type; must be a reference to @ref array_t,
- @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or
- @ref number_float_t. Enforced by static assertion.
-
- @return reference to the internally stored JSON value if the requested
- reference type @a ReferenceType fits to the JSON value; throws
- type_error.303 otherwise
-
- @throw type_error.303 in case passed type @a ReferenceType is incompatible
- with the stored JSON value; see example below
-
- @complexity Constant.
-
- @liveexample{The example shows several calls to `get_ref()`.,get_ref}
-
- @since version 1.1.0
- */
- template<typename ReferenceType, typename std::enable_if<
- std::is_reference<ReferenceType>::value, int>::type = 0>
- ReferenceType get_ref()
- {
- // delegate call to get_ref_impl
- return get_ref_impl<ReferenceType>(*this);
- }
-
- /*!
- @brief get a reference value (implicit)
- @copydoc get_ref()
- */
- template<typename ReferenceType, typename std::enable_if<
- std::is_reference<ReferenceType>::value and
- std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0>
- ReferenceType get_ref() const
- {
- // delegate call to get_ref_impl
- return get_ref_impl<ReferenceType>(*this);
- }
-
- /*!
- @brief get a value (implicit)
-
- Implicit type conversion between the JSON value and a compatible value.
- The call is realized by calling @ref get() const.
-
- @tparam ValueType non-pointer type compatible to the JSON value, for
- instance `int` for JSON integer numbers, `bool` for JSON booleans, or
- `std::vector` types for JSON arrays. The character type of @ref string_t
- as well as an initializer list of this type is excluded to avoid
- ambiguities as these types implicitly convert to `std::string`.
-
- @return copy of the JSON value, converted to type @a ValueType
-
- @throw type_error.302 in case passed type @a ValueType is incompatible
- to the JSON value type (e.g., the JSON value is of type boolean, but a
- string is requested); see example below
-
- @complexity Linear in the size of the JSON value.
-
- @liveexample{The example below shows several conversions from JSON values
- to other types. There a few things to note: (1) Floating-point numbers can
- be converted to integers\, (2) A JSON array can be converted to a standard
- `std::vector<short>`\, (3) A JSON object can be converted to C++
- associative containers such as `std::unordered_map<std::string\,
- json>`.,operator__ValueType}
-
- @since version 1.0.0
- */
- template < typename ValueType, typename std::enable_if <
- not std::is_pointer<ValueType>::value and
- not std::is_same<ValueType, detail::json_ref<basic_json>>::value and
- not std::is_same<ValueType, typename string_t::value_type>::value and
- not detail::is_basic_json<ValueType>::value
-#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015
- and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value
-#endif
-#if defined(JSON_HAS_CPP_17)
- and not std::is_same<ValueType, typename std::string_view>::value
-#endif
- , int >::type = 0 >
- operator ValueType() const
- {
- // delegate the call to get<>() const
- return get<ValueType>();
- }
-
- /// @}
-
-
- ////////////////////
- // element access //
- ////////////////////
-
- /// @name element access
- /// Access to the JSON value.
- /// @{
-
- /*!
- @brief access specified array element with bounds checking
-
- Returns a reference to the element at specified location @a idx, with
- bounds checking.
-
- @param[in] idx index of the element to access
-
- @return reference to the element at index @a idx
-
- @throw type_error.304 if the JSON value is not an array; in this case,
- calling `at` with an index makes no sense. See example below.
- @throw out_of_range.401 if the index @a idx is out of range of the array;
- that is, `idx >= size()`. See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @since version 1.0.0
-
- @liveexample{The example below shows how array elements can be read and
- written using `at()`. It also demonstrates the different exceptions that
- can be thrown.,at__size_type}
- */
- reference at(size_type idx)
- {
- // at only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- JSON_TRY
- {
- return m_value.array->at(idx);
- }
- JSON_CATCH (std::out_of_range&)
- {
- // create better exception explanation
- JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
- }
- }
- else
- {
- JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief access specified array element with bounds checking
-
- Returns a const reference to the element at specified location @a idx,
- with bounds checking.
-
- @param[in] idx index of the element to access
-
- @return const reference to the element at index @a idx
-
- @throw type_error.304 if the JSON value is not an array; in this case,
- calling `at` with an index makes no sense. See example below.
- @throw out_of_range.401 if the index @a idx is out of range of the array;
- that is, `idx >= size()`. See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @since version 1.0.0
-
- @liveexample{The example below shows how array elements can be read using
- `at()`. It also demonstrates the different exceptions that can be thrown.,
- at__size_type_const}
- */
- const_reference at(size_type idx) const
- {
- // at only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- JSON_TRY
- {
- return m_value.array->at(idx);
- }
- JSON_CATCH (std::out_of_range&)
- {
- // create better exception explanation
- JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
- }
- }
- else
- {
- JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief access specified object element with bounds checking
-
- Returns a reference to the element at with specified key @a key, with
- bounds checking.
-
- @param[in] key key of the element to access
-
- @return reference to the element at key @a key
-
- @throw type_error.304 if the JSON value is not an object; in this case,
- calling `at` with a key makes no sense. See example below.
- @throw out_of_range.403 if the key @a key is is not stored in the object;
- that is, `find(key) == end()`. See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Logarithmic in the size of the container.
-
- @sa @ref operator[](const typename object_t::key_type&) for unchecked
- access by reference
- @sa @ref value() for access by value with a default value
-
- @since version 1.0.0
-
- @liveexample{The example below shows how object elements can be read and
- written using `at()`. It also demonstrates the different exceptions that
- can be thrown.,at__object_t_key_type}
- */
- reference at(const typename object_t::key_type& key)
- {
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- JSON_TRY
- {
- return m_value.object->at(key);
- }
- JSON_CATCH (std::out_of_range&)
- {
- // create better exception explanation
- JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
- }
- }
- else
- {
- JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief access specified object element with bounds checking
-
- Returns a const reference to the element at with specified key @a key,
- with bounds checking.
-
- @param[in] key key of the element to access
-
- @return const reference to the element at key @a key
-
- @throw type_error.304 if the JSON value is not an object; in this case,
- calling `at` with a key makes no sense. See example below.
- @throw out_of_range.403 if the key @a key is is not stored in the object;
- that is, `find(key) == end()`. See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Logarithmic in the size of the container.
-
- @sa @ref operator[](const typename object_t::key_type&) for unchecked
- access by reference
- @sa @ref value() for access by value with a default value
-
- @since version 1.0.0
-
- @liveexample{The example below shows how object elements can be read using
- `at()`. It also demonstrates the different exceptions that can be thrown.,
- at__object_t_key_type_const}
- */
- const_reference at(const typename object_t::key_type& key) const
- {
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- JSON_TRY
- {
- return m_value.object->at(key);
- }
- JSON_CATCH (std::out_of_range&)
- {
- // create better exception explanation
- JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
- }
- }
- else
- {
- JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief access specified array element
-
- Returns a reference to the element at specified location @a idx.
-
- @note If @a idx is beyond the range of the array (i.e., `idx >= size()`),
- then the array is silently filled up with `null` values to make `idx` a
- valid reference to the last stored element.
-
- @param[in] idx index of the element to access
-
- @return reference to the element at index @a idx
-
- @throw type_error.305 if the JSON value is not an array or null; in that
- cases, using the [] operator with an index makes no sense.
-
- @complexity Constant if @a idx is in the range of the array. Otherwise
- linear in `idx - size()`.
-
- @liveexample{The example below shows how array elements can be read and
- written using `[]` operator. Note the addition of `null`
- values.,operatorarray__size_type}
-
- @since version 1.0.0
- */
- reference operator[](size_type idx)
- {
- // implicitly convert null value to an empty array
- if (is_null())
- {
- m_type = value_t::array;
- m_value.array = create<array_t>();
- assert_invariant();
- }
-
- // operator[] only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- // fill up array with null values if given idx is outside range
- if (idx >= m_value.array->size())
- {
- m_value.array->insert(m_value.array->end(),
- idx - m_value.array->size() + 1,
- basic_json());
- }
-
- return m_value.array->operator[](idx);
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief access specified array element
-
- Returns a const reference to the element at specified location @a idx.
-
- @param[in] idx index of the element to access
-
- @return const reference to the element at index @a idx
-
- @throw type_error.305 if the JSON value is not an array; in that case,
- using the [] operator with an index makes no sense.
-
- @complexity Constant.
-
- @liveexample{The example below shows how array elements can be read using
- the `[]` operator.,operatorarray__size_type_const}
-
- @since version 1.0.0
- */
- const_reference operator[](size_type idx) const
- {
- // const operator[] only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- return m_value.array->operator[](idx);
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief access specified object element
-
- Returns a reference to the element at with specified key @a key.
-
- @note If @a key is not found in the object, then it is silently added to
- the object and filled with a `null` value to make `key` a valid reference.
- In case the value was `null` before, it is converted to an object.
-
- @param[in] key key of the element to access
-
- @return reference to the element at key @a key
-
- @throw type_error.305 if the JSON value is not an object or null; in that
- cases, using the [] operator with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be read and
- written using the `[]` operator.,operatorarray__key_type}
-
- @sa @ref at(const typename object_t::key_type&) for access by reference
- with range checking
- @sa @ref value() for access by value with a default value
-
- @since version 1.0.0
- */
- reference operator[](const typename object_t::key_type& key)
- {
- // implicitly convert null value to an empty object
- if (is_null())
- {
- m_type = value_t::object;
- m_value.object = create<object_t>();
- assert_invariant();
- }
-
- // operator[] only works for objects
- if (JSON_LIKELY(is_object()))
- {
- return m_value.object->operator[](key);
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief read-only access specified object element
-
- Returns a const reference to the element at with specified key @a key. No
- bounds checking is performed.
-
- @warning If the element with key @a key does not exist, the behavior is
- undefined.
-
- @param[in] key key of the element to access
-
- @return const reference to the element at key @a key
-
- @pre The element with key @a key must exist. **This precondition is
- enforced with an assertion.**
-
- @throw type_error.305 if the JSON value is not an object; in that case,
- using the [] operator with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be read using
- the `[]` operator.,operatorarray__key_type_const}
-
- @sa @ref at(const typename object_t::key_type&) for access by reference
- with range checking
- @sa @ref value() for access by value with a default value
-
- @since version 1.0.0
- */
- const_reference operator[](const typename object_t::key_type& key) const
- {
- // const operator[] only works for objects
- if (JSON_LIKELY(is_object()))
- {
- assert(m_value.object->find(key) != m_value.object->end());
- return m_value.object->find(key)->second;
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief access specified object element
-
- Returns a reference to the element at with specified key @a key.
-
- @note If @a key is not found in the object, then it is silently added to
- the object and filled with a `null` value to make `key` a valid reference.
- In case the value was `null` before, it is converted to an object.
-
- @param[in] key key of the element to access
-
- @return reference to the element at key @a key
-
- @throw type_error.305 if the JSON value is not an object or null; in that
- cases, using the [] operator with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be read and
- written using the `[]` operator.,operatorarray__key_type}
-
- @sa @ref at(const typename object_t::key_type&) for access by reference
- with range checking
- @sa @ref value() for access by value with a default value
-
- @since version 1.1.0
- */
- template<typename T>
- reference operator[](T* key)
- {
- // implicitly convert null to object
- if (is_null())
- {
- m_type = value_t::object;
- m_value = value_t::object;
- assert_invariant();
- }
-
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- return m_value.object->operator[](key);
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief read-only access specified object element
-
- Returns a const reference to the element at with specified key @a key. No
- bounds checking is performed.
-
- @warning If the element with key @a key does not exist, the behavior is
- undefined.
-
- @param[in] key key of the element to access
-
- @return const reference to the element at key @a key
-
- @pre The element with key @a key must exist. **This precondition is
- enforced with an assertion.**
-
- @throw type_error.305 if the JSON value is not an object; in that case,
- using the [] operator with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be read using
- the `[]` operator.,operatorarray__key_type_const}
-
- @sa @ref at(const typename object_t::key_type&) for access by reference
- with range checking
- @sa @ref value() for access by value with a default value
-
- @since version 1.1.0
- */
- template<typename T>
- const_reference operator[](T* key) const
- {
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- assert(m_value.object->find(key) != m_value.object->end());
- return m_value.object->find(key)->second;
- }
-
- JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name())));
- }
-
- /*!
- @brief access specified object element with default value
-
- Returns either a copy of an object's element at the specified key @a key
- or a given default value if no element with key @a key exists.
-
- The function is basically equivalent to executing
- @code {.cpp}
- try {
- return at(key);
- } catch(out_of_range) {
- return default_value;
- }
- @endcode
-
- @note Unlike @ref at(const typename object_t::key_type&), this function
- does not throw if the given key @a key was not found.
-
- @note Unlike @ref operator[](const typename object_t::key_type& key), this
- function does not implicitly add an element to the position defined by @a
- key. This function is furthermore also applicable to const objects.
-
- @param[in] key key of the element to access
- @param[in] default_value the value to return if @a key is not found
-
- @tparam ValueType type compatible to JSON values, for instance `int` for
- JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
- JSON arrays. Note the type of the expected value at @a key and the default
- value @a default_value must be compatible.
-
- @return copy of the element at key @a key or @a default_value if @a key
- is not found
-
- @throw type_error.306 if the JSON value is not an object; in that case,
- using `value()` with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be queried
- with a default value.,basic_json__value}
-
- @sa @ref at(const typename object_t::key_type&) for access by reference
- with range checking
- @sa @ref operator[](const typename object_t::key_type&) for unchecked
- access by reference
-
- @since version 1.0.0
- */
- template<class ValueType, typename std::enable_if<
- std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
- ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const
- {
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- // if key is found, return value and given default value otherwise
- const auto it = find(key);
- if (it != end())
- {
- return *it;
- }
-
- return default_value;
- }
-
- JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
- }
-
- /*!
- @brief overload for a default value of type const char*
- @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const
- */
- string_t value(const typename object_t::key_type& key, const char* default_value) const
- {
- return value(key, string_t(default_value));
- }
-
- /*!
- @brief access specified object element via JSON Pointer with default value
-
- Returns either a copy of an object's element at the specified key @a key
- or a given default value if no element with key @a key exists.
-
- The function is basically equivalent to executing
- @code {.cpp}
- try {
- return at(ptr);
- } catch(out_of_range) {
- return default_value;
- }
- @endcode
-
- @note Unlike @ref at(const json_pointer&), this function does not throw
- if the given key @a key was not found.
-
- @param[in] ptr a JSON pointer to the element to access
- @param[in] default_value the value to return if @a ptr found no value
-
- @tparam ValueType type compatible to JSON values, for instance `int` for
- JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
- JSON arrays. Note the type of the expected value at @a key and the default
- value @a default_value must be compatible.
-
- @return copy of the element at key @a key or @a default_value if @a key
- is not found
-
- @throw type_error.306 if the JSON value is not an objec; in that case,
- using `value()` with a key makes no sense.
-
- @complexity Logarithmic in the size of the container.
-
- @liveexample{The example below shows how object elements can be queried
- with a default value.,basic_json__value_ptr}
-
- @sa @ref operator[](const json_pointer&) for unchecked access by reference
-
- @since version 2.0.2
- */
- template<class ValueType, typename std::enable_if<
- std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
- ValueType value(const json_pointer& ptr, const ValueType& default_value) const
- {
- // at only works for objects
- if (JSON_LIKELY(is_object()))
- {
- // if pointer resolves a value, return it or use default value
- JSON_TRY
- {
- return ptr.get_checked(this);
- }
- JSON_CATCH (out_of_range&)
- {
- return default_value;
- }
- }
-
- JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
- }
-
- /*!
- @brief overload for a default value of type const char*
- @copydoc basic_json::value(const json_pointer&, ValueType) const
- */
- string_t value(const json_pointer& ptr, const char* default_value) const
- {
- return value(ptr, string_t(default_value));
- }
-
- /*!
- @brief access the first element
-
- Returns a reference to the first element in the container. For a JSON
- container `c`, the expression `c.front()` is equivalent to `*c.begin()`.
-
- @return In case of a structured type (array or object), a reference to the
- first element is returned. In case of number, string, or boolean values, a
- reference to the value is returned.
-
- @complexity Constant.
-
- @pre The JSON value must not be `null` (would throw `std::out_of_range`)
- or an empty array or object (undefined behavior, **guarded by
- assertions**).
- @post The JSON value remains unchanged.
-
- @throw invalid_iterator.214 when called on `null` value
-
- @liveexample{The following code shows an example for `front()`.,front}
-
- @sa @ref back() -- access the last element
-
- @since version 1.0.0
- */
- reference front()
- {
- return *begin();
- }
-
- /*!
- @copydoc basic_json::front()
- */
- const_reference front() const
- {
- return *cbegin();
- }
-
- /*!
- @brief access the last element
-
- Returns a reference to the last element in the container. For a JSON
- container `c`, the expression `c.back()` is equivalent to
- @code {.cpp}
- auto tmp = c.end();
- --tmp;
- return *tmp;
- @endcode
-
- @return In case of a structured type (array or object), a reference to the
- last element is returned. In case of number, string, or boolean values, a
- reference to the value is returned.
-
- @complexity Constant.
-
- @pre The JSON value must not be `null` (would throw `std::out_of_range`)
- or an empty array or object (undefined behavior, **guarded by
- assertions**).
- @post The JSON value remains unchanged.
-
- @throw invalid_iterator.214 when called on a `null` value. See example
- below.
-
- @liveexample{The following code shows an example for `back()`.,back}
-
- @sa @ref front() -- access the first element
-
- @since version 1.0.0
- */
- reference back()
- {
- auto tmp = end();
- --tmp;
- return *tmp;
- }
-
- /*!
- @copydoc basic_json::back()
- */
- const_reference back() const
- {
- auto tmp = cend();
- --tmp;
- return *tmp;
- }
-
- /*!
- @brief remove element given an iterator
-
- Removes the element specified by iterator @a pos. The iterator @a pos must
- be valid and dereferenceable. Thus the `end()` iterator (which is valid,
- but is not dereferenceable) cannot be used as a value for @a pos.
-
- If called on a primitive type other than `null`, the resulting JSON value
- will be `null`.
-
- @param[in] pos iterator to the element to remove
- @return Iterator following the last removed element. If the iterator @a
- pos refers to the last element, the `end()` iterator is returned.
-
- @tparam IteratorType an @ref iterator or @ref const_iterator
-
- @post Invalidates iterators and references at or after the point of the
- erase, including the `end()` iterator.
-
- @throw type_error.307 if called on a `null` value; example: `"cannot use
- erase() with null"`
- @throw invalid_iterator.202 if called on an iterator which does not belong
- to the current JSON value; example: `"iterator does not fit current
- value"`
- @throw invalid_iterator.205 if called on a primitive type with invalid
- iterator (i.e., any iterator which is not `begin()`); example: `"iterator
- out of range"`
-
- @complexity The complexity depends on the type:
- - objects: amortized constant
- - arrays: linear in distance between @a pos and the end of the container
- - strings: linear in the length of the string
- - other types: constant
-
- @liveexample{The example shows the result of `erase()` for different JSON
- types.,erase__IteratorType}
-
- @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
- the given range
- @sa @ref erase(const typename object_t::key_type&) -- removes the element
- from an object at the given key
- @sa @ref erase(const size_type) -- removes the element from an array at
- the given index
-
- @since version 1.0.0
- */
- template<class IteratorType, typename std::enable_if<
- std::is_same<IteratorType, typename basic_json_t::iterator>::value or
- std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
- = 0>
- IteratorType erase(IteratorType pos)
- {
- // make sure iterator fits the current value
- if (JSON_UNLIKELY(this != pos.m_object))
- {
- JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
- }
-
- IteratorType result = end();
-
- switch (m_type)
- {
- case value_t::boolean:
- case value_t::number_float:
- case value_t::number_integer:
- case value_t::number_unsigned:
- case value_t::string:
- {
- if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin()))
- {
- JSON_THROW(invalid_iterator::create(205, "iterator out of range"));
- }
-
- if (is_string())
- {
- AllocatorType<string_t> alloc;
- std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
- std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
- m_value.string = nullptr;
- }
-
- m_type = value_t::null;
- assert_invariant();
- break;
- }
-
- case value_t::object:
- {
- result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator);
- break;
- }
-
- case value_t::array:
- {
- result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator);
- break;
- }
-
- default:
- JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
- }
-
- return result;
- }
-
- /*!
- @brief remove elements given an iterator range
-
- Removes the element specified by the range `[first; last)`. The iterator
- @a first does not need to be dereferenceable if `first == last`: erasing
- an empty range is a no-op.
-
- If called on a primitive type other than `null`, the resulting JSON value
- will be `null`.
-
- @param[in] first iterator to the beginning of the range to remove
- @param[in] last iterator past the end of the range to remove
- @return Iterator following the last removed element. If the iterator @a
- second refers to the last element, the `end()` iterator is returned.
-
- @tparam IteratorType an @ref iterator or @ref const_iterator
-
- @post Invalidates iterators and references at or after the point of the
- erase, including the `end()` iterator.
-
- @throw type_error.307 if called on a `null` value; example: `"cannot use
- erase() with null"`
- @throw invalid_iterator.203 if called on iterators which does not belong
- to the current JSON value; example: `"iterators do not fit current value"`
- @throw invalid_iterator.204 if called on a primitive type with invalid
- iterators (i.e., if `first != begin()` and `last != end()`); example:
- `"iterators out of range"`
-
- @complexity The complexity depends on the type:
- - objects: `log(size()) + std::distance(first, last)`
- - arrays: linear in the distance between @a first and @a last, plus linear
- in the distance between @a last and end of the container
- - strings: linear in the length of the string
- - other types: constant
-
- @liveexample{The example shows the result of `erase()` for different JSON
- types.,erase__IteratorType_IteratorType}
-
- @sa @ref erase(IteratorType) -- removes the element at a given position
- @sa @ref erase(const typename object_t::key_type&) -- removes the element
- from an object at the given key
- @sa @ref erase(const size_type) -- removes the element from an array at
- the given index
-
- @since version 1.0.0
- */
- template<class IteratorType, typename std::enable_if<
- std::is_same<IteratorType, typename basic_json_t::iterator>::value or
- std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
- = 0>
- IteratorType erase(IteratorType first, IteratorType last)
- {
- // make sure iterator fits the current value
- if (JSON_UNLIKELY(this != first.m_object or this != last.m_object))
- {
- JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value"));
- }
-
- IteratorType result = end();
-
- switch (m_type)
- {
- case value_t::boolean:
- case value_t::number_float:
- case value_t::number_integer:
- case value_t::number_unsigned:
- case value_t::string:
- {
- if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin()
- or not last.m_it.primitive_iterator.is_end()))
- {
- JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
- }
-
- if (is_string())
- {
- AllocatorType<string_t> alloc;
- std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
- std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
- m_value.string = nullptr;
- }
-
- m_type = value_t::null;
- assert_invariant();
- break;
- }
-
- case value_t::object:
- {
- result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator,
- last.m_it.object_iterator);
- break;
- }
-
- case value_t::array:
- {
- result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator,
- last.m_it.array_iterator);
- break;
- }
-
- default:
- JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
- }
-
- return result;
- }
-
- /*!
- @brief remove element from a JSON object given a key
-
- Removes elements from a JSON object with the key value @a key.
-
- @param[in] key value of the elements to remove
-
- @return Number of elements removed. If @a ObjectType is the default
- `std::map` type, the return value will always be `0` (@a key was not
- found) or `1` (@a key was found).
-
- @post References and iterators to the erased elements are invalidated.
- Other references and iterators are not affected.
-
- @throw type_error.307 when called on a type other than JSON object;
- example: `"cannot use erase() with null"`
-
- @complexity `log(size()) + count(key)`
-
- @liveexample{The example shows the effect of `erase()`.,erase__key_type}
-
- @sa @ref erase(IteratorType) -- removes the element at a given position
- @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
- the given range
- @sa @ref erase(const size_type) -- removes the element from an array at
- the given index
-
- @since version 1.0.0
- */
- size_type erase(const typename object_t::key_type& key)
- {
- // this erase only works for objects
- if (JSON_LIKELY(is_object()))
- {
- return m_value.object->erase(key);
- }
-
- JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
- }
-
- /*!
- @brief remove element from a JSON array given an index
-
- Removes element from a JSON array at the index @a idx.
-
- @param[in] idx index of the element to remove
-
- @throw type_error.307 when called on a type other than JSON object;
- example: `"cannot use erase() with null"`
- @throw out_of_range.401 when `idx >= size()`; example: `"array index 17
- is out of range"`
-
- @complexity Linear in distance between @a idx and the end of the container.
-
- @liveexample{The example shows the effect of `erase()`.,erase__size_type}
-
- @sa @ref erase(IteratorType) -- removes the element at a given position
- @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
- the given range
- @sa @ref erase(const typename object_t::key_type&) -- removes the element
- from an object at the given key
-
- @since version 1.0.0
- */
- void erase(const size_type idx)
- {
- // this erase only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- if (JSON_UNLIKELY(idx >= size()))
- {
- JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
- }
-
- m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
- }
- else
- {
- JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
- }
- }
-
- /// @}
-
-
- ////////////
- // lookup //
- ////////////
-
- /// @name lookup
- /// @{
-
- /*!
- @brief find an element in a JSON object
-
- Finds an element in a JSON object with key equivalent to @a key. If the
- element is not found or the JSON value is not an object, end() is
- returned.
-
- @note This method always returns @ref end() when executed on a JSON type
- that is not an object.
-
- @param[in] key key value of the element to search for.
-
- @return Iterator to an element with key equivalent to @a key. If no such
- element is found or the JSON value is not an object, past-the-end (see
- @ref end()) iterator is returned.
-
- @complexity Logarithmic in the size of the JSON object.
-
- @liveexample{The example shows how `find()` is used.,find__key_type}
-
- @since version 1.0.0
- */
- template<typename KeyT>
- iterator find(KeyT&& key)
- {
- auto result = end();
-
- if (is_object())
- {
- result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
- }
-
- return result;
- }
-
- /*!
- @brief find an element in a JSON object
- @copydoc find(KeyT&&)
- */
- template<typename KeyT>
- const_iterator find(KeyT&& key) const
- {
- auto result = cend();
-
- if (is_object())
- {
- result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
- }
-
- return result;
- }
-
- /*!
- @brief returns the number of occurrences of a key in a JSON object
-
- Returns the number of elements with key @a key. If ObjectType is the
- default `std::map` type, the return value will always be `0` (@a key was
- not found) or `1` (@a key was found).
-
- @note This method always returns `0` when executed on a JSON type that is
- not an object.
-
- @param[in] key key value of the element to count
-
- @return Number of elements with key @a key. If the JSON value is not an
- object, the return value will be `0`.
-
- @complexity Logarithmic in the size of the JSON object.
-
- @liveexample{The example shows how `count()` is used.,count}
-
- @since version 1.0.0
- */
- template<typename KeyT>
- size_type count(KeyT&& key) const
- {
- // return 0 for all nonobject types
- return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0;
- }
-
- /// @}
-
-
- ///////////////
- // iterators //
- ///////////////
-
- /// @name iterators
- /// @{
-
- /*!
- @brief returns an iterator to the first element
-
- Returns an iterator to the first element.
-
- @image html range-begin-end.svg "Illustration from cppreference.com"
-
- @return iterator to the first element
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
-
- @liveexample{The following code shows an example for `begin()`.,begin}
-
- @sa @ref cbegin() -- returns a const iterator to the beginning
- @sa @ref end() -- returns an iterator to the end
- @sa @ref cend() -- returns a const iterator to the end
-
- @since version 1.0.0
- */
- iterator begin() noexcept
- {
- iterator result(this);
- result.set_begin();
- return result;
- }
-
- /*!
- @copydoc basic_json::cbegin()
- */
- const_iterator begin() const noexcept
- {
- return cbegin();
- }
-
- /*!
- @brief returns a const iterator to the first element
-
- Returns a const iterator to the first element.
-
- @image html range-begin-end.svg "Illustration from cppreference.com"
-
- @return const iterator to the first element
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
- - Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
-
- @liveexample{The following code shows an example for `cbegin()`.,cbegin}
-
- @sa @ref begin() -- returns an iterator to the beginning
- @sa @ref end() -- returns an iterator to the end
- @sa @ref cend() -- returns a const iterator to the end
-
- @since version 1.0.0
- */
- const_iterator cbegin() const noexcept
- {
- const_iterator result(this);
- result.set_begin();
- return result;
- }
-
- /*!
- @brief returns an iterator to one past the last element
-
- Returns an iterator to one past the last element.
-
- @image html range-begin-end.svg "Illustration from cppreference.com"
-
- @return iterator one past the last element
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
-
- @liveexample{The following code shows an example for `end()`.,end}
-
- @sa @ref cend() -- returns a const iterator to the end
- @sa @ref begin() -- returns an iterator to the beginning
- @sa @ref cbegin() -- returns a const iterator to the beginning
-
- @since version 1.0.0
- */
- iterator end() noexcept
- {
- iterator result(this);
- result.set_end();
- return result;
- }
-
- /*!
- @copydoc basic_json::cend()
- */
- const_iterator end() const noexcept
- {
- return cend();
- }
-
- /*!
- @brief returns a const iterator to one past the last element
-
- Returns a const iterator to one past the last element.
-
- @image html range-begin-end.svg "Illustration from cppreference.com"
-
- @return const iterator one past the last element
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
- - Has the semantics of `const_cast<const basic_json&>(*this).end()`.
-
- @liveexample{The following code shows an example for `cend()`.,cend}
-
- @sa @ref end() -- returns an iterator to the end
- @sa @ref begin() -- returns an iterator to the beginning
- @sa @ref cbegin() -- returns a const iterator to the beginning
-
- @since version 1.0.0
- */
- const_iterator cend() const noexcept
- {
- const_iterator result(this);
- result.set_end();
- return result;
- }
-
- /*!
- @brief returns an iterator to the reverse-beginning
-
- Returns an iterator to the reverse-beginning; that is, the last element.
-
- @image html range-rbegin-rend.svg "Illustration from cppreference.com"
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
- requirements:
- - The complexity is constant.
- - Has the semantics of `reverse_iterator(end())`.
-
- @liveexample{The following code shows an example for `rbegin()`.,rbegin}
-
- @sa @ref crbegin() -- returns a const reverse iterator to the beginning
- @sa @ref rend() -- returns a reverse iterator to the end
- @sa @ref crend() -- returns a const reverse iterator to the end
-
- @since version 1.0.0
- */
- reverse_iterator rbegin() noexcept
- {
- return reverse_iterator(end());
- }
-
- /*!
- @copydoc basic_json::crbegin()
- */
- const_reverse_iterator rbegin() const noexcept
- {
- return crbegin();
- }
-
- /*!
- @brief returns an iterator to the reverse-end
-
- Returns an iterator to the reverse-end; that is, one before the first
- element.
-
- @image html range-rbegin-rend.svg "Illustration from cppreference.com"
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
- requirements:
- - The complexity is constant.
- - Has the semantics of `reverse_iterator(begin())`.
-
- @liveexample{The following code shows an example for `rend()`.,rend}
-
- @sa @ref crend() -- returns a const reverse iterator to the end
- @sa @ref rbegin() -- returns a reverse iterator to the beginning
- @sa @ref crbegin() -- returns a const reverse iterator to the beginning
-
- @since version 1.0.0
- */
- reverse_iterator rend() noexcept
- {
- return reverse_iterator(begin());
- }
-
- /*!
- @copydoc basic_json::crend()
- */
- const_reverse_iterator rend() const noexcept
- {
- return crend();
- }
-
- /*!
- @brief returns a const reverse iterator to the last element
-
- Returns a const iterator to the reverse-beginning; that is, the last
- element.
-
- @image html range-rbegin-rend.svg "Illustration from cppreference.com"
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
- requirements:
- - The complexity is constant.
- - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
-
- @liveexample{The following code shows an example for `crbegin()`.,crbegin}
-
- @sa @ref rbegin() -- returns a reverse iterator to the beginning
- @sa @ref rend() -- returns a reverse iterator to the end
- @sa @ref crend() -- returns a const reverse iterator to the end
-
- @since version 1.0.0
- */
- const_reverse_iterator crbegin() const noexcept
- {
- return const_reverse_iterator(cend());
- }
-
- /*!
- @brief returns a const reverse iterator to one before the first
-
- Returns a const reverse iterator to the reverse-end; that is, one before
- the first element.
-
- @image html range-rbegin-rend.svg "Illustration from cppreference.com"
-
- @complexity Constant.
-
- @requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
- requirements:
- - The complexity is constant.
- - Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
-
- @liveexample{The following code shows an example for `crend()`.,crend}
-
- @sa @ref rend() -- returns a reverse iterator to the end
- @sa @ref rbegin() -- returns a reverse iterator to the beginning
- @sa @ref crbegin() -- returns a const reverse iterator to the beginning
-
- @since version 1.0.0
- */
- const_reverse_iterator crend() const noexcept
- {
- return const_reverse_iterator(cbegin());
- }
-
- public:
- /*!
- @brief wrapper to access iterator member functions in range-based for
-
- This function allows to access @ref iterator::key() and @ref
- iterator::value() during range-based for loops. In these loops, a
- reference to the JSON values is returned, so there is no access to the
- underlying iterator.
-
- For loop without iterator_wrapper:
-
- @code{cpp}
- for (auto it = j_object.begin(); it != j_object.end(); ++it)
- {
- std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
- }
- @endcode
-
- Range-based for loop without iterator proxy:
-
- @code{cpp}
- for (auto it : j_object)
- {
- // "it" is of type json::reference and has no key() member
- std::cout << "value: " << it << '\n';
- }
- @endcode
-
- Range-based for loop with iterator proxy:
-
- @code{cpp}
- for (auto it : json::iterator_wrapper(j_object))
- {
- std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
- }
- @endcode
-
- @note When iterating over an array, `key()` will return the index of the
- element as string (see example).
-
- @param[in] ref reference to a JSON value
- @return iteration proxy object wrapping @a ref with an interface to use in
- range-based for loops
-
- @liveexample{The following code shows how the wrapper is used,iterator_wrapper}
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @note The name of this function is not yet final and may change in the
- future.
-
- @deprecated This stream operator is deprecated and will be removed in
- future 4.0.0 of the library. Please use @ref items() instead;
- that is, replace `json::iterator_wrapper(j)` with `j.items()`.
- */
- JSON_DEPRECATED
- static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
- {
- return ref.items();
- }
-
- /*!
- @copydoc iterator_wrapper(reference)
- */
- JSON_DEPRECATED
- static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
- {
- return ref.items();
- }
-
- /*!
- @brief helper to access iterator member functions in range-based for
-
- This function allows to access @ref iterator::key() and @ref
- iterator::value() during range-based for loops. In these loops, a
- reference to the JSON values is returned, so there is no access to the
- underlying iterator.
-
- For loop without `items()` function:
-
- @code{cpp}
- for (auto it = j_object.begin(); it != j_object.end(); ++it)
- {
- std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
- }
- @endcode
-
- Range-based for loop without `items()` function:
-
- @code{cpp}
- for (auto it : j_object)
- {
- // "it" is of type json::reference and has no key() member
- std::cout << "value: " << it << '\n';
- }
- @endcode
-
- Range-based for loop with `items()` function:
-
- @code{cpp}
- for (auto it : j_object.items())
- {
- std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
- }
- @endcode
-
- @note When iterating over an array, `key()` will return the index of the
- element as string (see example). For primitive types (e.g., numbers),
- `key()` returns an empty string.
-
- @return iteration proxy object wrapping @a ref with an interface to use in
- range-based for loops
-
- @liveexample{The following code shows how the function is used.,items}
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @since version 3.x.x.
- */
- iteration_proxy<iterator> items() noexcept
- {
- return iteration_proxy<iterator>(*this);
- }
-
- /*!
- @copydoc items()
- */
- iteration_proxy<const_iterator> items() const noexcept
- {
- return iteration_proxy<const_iterator>(*this);
- }
-
- /// @}
-
-
- //////////////
- // capacity //
- //////////////
-
- /// @name capacity
- /// @{
-
- /*!
- @brief checks whether the container is empty.
-
- Checks if a JSON value has no elements (i.e. whether its @ref size is `0`).
-
- @return The return value depends on the different types and is
- defined as follows:
- Value type | return value
- ----------- | -------------
- null | `true`
- boolean | `false`
- string | `false`
- number | `false`
- object | result of function `object_t::empty()`
- array | result of function `array_t::empty()`
-
- @liveexample{The following code uses `empty()` to check if a JSON
- object contains any elements.,empty}
-
- @complexity Constant, as long as @ref array_t and @ref object_t satisfy
- the Container concept; that is, their `empty()` functions have constant
- complexity.
-
- @iterators No changes.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @note This function does not return whether a string stored as JSON value
- is empty - it returns whether the JSON container itself is empty which is
- false in the case of a string.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
- - Has the semantics of `begin() == end()`.
-
- @sa @ref size() -- returns the number of elements
-
- @since version 1.0.0
- */
- bool empty() const noexcept
- {
- switch (m_type)
- {
- case value_t::null:
- {
- // null values are empty
- return true;
- }
-
- case value_t::array:
- {
- // delegate call to array_t::empty()
- return m_value.array->empty();
- }
-
- case value_t::object:
- {
- // delegate call to object_t::empty()
- return m_value.object->empty();
- }
-
- default:
- {
- // all other types are nonempty
- return false;
- }
- }
- }
-
- /*!
- @brief returns the number of elements
-
- Returns the number of elements in a JSON value.
-
- @return The return value depends on the different types and is
- defined as follows:
- Value type | return value
- ----------- | -------------
- null | `0`
- boolean | `1`
- string | `1`
- number | `1`
- object | result of function object_t::size()
- array | result of function array_t::size()
-
- @liveexample{The following code calls `size()` on the different value
- types.,size}
-
- @complexity Constant, as long as @ref array_t and @ref object_t satisfy
- the Container concept; that is, their size() functions have constant
- complexity.
-
- @iterators No changes.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @note This function does not return the length of a string stored as JSON
- value - it returns the number of elements in the JSON value which is 1 in
- the case of a string.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
- - Has the semantics of `std::distance(begin(), end())`.
-
- @sa @ref empty() -- checks whether the container is empty
- @sa @ref max_size() -- returns the maximal number of elements
-
- @since version 1.0.0
- */
- size_type size() const noexcept
- {
- switch (m_type)
- {
- case value_t::null:
- {
- // null values are empty
- return 0;
- }
-
- case value_t::array:
- {
- // delegate call to array_t::size()
- return m_value.array->size();
- }
-
- case value_t::object:
- {
- // delegate call to object_t::size()
- return m_value.object->size();
- }
-
- default:
- {
- // all other types have size 1
- return 1;
- }
- }
- }
-
- /*!
- @brief returns the maximum possible number of elements
-
- Returns the maximum number of elements a JSON value is able to hold due to
- system or library implementation limitations, i.e. `std::distance(begin(),
- end())` for the JSON value.
-
- @return The return value depends on the different types and is
- defined as follows:
- Value type | return value
- ----------- | -------------
- null | `0` (same as `size()`)
- boolean | `1` (same as `size()`)
- string | `1` (same as `size()`)
- number | `1` (same as `size()`)
- object | result of function `object_t::max_size()`
- array | result of function `array_t::max_size()`
-
- @liveexample{The following code calls `max_size()` on the different value
- types. Note the output is implementation specific.,max_size}
-
- @complexity Constant, as long as @ref array_t and @ref object_t satisfy
- the Container concept; that is, their `max_size()` functions have constant
- complexity.
-
- @iterators No changes.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
- requirements:
- - The complexity is constant.
- - Has the semantics of returning `b.size()` where `b` is the largest
- possible JSON value.
-
- @sa @ref size() -- returns the number of elements
-
- @since version 1.0.0
- */
- size_type max_size() const noexcept
- {
- switch (m_type)
- {
- case value_t::array:
- {
- // delegate call to array_t::max_size()
- return m_value.array->max_size();
- }
-
- case value_t::object:
- {
- // delegate call to object_t::max_size()
- return m_value.object->max_size();
- }
-
- default:
- {
- // all other types have max_size() == size()
- return size();
- }
- }
- }
-
- /// @}
-
-
- ///////////////
- // modifiers //
- ///////////////
-
- /// @name modifiers
- /// @{
-
- /*!
- @brief clears the contents
-
- Clears the content of a JSON value and resets it to the default value as
- if @ref basic_json(value_t) would have been called with the current value
- type from @ref type():
-
- Value type | initial value
- ----------- | -------------
- null | `null`
- boolean | `false`
- string | `""`
- number | `0`
- object | `{}`
- array | `[]`
-
- @post Has the same effect as calling
- @code {.cpp}
- *this = basic_json(type());
- @endcode
-
- @liveexample{The example below shows the effect of `clear()` to different
- JSON types.,clear}
-
- @complexity Linear in the size of the JSON value.
-
- @iterators All iterators, pointers and references related to this container
- are invalidated.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @sa @ref basic_json(value_t) -- constructor that creates an object with the
- same value than calling `clear()`
-
- @since version 1.0.0
- */
- void clear() noexcept
- {
- switch (m_type)
- {
- case value_t::number_integer:
- {
- m_value.number_integer = 0;
- break;
- }
-
- case value_t::number_unsigned:
- {
- m_value.number_unsigned = 0;
- break;
- }
-
- case value_t::number_float:
- {
- m_value.number_float = 0.0;
- break;
- }
-
- case value_t::boolean:
- {
- m_value.boolean = false;
- break;
- }
-
- case value_t::string:
- {
- m_value.string->clear();
- break;
- }
-
- case value_t::array:
- {
- m_value.array->clear();
- break;
- }
-
- case value_t::object:
- {
- m_value.object->clear();
- break;
- }
-
- default:
- break;
- }
- }
-
- /*!
- @brief add an object to an array
-
- Appends the given element @a val to the end of the JSON value. If the
- function is called on a JSON null value, an empty array is created before
- appending @a val.
-
- @param[in] val the value to add to the JSON array
-
- @throw type_error.308 when called on a type other than JSON array or
- null; example: `"cannot use push_back() with number"`
-
- @complexity Amortized constant.
-
- @liveexample{The example shows how `push_back()` and `+=` can be used to
- add elements to a JSON array. Note how the `null` value was silently
- converted to a JSON array.,push_back}
-
- @since version 1.0.0
- */
- void push_back(basic_json&& val)
- {
- // push_back only works for null objects or arrays
- if (JSON_UNLIKELY(not(is_null() or is_array())))
- {
- JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
- }
-
- // transform null object into an array
- if (is_null())
- {
- m_type = value_t::array;
- m_value = value_t::array;
- assert_invariant();
- }
-
- // add element to array (move semantics)
- m_value.array->push_back(std::move(val));
- // invalidate object
- val.m_type = value_t::null;
- }
-
- /*!
- @brief add an object to an array
- @copydoc push_back(basic_json&&)
- */
- reference operator+=(basic_json&& val)
- {
- push_back(std::move(val));
- return *this;
- }
-
- /*!
- @brief add an object to an array
- @copydoc push_back(basic_json&&)
- */
- void push_back(const basic_json& val)
- {
- // push_back only works for null objects or arrays
- if (JSON_UNLIKELY(not(is_null() or is_array())))
- {
- JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
- }
-
- // transform null object into an array
- if (is_null())
- {
- m_type = value_t::array;
- m_value = value_t::array;
- assert_invariant();
- }
-
- // add element to array
- m_value.array->push_back(val);
- }
-
- /*!
- @brief add an object to an array
- @copydoc push_back(basic_json&&)
- */
- reference operator+=(const basic_json& val)
- {
- push_back(val);
- return *this;
- }
-
- /*!
- @brief add an object to an object
-
- Inserts the given element @a val to the JSON object. If the function is
- called on a JSON null value, an empty object is created before inserting
- @a val.
-
- @param[in] val the value to add to the JSON object
-
- @throw type_error.308 when called on a type other than JSON object or
- null; example: `"cannot use push_back() with number"`
-
- @complexity Logarithmic in the size of the container, O(log(`size()`)).
-
- @liveexample{The example shows how `push_back()` and `+=` can be used to
- add elements to a JSON object. Note how the `null` value was silently
- converted to a JSON object.,push_back__object_t__value}
-
- @since version 1.0.0
- */
- void push_back(const typename object_t::value_type& val)
- {
- // push_back only works for null objects or objects
- if (JSON_UNLIKELY(not(is_null() or is_object())))
- {
- JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
- }
-
- // transform null object into an object
- if (is_null())
- {
- m_type = value_t::object;
- m_value = value_t::object;
- assert_invariant();
- }
-
- // add element to array
- m_value.object->insert(val);
- }
-
- /*!
- @brief add an object to an object
- @copydoc push_back(const typename object_t::value_type&)
- */
- reference operator+=(const typename object_t::value_type& val)
- {
- push_back(val);
- return *this;
- }
-
- /*!
- @brief add an object to an object
-
- This function allows to use `push_back` with an initializer list. In case
-
- 1. the current value is an object,
- 2. the initializer list @a init contains only two elements, and
- 3. the first element of @a init is a string,
-
- @a init is converted into an object element and added using
- @ref push_back(const typename object_t::value_type&). Otherwise, @a init
- is converted to a JSON value and added using @ref push_back(basic_json&&).
-
- @param[in] init an initializer list
-
- @complexity Linear in the size of the initializer list @a init.
-
- @note This function is required to resolve an ambiguous overload error,
- because pairs like `{"key", "value"}` can be both interpreted as
- `object_t::value_type` or `std::initializer_list<basic_json>`, see
- https://github.com/nlohmann/json/issues/235 for more information.
-
- @liveexample{The example shows how initializer lists are treated as
- objects when possible.,push_back__initializer_list}
- */
- void push_back(initializer_list_t init)
- {
- if (is_object() and init.size() == 2 and (*init.begin())->is_string())
- {
- basic_json&& key = init.begin()->moved_or_copied();
- push_back(typename object_t::value_type(
- std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));
- }
- else
- {
- push_back(basic_json(init));
- }
- }
-
- /*!
- @brief add an object to an object
- @copydoc push_back(initializer_list_t)
- */
- reference operator+=(initializer_list_t init)
- {
- push_back(init);
- return *this;
- }
-
- /*!
- @brief add an object to an array
-
- Creates a JSON value from the passed parameters @a args to the end of the
- JSON value. If the function is called on a JSON null value, an empty array
- is created before appending the value created from @a args.
-
- @param[in] args arguments to forward to a constructor of @ref basic_json
- @tparam Args compatible types to create a @ref basic_json object
-
- @throw type_error.311 when called on a type other than JSON array or
- null; example: `"cannot use emplace_back() with number"`
-
- @complexity Amortized constant.
-
- @liveexample{The example shows how `push_back()` can be used to add
- elements to a JSON array. Note how the `null` value was silently converted
- to a JSON array.,emplace_back}
-
- @since version 2.0.8
- */
- template<class... Args>
- void emplace_back(Args&& ... args)
- {
- // emplace_back only works for null objects or arrays
- if (JSON_UNLIKELY(not(is_null() or is_array())))
- {
- JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name())));
- }
-
- // transform null object into an array
- if (is_null())
- {
- m_type = value_t::array;
- m_value = value_t::array;
- assert_invariant();
- }
-
- // add element to array (perfect forwarding)
- m_value.array->emplace_back(std::forward<Args>(args)...);
- }
-
- /*!
- @brief add an object to an object if key does not exist
-
- Inserts a new element into a JSON object constructed in-place with the
- given @a args if there is no element with the key in the container. If the
- function is called on a JSON null value, an empty object is created before
- appending the value created from @a args.
-
- @param[in] args arguments to forward to a constructor of @ref basic_json
- @tparam Args compatible types to create a @ref basic_json object
-
- @return a pair consisting of an iterator to the inserted element, or the
- already-existing element if no insertion happened, and a bool
- denoting whether the insertion took place.
-
- @throw type_error.311 when called on a type other than JSON object or
- null; example: `"cannot use emplace() with number"`
-
- @complexity Logarithmic in the size of the container, O(log(`size()`)).
-
- @liveexample{The example shows how `emplace()` can be used to add elements
- to a JSON object. Note how the `null` value was silently converted to a
- JSON object. Further note how no value is added if there was already one
- value stored with the same key.,emplace}
-
- @since version 2.0.8
- */
- template<class... Args>
- std::pair<iterator, bool> emplace(Args&& ... args)
- {
- // emplace only works for null objects or arrays
- if (JSON_UNLIKELY(not(is_null() or is_object())))
- {
- JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name())));
- }
-
- // transform null object into an object
- if (is_null())
- {
- m_type = value_t::object;
- m_value = value_t::object;
- assert_invariant();
- }
-
- // add element to array (perfect forwarding)
- auto res = m_value.object->emplace(std::forward<Args>(args)...);
- // create result iterator and set iterator to the result of emplace
- auto it = begin();
- it.m_it.object_iterator = res.first;
-
- // return pair of iterator and boolean
- return {it, res.second};
- }
-
- /*!
- @brief inserts element
-
- Inserts element @a val before iterator @a pos.
-
- @param[in] pos iterator before which the content will be inserted; may be
- the end() iterator
- @param[in] val element to insert
- @return iterator pointing to the inserted @a val.
-
- @throw type_error.309 if called on JSON values other than arrays;
- example: `"cannot use insert() with string"`
- @throw invalid_iterator.202 if @a pos is not an iterator of *this;
- example: `"iterator does not fit current value"`
-
- @complexity Constant plus linear in the distance between @a pos and end of
- the container.
-
- @liveexample{The example shows how `insert()` is used.,insert}
-
- @since version 1.0.0
- */
- iterator insert(const_iterator pos, const basic_json& val)
- {
- // insert only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- // check if iterator pos fits to this JSON value
- if (JSON_UNLIKELY(pos.m_object != this))
- {
- JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
- }
-
- // insert to array and return iterator
- iterator result(this);
- result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val);
- return result;
- }
-
- JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
- }
-
- /*!
- @brief inserts element
- @copydoc insert(const_iterator, const basic_json&)
- */
- iterator insert(const_iterator pos, basic_json&& val)
- {
- return insert(pos, val);
- }
-
- /*!
- @brief inserts elements
-
- Inserts @a cnt copies of @a val before iterator @a pos.
-
- @param[in] pos iterator before which the content will be inserted; may be
- the end() iterator
- @param[in] cnt number of copies of @a val to insert
- @param[in] val element to insert
- @return iterator pointing to the first element inserted, or @a pos if
- `cnt==0`
-
- @throw type_error.309 if called on JSON values other than arrays; example:
- `"cannot use insert() with string"`
- @throw invalid_iterator.202 if @a pos is not an iterator of *this;
- example: `"iterator does not fit current value"`
-
- @complexity Linear in @a cnt plus linear in the distance between @a pos
- and end of the container.
-
- @liveexample{The example shows how `insert()` is used.,insert__count}
-
- @since version 1.0.0
- */
- iterator insert(const_iterator pos, size_type cnt, const basic_json& val)
- {
- // insert only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- // check if iterator pos fits to this JSON value
- if (JSON_UNLIKELY(pos.m_object != this))
- {
- JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
- }
-
- // insert to array and return iterator
- iterator result(this);
- result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);
- return result;
- }
-
- JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
- }
-
- /*!
- @brief inserts elements
-
- Inserts elements from range `[first, last)` before iterator @a pos.
-
- @param[in] pos iterator before which the content will be inserted; may be
- the end() iterator
- @param[in] first begin of the range of elements to insert
- @param[in] last end of the range of elements to insert
-
- @throw type_error.309 if called on JSON values other than arrays; example:
- `"cannot use insert() with string"`
- @throw invalid_iterator.202 if @a pos is not an iterator of *this;
- example: `"iterator does not fit current value"`
- @throw invalid_iterator.210 if @a first and @a last do not belong to the
- same JSON value; example: `"iterators do not fit"`
- @throw invalid_iterator.211 if @a first or @a last are iterators into
- container for which insert is called; example: `"passed iterators may not
- belong to container"`
-
- @return iterator pointing to the first element inserted, or @a pos if
- `first==last`
-
- @complexity Linear in `std::distance(first, last)` plus linear in the
- distance between @a pos and end of the container.
-
- @liveexample{The example shows how `insert()` is used.,insert__range}
-
- @since version 1.0.0
- */
- iterator insert(const_iterator pos, const_iterator first, const_iterator last)
- {
- // insert only works for arrays
- if (JSON_UNLIKELY(not is_array()))
- {
- JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
- }
-
- // check if iterator pos fits to this JSON value
- if (JSON_UNLIKELY(pos.m_object != this))
- {
- JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
- }
-
- // check if range iterators belong to the same JSON object
- if (JSON_UNLIKELY(first.m_object != last.m_object))
- {
- JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
- }
-
- if (JSON_UNLIKELY(first.m_object == this))
- {
- JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container"));
- }
-
- // insert to array and return iterator
- iterator result(this);
- result.m_it.array_iterator = m_value.array->insert(
- pos.m_it.array_iterator,
- first.m_it.array_iterator,
- last.m_it.array_iterator);
- return result;
- }
-
- /*!
- @brief inserts elements
-
- Inserts elements from initializer list @a ilist before iterator @a pos.
-
- @param[in] pos iterator before which the content will be inserted; may be
- the end() iterator
- @param[in] ilist initializer list to insert the values from
-
- @throw type_error.309 if called on JSON values other than arrays; example:
- `"cannot use insert() with string"`
- @throw invalid_iterator.202 if @a pos is not an iterator of *this;
- example: `"iterator does not fit current value"`
-
- @return iterator pointing to the first element inserted, or @a pos if
- `ilist` is empty
-
- @complexity Linear in `ilist.size()` plus linear in the distance between
- @a pos and end of the container.
-
- @liveexample{The example shows how `insert()` is used.,insert__ilist}
-
- @since version 1.0.0
- */
- iterator insert(const_iterator pos, initializer_list_t ilist)
- {
- // insert only works for arrays
- if (JSON_UNLIKELY(not is_array()))
- {
- JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
- }
-
- // check if iterator pos fits to this JSON value
- if (JSON_UNLIKELY(pos.m_object != this))
- {
- JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
- }
-
- // insert to array and return iterator
- iterator result(this);
- result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end());
- return result;
- }
-
- /*!
- @brief inserts elements
-
- Inserts elements from range `[first, last)`.
-
- @param[in] first begin of the range of elements to insert
- @param[in] last end of the range of elements to insert
-
- @throw type_error.309 if called on JSON values other than objects; example:
- `"cannot use insert() with string"`
- @throw invalid_iterator.202 if iterator @a first or @a last does does not
- point to an object; example: `"iterators first and last must point to
- objects"`
- @throw invalid_iterator.210 if @a first and @a last do not belong to the
- same JSON value; example: `"iterators do not fit"`
-
- @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number
- of elements to insert.
-
- @liveexample{The example shows how `insert()` is used.,insert__range_object}
-
- @since version 3.0.0
- */
- void insert(const_iterator first, const_iterator last)
- {
- // insert only works for objects
- if (JSON_UNLIKELY(not is_object()))
- {
- JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
- }
-
- // check if range iterators belong to the same JSON object
- if (JSON_UNLIKELY(first.m_object != last.m_object))
- {
- JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
- }
-
- // passed iterators must belong to objects
- if (JSON_UNLIKELY(not first.m_object->is_object()))
- {
- JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
- }
-
- m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
- }
-
- /*!
- @brief updates a JSON object from another object, overwriting existing keys
-
- Inserts all values from JSON object @a j and overwrites existing keys.
-
- @param[in] j JSON object to read values from
-
- @throw type_error.312 if called on JSON values other than objects; example:
- `"cannot use update() with string"`
-
- @complexity O(N*log(size() + N)), where N is the number of elements to
- insert.
-
- @liveexample{The example shows how `update()` is used.,update}
-
- @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
-
- @since version 3.0.0
- */
- void update(const_reference j)
- {
- // implicitly convert null value to an empty object
- if (is_null())
- {
- m_type = value_t::object;
- m_value.object = create<object_t>();
- assert_invariant();
- }
-
- if (JSON_UNLIKELY(not is_object()))
- {
- JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
- }
- if (JSON_UNLIKELY(not j.is_object()))
- {
- JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name())));
- }
-
- for (auto it = j.cbegin(); it != j.cend(); ++it)
- {
- m_value.object->operator[](it.key()) = it.value();
- }
- }
-
- /*!
- @brief updates a JSON object from another object, overwriting existing keys
-
- Inserts all values from from range `[first, last)` and overwrites existing
- keys.
-
- @param[in] first begin of the range of elements to insert
- @param[in] last end of the range of elements to insert
-
- @throw type_error.312 if called on JSON values other than objects; example:
- `"cannot use update() with string"`
- @throw invalid_iterator.202 if iterator @a first or @a last does does not
- point to an object; example: `"iterators first and last must point to
- objects"`
- @throw invalid_iterator.210 if @a first and @a last do not belong to the
- same JSON value; example: `"iterators do not fit"`
-
- @complexity O(N*log(size() + N)), where N is the number of elements to
- insert.
-
- @liveexample{The example shows how `update()` is used__range.,update}
-
- @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
-
- @since version 3.0.0
- */
- void update(const_iterator first, const_iterator last)
- {
- // implicitly convert null value to an empty object
- if (is_null())
- {
- m_type = value_t::object;
- m_value.object = create<object_t>();
- assert_invariant();
- }
-
- if (JSON_UNLIKELY(not is_object()))
- {
- JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
- }
-
- // check if range iterators belong to the same JSON object
- if (JSON_UNLIKELY(first.m_object != last.m_object))
- {
- JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
- }
-
- // passed iterators must belong to objects
- if (JSON_UNLIKELY(not first.m_object->is_object()
- or not last.m_object->is_object()))
- {
- JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
- }
-
- for (auto it = first; it != last; ++it)
- {
- m_value.object->operator[](it.key()) = it.value();
- }
- }
-
- /*!
- @brief exchanges the values
-
- Exchanges the contents of the JSON value with those of @a other. Does not
- invoke any move, copy, or swap operations on individual elements. All
- iterators and references remain valid. The past-the-end iterator is
- invalidated.
-
- @param[in,out] other JSON value to exchange the contents with
-
- @complexity Constant.
-
- @liveexample{The example below shows how JSON values can be swapped with
- `swap()`.,swap__reference}
-
- @since version 1.0.0
- */
- void swap(reference other) noexcept (
- std::is_nothrow_move_constructible<value_t>::value and
- std::is_nothrow_move_assignable<value_t>::value and
- std::is_nothrow_move_constructible<json_value>::value and
- std::is_nothrow_move_assignable<json_value>::value
- )
- {
- std::swap(m_type, other.m_type);
- std::swap(m_value, other.m_value);
- assert_invariant();
- }
-
- /*!
- @brief exchanges the values
-
- Exchanges the contents of a JSON array with those of @a other. Does not
- invoke any move, copy, or swap operations on individual elements. All
- iterators and references remain valid. The past-the-end iterator is
- invalidated.
-
- @param[in,out] other array to exchange the contents with
-
- @throw type_error.310 when JSON value is not an array; example: `"cannot
- use swap() with string"`
-
- @complexity Constant.
-
- @liveexample{The example below shows how arrays can be swapped with
- `swap()`.,swap__array_t}
-
- @since version 1.0.0
- */
- void swap(array_t& other)
- {
- // swap only works for arrays
- if (JSON_LIKELY(is_array()))
- {
- std::swap(*(m_value.array), other);
- }
- else
- {
- JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief exchanges the values
-
- Exchanges the contents of a JSON object with those of @a other. Does not
- invoke any move, copy, or swap operations on individual elements. All
- iterators and references remain valid. The past-the-end iterator is
- invalidated.
-
- @param[in,out] other object to exchange the contents with
-
- @throw type_error.310 when JSON value is not an object; example:
- `"cannot use swap() with string"`
-
- @complexity Constant.
-
- @liveexample{The example below shows how objects can be swapped with
- `swap()`.,swap__object_t}
-
- @since version 1.0.0
- */
- void swap(object_t& other)
- {
- // swap only works for objects
- if (JSON_LIKELY(is_object()))
- {
- std::swap(*(m_value.object), other);
- }
- else
- {
- JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
- }
- }
-
- /*!
- @brief exchanges the values
-
- Exchanges the contents of a JSON string with those of @a other. Does not
- invoke any move, copy, or swap operations on individual elements. All
- iterators and references remain valid. The past-the-end iterator is
- invalidated.
-
- @param[in,out] other string to exchange the contents with
-
- @throw type_error.310 when JSON value is not a string; example: `"cannot
- use swap() with boolean"`
-
- @complexity Constant.
-
- @liveexample{The example below shows how strings can be swapped with
- `swap()`.,swap__string_t}
-
- @since version 1.0.0
- */
- void swap(string_t& other)
- {
- // swap only works for strings
- if (JSON_LIKELY(is_string()))
- {
- std::swap(*(m_value.string), other);
- }
- else
- {
- JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
- }
- }
-
- /// @}
-
- public:
- //////////////////////////////////////////
- // lexicographical comparison operators //
- //////////////////////////////////////////
-
- /// @name lexicographical comparison operators
- /// @{
-
- /*!
- @brief comparison: equal
-
- Compares two JSON values for equality according to the following rules:
- - Two JSON values are equal if (1) they are from the same type and (2)
- their stored values are the same according to their respective
- `operator==`.
- - Integer and floating-point numbers are automatically converted before
- comparison. Note than two NaN values are always treated as unequal.
- - Two JSON null values are equal.
-
- @note Floating-point inside JSON values numbers are compared with
- `json::number_float_t::operator==` which is `double::operator==` by
- default. To compare floating-point while respecting an epsilon, an alternative
- [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39)
- could be used, for instance
- @code {.cpp}
- template<typename T, typename = typename std::enable_if<std::is_floating_point<T>::value, T>::type>
- inline bool is_same(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept
- {
- return std::abs(a - b) <= epsilon;
- }
- @endcode
-
- @note NaN values never compare equal to themselves or to other NaN values.
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether the values @a lhs and @a rhs are equal
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @complexity Linear.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__equal}
-
- @since version 1.0.0
- */
- friend bool operator==(const_reference lhs, const_reference rhs) noexcept
- {
- const auto lhs_type = lhs.type();
- const auto rhs_type = rhs.type();
-
- if (lhs_type == rhs_type)
- {
- switch (lhs_type)
- {
- case value_t::array:
- return (*lhs.m_value.array == *rhs.m_value.array);
-
- case value_t::object:
- return (*lhs.m_value.object == *rhs.m_value.object);
-
- case value_t::null:
- return true;
-
- case value_t::string:
- return (*lhs.m_value.string == *rhs.m_value.string);
-
- case value_t::boolean:
- return (lhs.m_value.boolean == rhs.m_value.boolean);
-
- case value_t::number_integer:
- return (lhs.m_value.number_integer == rhs.m_value.number_integer);
-
- case value_t::number_unsigned:
- return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned);
-
- case value_t::number_float:
- return (lhs.m_value.number_float == rhs.m_value.number_float);
-
- default:
- return false;
- }
- }
- else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
- {
- return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float);
- }
- else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
- {
- return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer));
- }
- else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
- {
- return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float);
- }
- else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
- {
- return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned));
- }
- else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
- {
- return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer);
- }
- else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
- {
- return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned));
- }
-
- return false;
- }
-
- /*!
- @brief comparison: equal
- @copydoc operator==(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs == basic_json(rhs));
- }
-
- /*!
- @brief comparison: equal
- @copydoc operator==(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) == rhs);
- }
-
- /*!
- @brief comparison: not equal
-
- Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether the values @a lhs and @a rhs are not equal
-
- @complexity Linear.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__notequal}
-
- @since version 1.0.0
- */
- friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
- {
- return not (lhs == rhs);
- }
-
- /*!
- @brief comparison: not equal
- @copydoc operator!=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs != basic_json(rhs));
- }
-
- /*!
- @brief comparison: not equal
- @copydoc operator!=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) != rhs);
- }
-
- /*!
- @brief comparison: less than
-
- Compares whether one JSON value @a lhs is less than another JSON value @a
- rhs according to the following rules:
- - If @a lhs and @a rhs have the same type, the values are compared using
- the default `<` operator.
- - Integer and floating-point numbers are automatically converted before
- comparison
- - In case @a lhs and @a rhs have different types, the values are ignored
- and the order of the types is considered, see
- @ref operator<(const value_t, const value_t).
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether @a lhs is less than @a rhs
-
- @complexity Linear.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__less}
-
- @since version 1.0.0
- */
- friend bool operator<(const_reference lhs, const_reference rhs) noexcept
- {
- const auto lhs_type = lhs.type();
- const auto rhs_type = rhs.type();
-
- if (lhs_type == rhs_type)
- {
- switch (lhs_type)
- {
- case value_t::array:
- return (*lhs.m_value.array) < (*rhs.m_value.array);
-
- case value_t::object:
- return *lhs.m_value.object < *rhs.m_value.object;
-
- case value_t::null:
- return false;
-
- case value_t::string:
- return *lhs.m_value.string < *rhs.m_value.string;
-
- case value_t::boolean:
- return lhs.m_value.boolean < rhs.m_value.boolean;
-
- case value_t::number_integer:
- return lhs.m_value.number_integer < rhs.m_value.number_integer;
-
- case value_t::number_unsigned:
- return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned;
-
- case value_t::number_float:
- return lhs.m_value.number_float < rhs.m_value.number_float;
-
- default:
- return false;
- }
- }
- else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
- {
- return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float;
- }
- else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
- {
- return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer);
- }
- else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
- {
- return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float;
- }
- else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
- {
- return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned);
- }
- else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
- {
- return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned);
- }
- else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
- {
- return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer;
- }
-
- // We only reach this line if we cannot compare values. In that case,
- // we compare types. Note we have to call the operator explicitly,
- // because MSVC has problems otherwise.
- return operator<(lhs_type, rhs_type);
- }
-
- /*!
- @brief comparison: less than
- @copydoc operator<(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs < basic_json(rhs));
- }
-
- /*!
- @brief comparison: less than
- @copydoc operator<(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) < rhs);
- }
-
- /*!
- @brief comparison: less than or equal
-
- Compares whether one JSON value @a lhs is less than or equal to another
- JSON value by calculating `not (rhs < lhs)`.
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether @a lhs is less than or equal to @a rhs
-
- @complexity Linear.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__greater}
-
- @since version 1.0.0
- */
- friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
- {
- return not (rhs < lhs);
- }
-
- /*!
- @brief comparison: less than or equal
- @copydoc operator<=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs <= basic_json(rhs));
- }
-
- /*!
- @brief comparison: less than or equal
- @copydoc operator<=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) <= rhs);
- }
-
- /*!
- @brief comparison: greater than
-
- Compares whether one JSON value @a lhs is greater than another
- JSON value by calculating `not (lhs <= rhs)`.
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether @a lhs is greater than to @a rhs
-
- @complexity Linear.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__lessequal}
-
- @since version 1.0.0
- */
- friend bool operator>(const_reference lhs, const_reference rhs) noexcept
- {
- return not (lhs <= rhs);
- }
-
- /*!
- @brief comparison: greater than
- @copydoc operator>(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs > basic_json(rhs));
- }
-
- /*!
- @brief comparison: greater than
- @copydoc operator>(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) > rhs);
- }
-
- /*!
- @brief comparison: greater than or equal
-
- Compares whether one JSON value @a lhs is greater than or equal to another
- JSON value by calculating `not (lhs < rhs)`.
-
- @param[in] lhs first JSON value to consider
- @param[in] rhs second JSON value to consider
- @return whether @a lhs is greater than or equal to @a rhs
-
- @complexity Linear.
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @liveexample{The example demonstrates comparing several JSON
- types.,operator__greaterequal}
-
- @since version 1.0.0
- */
- friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
- {
- return not (lhs < rhs);
- }
-
- /*!
- @brief comparison: greater than or equal
- @copydoc operator>=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept
- {
- return (lhs >= basic_json(rhs));
- }
-
- /*!
- @brief comparison: greater than or equal
- @copydoc operator>=(const_reference, const_reference)
- */
- template<typename ScalarType, typename std::enable_if<
- std::is_scalar<ScalarType>::value, int>::type = 0>
- friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept
- {
- return (basic_json(lhs) >= rhs);
- }
-
- /// @}
-
- ///////////////////
- // serialization //
- ///////////////////
-
- /// @name serialization
- /// @{
-
- /*!
- @brief serialize to stream
-
- Serialize the given JSON value @a j to the output stream @a o. The JSON
- value will be serialized using the @ref dump member function.
-
- - The indentation of the output can be controlled with the member variable
- `width` of the output stream @a o. For instance, using the manipulator
- `std::setw(4)` on @a o sets the indentation level to `4` and the
- serialization result is the same as calling `dump(4)`.
-
- - The indentation character can be controlled with the member variable
- `fill` of the output stream @a o. For instance, the manipulator
- `std::setfill('\\t')` sets indentation to use a tab character rather than
- the default space character.
-
- @param[in,out] o stream to serialize to
- @param[in] j JSON value to serialize
-
- @return the stream @a o
-
- @throw type_error.316 if a string stored inside the JSON value is not
- UTF-8 encoded
-
- @complexity Linear.
-
- @liveexample{The example below shows the serialization with different
- parameters to `width` to adjust the indentation level.,operator_serialize}
-
- @since version 1.0.0; indentation character added in version 3.0.0
- */
- friend std::ostream& operator<<(std::ostream& o, const basic_json& j)
- {
- // read width member and use it as indentation parameter if nonzero
- const bool pretty_print = (o.width() > 0);
- const auto indentation = (pretty_print ? o.width() : 0);
-
- // reset width to 0 for subsequent calls to this stream
- o.width(0);
-
- // do the actual serialization
- serializer s(detail::output_adapter<char>(o), o.fill());
- s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
- return o;
- }
-
- /*!
- @brief serialize to stream
- @deprecated This stream operator is deprecated and will be removed in
- future 4.0.0 of the library. Please use
- @ref operator<<(std::ostream&, const basic_json&)
- instead; that is, replace calls like `j >> o;` with `o << j;`.
- @since version 1.0.0; deprecated since version 3.0.0
- */
- JSON_DEPRECATED
- friend std::ostream& operator>>(const basic_json& j, std::ostream& o)
- {
- return o << j;
- }
-
- /// @}
-
-
- /////////////////////
- // deserialization //
- /////////////////////
-
- /// @name deserialization
- /// @{
-
- /*!
- @brief deserialize from a compatible input
-
- This function reads from a compatible input. Examples are:
- - an array of 1-byte values
- - strings with character/literal type with size of 1 byte
- - input streams
- - container with contiguous storage of 1-byte values. Compatible container
- types include `std::vector`, `std::string`, `std::array`,
- `std::valarray`, and `std::initializer_list`. Furthermore, C-style
- arrays can be used with `std::begin()`/`std::end()`. User-defined
- containers can be used as long as they implement random-access iterators
- and a contiguous storage.
-
- @pre Each element of the container has a size of 1 byte. Violating this
- precondition yields undefined behavior. **This precondition is enforced
- with a static assertion.**
-
- @pre The container storage is contiguous. Violating this precondition
- yields undefined behavior. **This precondition is enforced with an
- assertion.**
- @pre Each element of the container has a size of 1 byte. Violating this
- precondition yields undefined behavior. **This precondition is enforced
- with a static assertion.**
-
- @warning There is no way to enforce all preconditions at compile-time. If
- the function is called with a noncompliant container and with
- assertions switched off, the behavior is undefined and will most
- likely yield segmentation violation.
-
- @param[in] i input to read from
- @param[in] cb a parser callback function of type @ref parser_callback_t
- which is used to control the deserialization by filtering unwanted values
- (optional)
-
- @return result of the deserialization
-
- @throw parse_error.101 if a parse error occurs; example: `""unexpected end
- of input; expected string literal""`
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
-
- @complexity Linear in the length of the input. The parser is a predictive
- LL(1) parser. The complexity can be higher if the parser callback function
- @a cb has a super-linear complexity.
-
- @note A UTF-8 byte order mark is silently ignored.
-
- @liveexample{The example below demonstrates the `parse()` function reading
- from an array.,parse__array__parser_callback_t}
-
- @liveexample{The example below demonstrates the `parse()` function with
- and without callback function.,parse__string__parser_callback_t}
-
- @liveexample{The example below demonstrates the `parse()` function with
- and without callback function.,parse__istream__parser_callback_t}
-
- @liveexample{The example below demonstrates the `parse()` function reading
- from a contiguous container.,parse__contiguouscontainer__parser_callback_t}
-
- @since version 2.0.3 (contiguous containers)
- */
- static basic_json parse(detail::input_adapter i,
- const parser_callback_t cb = nullptr,
- const bool allow_exceptions = true)
- {
- basic_json result;
- parser(i, cb, allow_exceptions).parse(true, result);
- return result;
- }
-
- /*!
- @copydoc basic_json parse(detail::input_adapter, const parser_callback_t)
- */
- static basic_json parse(detail::input_adapter& i,
- const parser_callback_t cb = nullptr,
- const bool allow_exceptions = true)
- {
- basic_json result;
- parser(i, cb, allow_exceptions).parse(true, result);
- return result;
- }
-
- static bool accept(detail::input_adapter i)
- {
- return parser(i).accept(true);
- }
-
- static bool accept(detail::input_adapter& i)
- {
- return parser(i).accept(true);
- }
-
- /*!
- @brief deserialize from an iterator range with contiguous storage
-
- This function reads from an iterator range of a container with contiguous
- storage of 1-byte values. Compatible container types include
- `std::vector`, `std::string`, `std::array`, `std::valarray`, and
- `std::initializer_list`. Furthermore, C-style arrays can be used with
- `std::begin()`/`std::end()`. User-defined containers can be used as long
- as they implement random-access iterators and a contiguous storage.
-
- @pre The iterator range is contiguous. Violating this precondition yields
- undefined behavior. **This precondition is enforced with an assertion.**
- @pre Each element in the range has a size of 1 byte. Violating this
- precondition yields undefined behavior. **This precondition is enforced
- with a static assertion.**
-
- @warning There is no way to enforce all preconditions at compile-time. If
- the function is called with noncompliant iterators and with
- assertions switched off, the behavior is undefined and will most
- likely yield segmentation violation.
-
- @tparam IteratorType iterator of container with contiguous storage
- @param[in] first begin of the range to parse (included)
- @param[in] last end of the range to parse (excluded)
- @param[in] cb a parser callback function of type @ref parser_callback_t
- which is used to control the deserialization by filtering unwanted values
- (optional)
- @param[in] allow_exceptions whether to throw exceptions in case of a
- parse error (optional, true by default)
-
- @return result of the deserialization
-
- @throw parse_error.101 in case of an unexpected token
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
-
- @complexity Linear in the length of the input. The parser is a predictive
- LL(1) parser. The complexity can be higher if the parser callback function
- @a cb has a super-linear complexity.
-
- @note A UTF-8 byte order mark is silently ignored.
-
- @liveexample{The example below demonstrates the `parse()` function reading
- from an iterator range.,parse__iteratortype__parser_callback_t}
-
- @since version 2.0.3
- */
- template<class IteratorType, typename std::enable_if<
- std::is_base_of<
- std::random_access_iterator_tag,
- typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
- static basic_json parse(IteratorType first, IteratorType last,
- const parser_callback_t cb = nullptr,
- const bool allow_exceptions = true)
- {
- basic_json result;
- parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result);
- return result;
- }
-
- template<class IteratorType, typename std::enable_if<
- std::is_base_of<
- std::random_access_iterator_tag,
- typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
- static bool accept(IteratorType first, IteratorType last)
- {
- return parser(detail::input_adapter(first, last)).accept(true);
- }
-
- /*!
- @brief deserialize from stream
- @deprecated This stream operator is deprecated and will be removed in
- version 4.0.0 of the library. Please use
- @ref operator>>(std::istream&, basic_json&)
- instead; that is, replace calls like `j << i;` with `i >> j;`.
- @since version 1.0.0; deprecated since version 3.0.0
- */
- JSON_DEPRECATED
- friend std::istream& operator<<(basic_json& j, std::istream& i)
- {
- return operator>>(i, j);
- }
-
- /*!
- @brief deserialize from stream
-
- Deserializes an input stream to a JSON value.
-
- @param[in,out] i input stream to read a serialized JSON value from
- @param[in,out] j JSON value to write the deserialized input to
-
- @throw parse_error.101 in case of an unexpected token
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
-
- @complexity Linear in the length of the input. The parser is a predictive
- LL(1) parser.
-
- @note A UTF-8 byte order mark is silently ignored.
-
- @liveexample{The example below shows how a JSON value is constructed by
- reading a serialization from a stream.,operator_deserialize}
-
- @sa parse(std::istream&, const parser_callback_t) for a variant with a
- parser callback function to filter values while parsing
-
- @since version 1.0.0
- */
- friend std::istream& operator>>(std::istream& i, basic_json& j)
- {
- parser(detail::input_adapter(i)).parse(false, j);
- return i;
- }
-
- /// @}
-
- ///////////////////////////
- // convenience functions //
- ///////////////////////////
-
- /*!
- @brief return the type as string
-
- Returns the type name as string to be used in error messages - usually to
- indicate that a function was called on a wrong JSON type.
-
- @return a string representation of a the @a m_type member:
- Value type | return value
- ----------- | -------------
- null | `"null"`
- boolean | `"boolean"`
- string | `"string"`
- number | `"number"` (for all number types)
- object | `"object"`
- array | `"array"`
- discarded | `"discarded"`
-
- @exceptionsafety No-throw guarantee: this function never throws exceptions.
-
- @complexity Constant.
-
- @liveexample{The following code exemplifies `type_name()` for all JSON
- types.,type_name}
-
- @sa @ref type() -- return the type of the JSON value
- @sa @ref operator value_t() -- return the type of the JSON value (implicit)
-
- @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept`
- since 3.0.0
- */
- const char* type_name() const noexcept
- {
- {
- switch (m_type)
- {
- case value_t::null:
- return "null";
- case value_t::object:
- return "object";
- case value_t::array:
- return "array";
- case value_t::string:
- return "string";
- case value_t::boolean:
- return "boolean";
- case value_t::discarded:
- return "discarded";
- default:
- return "number";
- }
- }
- }
-
-
- private:
- //////////////////////
- // member variables //
- //////////////////////
-
- /// the type of the current element
- value_t m_type = value_t::null;
-
- /// the value of the current element
- json_value m_value = {};
-
- //////////////////////////////////////////
- // binary serialization/deserialization //
- //////////////////////////////////////////
-
- /// @name binary serialization/deserialization support
- /// @{
-
- public:
- /*!
- @brief create a CBOR serialization of a given JSON value
-
- Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
- Binary Object Representation) serialization format. CBOR is a binary
- serialization format which aims to be more compact than JSON itself, yet
- more efficient to parse.
-
- The library uses the following mapping from JSON values types to
- CBOR types according to the CBOR specification (RFC 7049):
-
- JSON value type | value/range | CBOR type | first byte
- --------------- | ------------------------------------------ | ---------------------------------- | ---------------
- null | `null` | Null | 0xF6
- boolean | `true` | True | 0xF5
- boolean | `false` | False | 0xF4
- number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B
- number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A
- number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39
- number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38
- number_integer | -24..-1 | Negative integer | 0x20..0x37
- number_integer | 0..23 | Integer | 0x00..0x17
- number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18
- number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19
- number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A
- number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B
- number_unsigned | 0..23 | Integer | 0x00..0x17
- number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18
- number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19
- number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A
- number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B
- number_float | *any value* | Double-Precision Float | 0xFB
- string | *length*: 0..23 | UTF-8 string | 0x60..0x77
- string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78
- string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79
- string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A
- string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B
- array | *size*: 0..23 | array | 0x80..0x97
- array | *size*: 23..255 | array (1 byte follow) | 0x98
- array | *size*: 256..65535 | array (2 bytes follow) | 0x99
- array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A
- array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B
- object | *size*: 0..23 | map | 0xA0..0xB7
- object | *size*: 23..255 | map (1 byte follow) | 0xB8
- object | *size*: 256..65535 | map (2 bytes follow) | 0xB9
- object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA
- object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB
-
- @note The mapping is **complete** in the sense that any JSON value type
- can be converted to a CBOR value.
-
- @note If NaN or Infinity are stored inside a JSON number, they are
- serialized properly. This behavior differs from the @ref dump()
- function which serializes NaN or Infinity to `null`.
-
- @note The following CBOR types are not used in the conversion:
- - byte strings (0x40..0x5F)
- - UTF-8 strings terminated by "break" (0x7F)
- - arrays terminated by "break" (0x9F)
- - maps terminated by "break" (0xBF)
- - date/time (0xC0..0xC1)
- - bignum (0xC2..0xC3)
- - decimal fraction (0xC4)
- - bigfloat (0xC5)
- - tagged items (0xC6..0xD4, 0xD8..0xDB)
- - expected conversions (0xD5..0xD7)
- - simple values (0xE0..0xF3, 0xF8)
- - undefined (0xF7)
- - half and single-precision floats (0xF9-0xFA)
- - break (0xFF)
-
- @param[in] j JSON value to serialize
- @return MessagePack serialization as byte vector
-
- @complexity Linear in the size of the JSON value @a j.
-
- @liveexample{The example shows the serialization of a JSON value to a byte
- vector in CBOR format.,to_cbor}
-
- @sa http://cbor.io
- @sa @ref from_cbor(detail::input_adapter, const bool strict) for the
- analogous deserialization
- @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
- @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
- related UBJSON format
-
- @since version 2.0.9
- */
- static std::vector<uint8_t> to_cbor(const basic_json& j)
- {
- std::vector<uint8_t> result;
- to_cbor(j, result);
- return result;
- }
-
- static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o)
- {
- binary_writer<uint8_t>(o).write_cbor(j);
- }
-
- static void to_cbor(const basic_json& j, detail::output_adapter<char> o)
- {
- binary_writer<char>(o).write_cbor(j);
- }
-
- /*!
- @brief create a MessagePack serialization of a given JSON value
-
- Serializes a given JSON value @a j to a byte vector using the MessagePack
- serialization format. MessagePack is a binary serialization format which
- aims to be more compact than JSON itself, yet more efficient to parse.
-
- The library uses the following mapping from JSON values types to
- MessagePack types according to the MessagePack specification:
-
- JSON value type | value/range | MessagePack type | first byte
- --------------- | --------------------------------- | ---------------- | ----------
- null | `null` | nil | 0xC0
- boolean | `true` | true | 0xC3
- boolean | `false` | false | 0xC2
- number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3
- number_integer | -2147483648..-32769 | int32 | 0xD2
- number_integer | -32768..-129 | int16 | 0xD1
- number_integer | -128..-33 | int8 | 0xD0
- number_integer | -32..-1 | negative fixint | 0xE0..0xFF
- number_integer | 0..127 | positive fixint | 0x00..0x7F
- number_integer | 128..255 | uint 8 | 0xCC
- number_integer | 256..65535 | uint 16 | 0xCD
- number_integer | 65536..4294967295 | uint 32 | 0xCE
- number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF
- number_unsigned | 0..127 | positive fixint | 0x00..0x7F
- number_unsigned | 128..255 | uint 8 | 0xCC
- number_unsigned | 256..65535 | uint 16 | 0xCD
- number_unsigned | 65536..4294967295 | uint 32 | 0xCE
- number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF
- number_float | *any value* | float 64 | 0xCB
- string | *length*: 0..31 | fixstr | 0xA0..0xBF
- string | *length*: 32..255 | str 8 | 0xD9
- string | *length*: 256..65535 | str 16 | 0xDA
- string | *length*: 65536..4294967295 | str 32 | 0xDB
- array | *size*: 0..15 | fixarray | 0x90..0x9F
- array | *size*: 16..65535 | array 16 | 0xDC
- array | *size*: 65536..4294967295 | array 32 | 0xDD
- object | *size*: 0..15 | fix map | 0x80..0x8F
- object | *size*: 16..65535 | map 16 | 0xDE
- object | *size*: 65536..4294967295 | map 32 | 0xDF
-
- @note The mapping is **complete** in the sense that any JSON value type
- can be converted to a MessagePack value.
-
- @note The following values can **not** be converted to a MessagePack value:
- - strings with more than 4294967295 bytes
- - arrays with more than 4294967295 elements
- - objects with more than 4294967295 elements
-
- @note The following MessagePack types are not used in the conversion:
- - bin 8 - bin 32 (0xC4..0xC6)
- - ext 8 - ext 32 (0xC7..0xC9)
- - float 32 (0xCA)
- - fixext 1 - fixext 16 (0xD4..0xD8)
-
- @note Any MessagePack output created @ref to_msgpack can be successfully
- parsed by @ref from_msgpack.
-
- @note If NaN or Infinity are stored inside a JSON number, they are
- serialized properly. This behavior differs from the @ref dump()
- function which serializes NaN or Infinity to `null`.
-
- @param[in] j JSON value to serialize
- @return MessagePack serialization as byte vector
-
- @complexity Linear in the size of the JSON value @a j.
-
- @liveexample{The example shows the serialization of a JSON value to a byte
- vector in MessagePack format.,to_msgpack}
-
- @sa http://msgpack.org
- @sa @ref from_msgpack(const std::vector<uint8_t>&, const size_t) for the
- analogous deserialization
- @sa @ref to_cbor(const basic_json& for the related CBOR format
- @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
- related UBJSON format
-
- @since version 2.0.9
- */
- static std::vector<uint8_t> to_msgpack(const basic_json& j)
- {
- std::vector<uint8_t> result;
- to_msgpack(j, result);
- return result;
- }
-
- static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o)
- {
- binary_writer<uint8_t>(o).write_msgpack(j);
- }
-
- static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)
- {
- binary_writer<char>(o).write_msgpack(j);
- }
-
- /*!
- @brief create a UBJSON serialization of a given JSON value
-
- Serializes a given JSON value @a j to a byte vector using the UBJSON
- (Universal Binary JSON) serialization format. UBJSON aims to be more compact
- than JSON itself, yet more efficient to parse.
-
- The library uses the following mapping from JSON values types to
- UBJSON types according to the UBJSON specification:
-
- JSON value type | value/range | UBJSON type | marker
- --------------- | --------------------------------- | ----------- | ------
- null | `null` | null | `Z`
- boolean | `true` | true | `T`
- boolean | `false` | false | `F`
- number_integer | -9223372036854775808..-2147483649 | int64 | `L`
- number_integer | -2147483648..-32769 | int32 | `l`
- number_integer | -32768..-129 | int16 | `I`
- number_integer | -128..127 | int8 | `i`
- number_integer | 128..255 | uint8 | `U`
- number_integer | 256..32767 | int16 | `I`
- number_integer | 32768..2147483647 | int32 | `l`
- number_integer | 2147483648..9223372036854775807 | int64 | `L`
- number_unsigned | 0..127 | int8 | `i`
- number_unsigned | 128..255 | uint8 | `U`
- number_unsigned | 256..32767 | int16 | `I`
- number_unsigned | 32768..2147483647 | int32 | `l`
- number_unsigned | 2147483648..9223372036854775807 | int64 | `L`
- number_float | *any value* | float64 | `D`
- string | *with shortest length indicator* | string | `S`
- array | *see notes on optimized format* | array | `[`
- object | *see notes on optimized format* | map | `{`
-
- @note The mapping is **complete** in the sense that any JSON value type
- can be converted to a UBJSON value.
-
- @note The following values can **not** be converted to a UBJSON value:
- - strings with more than 9223372036854775807 bytes (theoretical)
- - unsigned integer numbers above 9223372036854775807
-
- @note The following markers are not used in the conversion:
- - `Z`: no-op values are not created.
- - `C`: single-byte strings are serialized with `S` markers.
-
- @note Any UBJSON output created @ref to_ubjson can be successfully parsed
- by @ref from_ubjson.
-
- @note If NaN or Infinity are stored inside a JSON number, they are
- serialized properly. This behavior differs from the @ref dump()
- function which serializes NaN or Infinity to `null`.
-
- @note The optimized formats for containers are supported: Parameter
- @a use_size adds size information to the beginning of a container and
- removes the closing marker. Parameter @a use_type further checks
- whether all elements of a container have the same type and adds the
- type marker to the beginning of the container. The @a use_type
- parameter must only be used together with @a use_size = true. Note
- that @a use_size = true alone may result in larger representations -
- the benefit of this parameter is that the receiving side is
- immediately informed on the number of elements of the container.
-
- @param[in] j JSON value to serialize
- @param[in] use_size whether to add size annotations to container types
- @param[in] use_type whether to add type annotations to container types
- (must be combined with @a use_size = true)
- @return UBJSON serialization as byte vector
-
- @complexity Linear in the size of the JSON value @a j.
-
- @liveexample{The example shows the serialization of a JSON value to a byte
- vector in UBJSON format.,to_ubjson}
-
- @sa http://ubjson.org
- @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the
- analogous deserialization
- @sa @ref to_cbor(const basic_json& for the related CBOR format
- @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
-
- @since version 3.1.0
- */
- static std::vector<uint8_t> to_ubjson(const basic_json& j,
- const bool use_size = false,
- const bool use_type = false)
- {
- std::vector<uint8_t> result;
- to_ubjson(j, result, use_size, use_type);
- return result;
- }
-
- static void to_ubjson(const basic_json& j, detail::output_adapter<uint8_t> o,
- const bool use_size = false, const bool use_type = false)
- {
- binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type);
- }
-
- static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
- const bool use_size = false, const bool use_type = false)
- {
- binary_writer<char>(o).write_ubjson(j, use_size, use_type);
- }
-
- /*!
- @brief create a JSON value from an input in CBOR format
-
- Deserializes a given input @a i to a JSON value using the CBOR (Concise
- Binary Object Representation) serialization format.
-
- The library maps CBOR types to JSON value types as follows:
-
- CBOR type | JSON value type | first byte
- ---------------------- | --------------- | ----------
- Integer | number_unsigned | 0x00..0x17
- Unsigned integer | number_unsigned | 0x18
- Unsigned integer | number_unsigned | 0x19
- Unsigned integer | number_unsigned | 0x1A
- Unsigned integer | number_unsigned | 0x1B
- Negative integer | number_integer | 0x20..0x37
- Negative integer | number_integer | 0x38
- Negative integer | number_integer | 0x39
- Negative integer | number_integer | 0x3A
- Negative integer | number_integer | 0x3B
- Negative integer | number_integer | 0x40..0x57
- UTF-8 string | string | 0x60..0x77
- UTF-8 string | string | 0x78
- UTF-8 string | string | 0x79
- UTF-8 string | string | 0x7A
- UTF-8 string | string | 0x7B
- UTF-8 string | string | 0x7F
- array | array | 0x80..0x97
- array | array | 0x98
- array | array | 0x99
- array | array | 0x9A
- array | array | 0x9B
- array | array | 0x9F
- map | object | 0xA0..0xB7
- map | object | 0xB8
- map | object | 0xB9
- map | object | 0xBA
- map | object | 0xBB
- map | object | 0xBF
- False | `false` | 0xF4
- True | `true` | 0xF5
- Nill | `null` | 0xF6
- Half-Precision Float | number_float | 0xF9
- Single-Precision Float | number_float | 0xFA
- Double-Precision Float | number_float | 0xFB
-
- @warning The mapping is **incomplete** in the sense that not all CBOR
- types can be converted to a JSON value. The following CBOR types
- are not supported and will yield parse errors (parse_error.112):
- - byte strings (0x40..0x5F)
- - date/time (0xC0..0xC1)
- - bignum (0xC2..0xC3)
- - decimal fraction (0xC4)
- - bigfloat (0xC5)
- - tagged items (0xC6..0xD4, 0xD8..0xDB)
- - expected conversions (0xD5..0xD7)
- - simple values (0xE0..0xF3, 0xF8)
- - undefined (0xF7)
-
- @warning CBOR allows map keys of any type, whereas JSON only allows
- strings as keys in object values. Therefore, CBOR maps with keys
- other than UTF-8 strings are rejected (parse_error.113).
-
- @note Any CBOR output created @ref to_cbor can be successfully parsed by
- @ref from_cbor.
-
- @param[in] i an input in CBOR format convertible to an input adapter
- @param[in] strict whether to expect the input to be consumed until EOF
- (true by default)
- @return deserialized JSON value
-
- @throw parse_error.110 if the given input ends prematurely or the end of
- file was not reached when @a strict was set to true
- @throw parse_error.112 if unsupported features from CBOR were
- used in the given input @a v or if the input is not valid CBOR
- @throw parse_error.113 if a string was expected as map key, but not found
-
- @complexity Linear in the size of the input @a i.
-
- @liveexample{The example shows the deserialization of a byte vector in CBOR
- format to a JSON value.,from_cbor}
-
- @sa http://cbor.io
- @sa @ref to_cbor(const basic_json&) for the analogous serialization
- @sa @ref from_msgpack(detail::input_adapter, const bool) for the
- related MessagePack format
- @sa @ref from_ubjson(detail::input_adapter, const bool) for the related
- UBJSON format
-
- @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
- consume input adapters, removed start_index parameter, and added
- @a strict parameter since 3.0.0
- */
- static basic_json from_cbor(detail::input_adapter i,
- const bool strict = true)
- {
- return binary_reader(i).parse_cbor(strict);
- }
-
- /*!
- @copydoc from_cbor(detail::input_adapter, const bool)
- */
- template<typename A1, typename A2,
- detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true)
- {
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict);
- }
-
- /*!
- @brief create a JSON value from an input in MessagePack format
-
- Deserializes a given input @a i to a JSON value using the MessagePack
- serialization format.
-
- The library maps MessagePack types to JSON value types as follows:
-
- MessagePack type | JSON value type | first byte
- ---------------- | --------------- | ----------
- positive fixint | number_unsigned | 0x00..0x7F
- fixmap | object | 0x80..0x8F
- fixarray | array | 0x90..0x9F
- fixstr | string | 0xA0..0xBF
- nil | `null` | 0xC0
- false | `false` | 0xC2
- true | `true` | 0xC3
- float 32 | number_float | 0xCA
- float 64 | number_float | 0xCB
- uint 8 | number_unsigned | 0xCC
- uint 16 | number_unsigned | 0xCD
- uint 32 | number_unsigned | 0xCE
- uint 64 | number_unsigned | 0xCF
- int 8 | number_integer | 0xD0
- int 16 | number_integer | 0xD1
- int 32 | number_integer | 0xD2
- int 64 | number_integer | 0xD3
- str 8 | string | 0xD9
- str 16 | string | 0xDA
- str 32 | string | 0xDB
- array 16 | array | 0xDC
- array 32 | array | 0xDD
- map 16 | object | 0xDE
- map 32 | object | 0xDF
- negative fixint | number_integer | 0xE0-0xFF
-
- @warning The mapping is **incomplete** in the sense that not all
- MessagePack types can be converted to a JSON value. The following
- MessagePack types are not supported and will yield parse errors:
- - bin 8 - bin 32 (0xC4..0xC6)
- - ext 8 - ext 32 (0xC7..0xC9)
- - fixext 1 - fixext 16 (0xD4..0xD8)
-
- @note Any MessagePack output created @ref to_msgpack can be successfully
- parsed by @ref from_msgpack.
-
- @param[in] i an input in MessagePack format convertible to an input
- adapter
- @param[in] strict whether to expect the input to be consumed until EOF
- (true by default)
-
- @throw parse_error.110 if the given input ends prematurely or the end of
- file was not reached when @a strict was set to true
- @throw parse_error.112 if unsupported features from MessagePack were
- used in the given input @a i or if the input is not valid MessagePack
- @throw parse_error.113 if a string was expected as map key, but not found
-
- @complexity Linear in the size of the input @a i.
-
- @liveexample{The example shows the deserialization of a byte vector in
- MessagePack format to a JSON value.,from_msgpack}
-
- @sa http://msgpack.org
- @sa @ref to_msgpack(const basic_json&) for the analogous serialization
- @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR
- format
- @sa @ref from_ubjson(detail::input_adapter, const bool) for the related
- UBJSON format
-
- @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
- consume input adapters, removed start_index parameter, and added
- @a strict parameter since 3.0.0
- */
- static basic_json from_msgpack(detail::input_adapter i,
- const bool strict = true)
- {
- return binary_reader(i).parse_msgpack(strict);
- }
-
- /*!
- @copydoc from_msgpack(detail::input_adapter, const bool)
- */
- template<typename A1, typename A2,
- detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true)
- {
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict);
- }
-
- /*!
- @brief create a JSON value from an input in UBJSON format
-
- Deserializes a given input @a i to a JSON value using the UBJSON (Universal
- Binary JSON) serialization format.
-
- The library maps UBJSON types to JSON value types as follows:
-
- UBJSON type | JSON value type | marker
- ----------- | --------------------------------------- | ------
- no-op | *no value, next value is read* | `N`
- null | `null` | `Z`
- false | `false` | `F`
- true | `true` | `T`
- float32 | number_float | `d`
- float64 | number_float | `D`
- uint8 | number_unsigned | `U`
- int8 | number_integer | `i`
- int16 | number_integer | `I`
- int32 | number_integer | `l`
- int64 | number_integer | `L`
- string | string | `S`
- char | string | `C`
- array | array (optimized values are supported) | `[`
- object | object (optimized values are supported) | `{`
-
- @note The mapping is **complete** in the sense that any UBJSON value can
- be converted to a JSON value.
-
- @param[in] i an input in UBJSON format convertible to an input adapter
- @param[in] strict whether to expect the input to be consumed until EOF
- (true by default)
-
- @throw parse_error.110 if the given input ends prematurely or the end of
- file was not reached when @a strict was set to true
- @throw parse_error.112 if a parse error occurs
- @throw parse_error.113 if a string could not be parsed successfully
-
- @complexity Linear in the size of the input @a i.
-
- @liveexample{The example shows the deserialization of a byte vector in
- UBJSON format to a JSON value.,from_ubjson}
-
- @sa http://ubjson.org
- @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
- analogous serialization
- @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR
- format
- @sa @ref from_msgpack(detail::input_adapter, const bool) for the related
- MessagePack format
-
- @since version 3.1.0
- */
- static basic_json from_ubjson(detail::input_adapter i,
- const bool strict = true)
- {
- return binary_reader(i).parse_ubjson(strict);
- }
-
- template<typename A1, typename A2,
- detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true)
- {
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_ubjson(strict);
- }
-
- /// @}
-
- //////////////////////////
- // JSON Pointer support //
- //////////////////////////
-
- /// @name JSON Pointer functions
- /// @{
-
- /*!
- @brief access specified element via JSON Pointer
-
- Uses a JSON pointer to retrieve a reference to the respective JSON value.
- No bound checking is performed. Similar to @ref operator[](const typename
- object_t::key_type&), `null` values are created in arrays and objects if
- necessary.
-
- In particular:
- - If the JSON pointer points to an object key that does not exist, it
- is created an filled with a `null` value before a reference to it
- is returned.
- - If the JSON pointer points to an array index that does not exist, it
- is created an filled with a `null` value before a reference to it
- is returned. All indices between the current maximum and the given
- index are also filled with `null`.
- - The special value `-` is treated as a synonym for the index past the
- end.
-
- @param[in] ptr a JSON pointer
-
- @return reference to the element pointed to by @a ptr
-
- @complexity Constant.
-
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.404 if the JSON pointer can not be resolved
-
- @liveexample{The behavior is shown in the example.,operatorjson_pointer}
-
- @since version 2.0.0
- */
- reference operator[](const json_pointer& ptr)
- {
- return ptr.get_unchecked(this);
- }
-
- /*!
- @brief access specified element via JSON Pointer
-
- Uses a JSON pointer to retrieve a reference to the respective JSON value.
- No bound checking is performed. The function does not change the JSON
- value; no `null` values are created. In particular, the the special value
- `-` yields an exception.
-
- @param[in] ptr JSON pointer to the desired element
-
- @return const reference to the element pointed to by @a ptr
-
- @complexity Constant.
-
- @throw parse_error.106 if an array index begins with '0'
- @throw parse_error.109 if an array index was not a number
- @throw out_of_range.402 if the array index '-' is used
- @throw out_of_range.404 if the JSON pointer can not be resolved
-
- @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
-
- @since version 2.0.0
- */
- const_reference operator[](const json_pointer& ptr) const
- {
- return ptr.get_unchecked(this);
- }
-
- /*!
- @brief access specified element via JSON Pointer
-
- Returns a reference to the element at with specified JSON pointer @a ptr,
- with bounds checking.
-
- @param[in] ptr JSON pointer to the desired element
-
- @return reference to the element pointed to by @a ptr
-
- @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
- begins with '0'. See example below.
-
- @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
- is not a number. See example below.
-
- @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
- is out of range. See example below.
-
- @throw out_of_range.402 if the array index '-' is used in the passed JSON
- pointer @a ptr. As `at` provides checked access (and no elements are
- implicitly inserted), the index '-' is always invalid. See example below.
-
- @throw out_of_range.403 if the JSON pointer describes a key of an object
- which cannot be found. See example below.
-
- @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
- See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @since version 2.0.0
-
- @liveexample{The behavior is shown in the example.,at_json_pointer}
- */
- reference at(const json_pointer& ptr)
- {
- return ptr.get_checked(this);
- }
-
- /*!
- @brief access specified element via JSON Pointer
-
- Returns a const reference to the element at with specified JSON pointer @a
- ptr, with bounds checking.
-
- @param[in] ptr JSON pointer to the desired element
-
- @return reference to the element pointed to by @a ptr
-
- @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
- begins with '0'. See example below.
-
- @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
- is not a number. See example below.
-
- @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
- is out of range. See example below.
-
- @throw out_of_range.402 if the array index '-' is used in the passed JSON
- pointer @a ptr. As `at` provides checked access (and no elements are
- implicitly inserted), the index '-' is always invalid. See example below.
-
- @throw out_of_range.403 if the JSON pointer describes a key of an object
- which cannot be found. See example below.
-
- @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
- See example below.
-
- @exceptionsafety Strong guarantee: if an exception is thrown, there are no
- changes in the JSON value.
-
- @complexity Constant.
-
- @since version 2.0.0
-
- @liveexample{The behavior is shown in the example.,at_json_pointer_const}
- */
- const_reference at(const json_pointer& ptr) const
- {
- return ptr.get_checked(this);
- }
-
- /*!
- @brief return flattened JSON value
-
- The function creates a JSON object whose keys are JSON pointers (see [RFC
- 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
- primitive. The original JSON value can be restored using the @ref
- unflatten() function.
-
- @return an object that maps JSON pointers to primitive values
-
- @note Empty objects and arrays are flattened to `null` and will not be
- reconstructed correctly by the @ref unflatten() function.
-
- @complexity Linear in the size the JSON value.
-
- @liveexample{The following code shows how a JSON object is flattened to an
- object whose keys consist of JSON pointers.,flatten}
-
- @sa @ref unflatten() for the reverse function
-
- @since version 2.0.0
- */
- basic_json flatten() const
- {
- basic_json result(value_t::object);
- json_pointer::flatten("", *this, result);
- return result;
- }
-
- /*!
- @brief unflatten a previously flattened JSON value
-
- The function restores the arbitrary nesting of a JSON value that has been
- flattened before using the @ref flatten() function. The JSON value must
- meet certain constraints:
- 1. The value must be an object.
- 2. The keys must be JSON pointers (see
- [RFC 6901](https://tools.ietf.org/html/rfc6901))
- 3. The mapped values must be primitive JSON types.
-
- @return the original JSON from a flattened version
-
- @note Empty objects and arrays are flattened by @ref flatten() to `null`
- values and can not unflattened to their original type. Apart from
- this example, for a JSON value `j`, the following is always true:
- `j == j.flatten().unflatten()`.
-
- @complexity Linear in the size the JSON value.
-
- @throw type_error.314 if value is not an object
- @throw type_error.315 if object values are not primitive
-
- @liveexample{The following code shows how a flattened JSON object is
- unflattened into the original nested JSON object.,unflatten}
-
- @sa @ref flatten() for the reverse function
-
- @since version 2.0.0
- */
- basic_json unflatten() const
- {
- return json_pointer::unflatten(*this);
- }
-
- /// @}
-
- //////////////////////////
- // JSON Patch functions //
- //////////////////////////
-
- /// @name JSON Patch functions
- /// @{
-
- /*!
- @brief applies a JSON patch
-
- [JSON Patch](http://jsonpatch.com) defines a JSON document structure for
- expressing a sequence of operations to apply to a JSON) document. With
- this function, a JSON Patch is applied to the current JSON value by
- executing all operations from the patch.
-
- @param[in] json_patch JSON patch document
- @return patched document
-
- @note The application of a patch is atomic: Either all operations succeed
- and the patched document is returned or an exception is thrown. In
- any case, the original value is not changed: the patch is applied
- to a copy of the value.
-
- @throw parse_error.104 if the JSON patch does not consist of an array of
- objects
-
- @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory
- attributes are missing); example: `"operation add must have member path"`
-
- @throw out_of_range.401 if an array index is out of range.
-
- @throw out_of_range.403 if a JSON pointer inside the patch could not be
- resolved successfully in the current JSON value; example: `"key baz not
- found"`
-
- @throw out_of_range.405 if JSON pointer has no parent ("add", "remove",
- "move")
-
- @throw other_error.501 if "test" operation was unsuccessful
-
- @complexity Linear in the size of the JSON value and the length of the
- JSON patch. As usually only a fraction of the JSON value is affected by
- the patch, the complexity can usually be neglected.
-
- @liveexample{The following code shows how a JSON patch is applied to a
- value.,patch}
-
- @sa @ref diff -- create a JSON patch by comparing two JSON values
-
- @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
- @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
-
- @since version 2.0.0
- */
- basic_json patch(const basic_json& json_patch) const
- {
- // make a working copy to apply the patch to
- basic_json result = *this;
-
- // the valid JSON Patch operations
- enum class patch_operations {add, remove, replace, move, copy, test, invalid};
-
- const auto get_op = [](const std::string & op)
- {
- if (op == "add")
- {
- return patch_operations::add;
- }
- if (op == "remove")
- {
- return patch_operations::remove;
- }
- if (op == "replace")
- {
- return patch_operations::replace;
- }
- if (op == "move")
- {
- return patch_operations::move;
- }
- if (op == "copy")
- {
- return patch_operations::copy;
- }
- if (op == "test")
- {
- return patch_operations::test;
- }
-
- return patch_operations::invalid;
- };
-
- // wrapper for "add" operation; add value at ptr
- const auto operation_add = [&result](json_pointer & ptr, basic_json val)
- {
- // adding to the root of the target document means replacing it
- if (ptr.is_root())
- {
- result = val;
- }
- else
- {
- // make sure the top element of the pointer exists
- json_pointer top_pointer = ptr.top();
- if (top_pointer != ptr)
- {
- result.at(top_pointer);
- }
-
- // get reference to parent of JSON pointer ptr
- const auto last_path = ptr.pop_back();
- basic_json& parent = result[ptr];
-
- switch (parent.m_type)
- {
- case value_t::null:
- case value_t::object:
- {
- // use operator[] to add value
- parent[last_path] = val;
- break;
- }
-
- case value_t::array:
- {
- if (last_path == "-")
- {
- // special case: append to back
- parent.push_back(val);
- }
- else
- {
- const auto idx = json_pointer::array_index(last_path);
- if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
- {
- // avoid undefined behavior
- JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
- }
- else
- {
- // default case: insert add offset
- parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
- }
- }
- break;
- }
-
- default:
- {
- // if there exists a parent it cannot be primitive
- assert(false); // LCOV_EXCL_LINE
- }
- }
- }
- };
-
- // wrapper for "remove" operation; remove value at ptr
- const auto operation_remove = [&result](json_pointer & ptr)
- {
- // get reference to parent of JSON pointer ptr
- const auto last_path = ptr.pop_back();
- basic_json& parent = result.at(ptr);
-
- // remove child
- if (parent.is_object())
- {
- // perform range check
- auto it = parent.find(last_path);
- if (JSON_LIKELY(it != parent.end()))
- {
- parent.erase(it);
- }
- else
- {
- JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found"));
- }
- }
- else if (parent.is_array())
- {
- // note erase performs range check
- parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
- }
- };
-
- // type check: top level value must be an array
- if (JSON_UNLIKELY(not json_patch.is_array()))
- {
- JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
- }
-
- // iterate and apply the operations
- for (const auto& val : json_patch)
- {
- // wrapper to get a value for an operation
- const auto get_value = [&val](const std::string & op,
- const std::string & member,
- bool string_type) -> basic_json &
- {
- // find value
- auto it = val.m_value.object->find(member);
-
- // context-sensitive error message
- const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
-
- // check if desired value is present
- if (JSON_UNLIKELY(it == val.m_value.object->end()))
- {
- JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'"));
- }
-
- // check if result is of type string
- if (JSON_UNLIKELY(string_type and not it->second.is_string()))
- {
- JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'"));
- }
-
- // no error: return value
- return it->second;
- };
-
- // type check: every element of the array must be an object
- if (JSON_UNLIKELY(not val.is_object()))
- {
- JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
- }
-
- // collect mandatory members
- const std::string op = get_value("op", "op", true);
- const std::string path = get_value(op, "path", true);
- json_pointer ptr(path);
-
- switch (get_op(op))
- {
- case patch_operations::add:
- {
- operation_add(ptr, get_value("add", "value", false));
- break;
- }
-
- case patch_operations::remove:
- {
- operation_remove(ptr);
- break;
- }
-
- case patch_operations::replace:
- {
- // the "path" location must exist - use at()
- result.at(ptr) = get_value("replace", "value", false);
- break;
- }
-
- case patch_operations::move:
- {
- const std::string from_path = get_value("move", "from", true);
- json_pointer from_ptr(from_path);
-
- // the "from" location must exist - use at()
- basic_json v = result.at(from_ptr);
-
- // The move operation is functionally identical to a
- // "remove" operation on the "from" location, followed
- // immediately by an "add" operation at the target
- // location with the value that was just removed.
- operation_remove(from_ptr);
- operation_add(ptr, v);
- break;
- }
-
- case patch_operations::copy:
- {
- const std::string from_path = get_value("copy", "from", true);
- const json_pointer from_ptr(from_path);
-
- // the "from" location must exist - use at()
- basic_json v = result.at(from_ptr);
-
- // The copy is functionally identical to an "add"
- // operation at the target location using the value
- // specified in the "from" member.
- operation_add(ptr, v);
- break;
- }
-
- case patch_operations::test:
- {
- bool success = false;
- JSON_TRY
- {
- // check if "value" matches the one at "path"
- // the "path" location must exist - use at()
- success = (result.at(ptr) == get_value("test", "value", false));
- }
- JSON_CATCH (out_of_range&)
- {
- // ignore out of range errors: success remains false
- }
-
- // throw an exception if test fails
- if (JSON_UNLIKELY(not success))
- {
- JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump()));
- }
-
- break;
- }
-
- case patch_operations::invalid:
- {
- // op must be "add", "remove", "replace", "move", "copy", or
- // "test"
- JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid"));
- }
- }
- }
-
- return result;
- }
-
- /*!
- @brief creates a diff as a JSON patch
-
- Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
- be changed into the value @a target by calling @ref patch function.
-
- @invariant For two JSON values @a source and @a target, the following code
- yields always `true`:
- @code {.cpp}
- source.patch(diff(source, target)) == target;
- @endcode
-
- @note Currently, only `remove`, `add`, and `replace` operations are
- generated.
-
- @param[in] source JSON value to compare from
- @param[in] target JSON value to compare against
- @param[in] path helper value to create JSON pointers
-
- @return a JSON patch to convert the @a source to @a target
-
- @complexity Linear in the lengths of @a source and @a target.
-
- @liveexample{The following code shows how a JSON patch is created as a
- diff for two JSON values.,diff}
-
- @sa @ref patch -- apply a JSON patch
- @sa @ref merge_patch -- apply a JSON Merge Patch
-
- @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
-
- @since version 2.0.0
- */
- static basic_json diff(const basic_json& source, const basic_json& target,
- const std::string& path = "")
- {
- // the patch
- basic_json result(value_t::array);
-
- // if the values are the same, return empty patch
- if (source == target)
- {
- return result;
- }
-
- if (source.type() != target.type())
- {
- // different types: replace value
- result.push_back(
- {
- {"op", "replace"}, {"path", path}, {"value", target}
- });
- }
- else
- {
- switch (source.type())
- {
- case value_t::array:
- {
- // first pass: traverse common elements
- std::size_t i = 0;
- while (i < source.size() and i < target.size())
- {
- // recursive call to compare array values at index i
- auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
- result.insert(result.end(), temp_diff.begin(), temp_diff.end());
- ++i;
- }
-
- // i now reached the end of at least one array
- // in a second pass, traverse the remaining elements
-
- // remove my remaining elements
- const auto end_index = static_cast<difference_type>(result.size());
- while (i < source.size())
- {
- // add operations in reverse order to avoid invalid
- // indices
- result.insert(result.begin() + end_index, object(
- {
- {"op", "remove"},
- {"path", path + "/" + std::to_string(i)}
- }));
- ++i;
- }
-
- // add other remaining elements
- while (i < target.size())
- {
- result.push_back(
- {
- {"op", "add"},
- {"path", path + "/" + std::to_string(i)},
- {"value", target[i]}
- });
- ++i;
- }
-
- break;
- }
-
- case value_t::object:
- {
- // first pass: traverse this object's elements
- for (auto it = source.cbegin(); it != source.cend(); ++it)
- {
- // escape the key name to be used in a JSON patch
- const auto key = json_pointer::escape(it.key());
-
- if (target.find(it.key()) != target.end())
- {
- // recursive call to compare object values at key it
- auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
- result.insert(result.end(), temp_diff.begin(), temp_diff.end());
- }
- else
- {
- // found a key that is not in o -> remove it
- result.push_back(object(
- {
- {"op", "remove"}, {"path", path + "/" + key}
- }));
- }
- }
-
- // second pass: traverse other object's elements
- for (auto it = target.cbegin(); it != target.cend(); ++it)
- {
- if (source.find(it.key()) == source.end())
- {
- // found a key that is not in this -> add it
- const auto key = json_pointer::escape(it.key());
- result.push_back(
- {
- {"op", "add"}, {"path", path + "/" + key},
- {"value", it.value()}
- });
- }
- }
-
- break;
- }
-
- default:
- {
- // both primitive type: replace value
- result.push_back(
- {
- {"op", "replace"}, {"path", path}, {"value", target}
- });
- break;
- }
- }
- }
-
- return result;
- }
-
- /// @}
-
- ////////////////////////////////
- // JSON Merge Patch functions //
- ////////////////////////////////
-
- /// @name JSON Merge Patch functions
- /// @{
-
- /*!
- @brief applies a JSON Merge Patch
-
- The merge patch format is primarily intended for use with the HTTP PATCH
- method as a means of describing a set of modifications to a target
- resource's content. This function applies a merge patch to the current
- JSON value.
-
- The function implements the following algorithm from Section 2 of
- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396):
-
- ```
- define MergePatch(Target, Patch):
- if Patch is an Object:
- if Target is not an Object:
- Target = {} // Ignore the contents and set it to an empty Object
- for each Name/Value pair in Patch:
- if Value is null:
- if Name exists in Target:
- remove the Name/Value pair from Target
- else:
- Target[Name] = MergePatch(Target[Name], Value)
- return Target
- else:
- return Patch
- ```
-
- Thereby, `Target` is the current object; that is, the patch is applied to
- the current value.
-
- @param[in] patch the patch to apply
-
- @complexity Linear in the lengths of @a patch.
-
- @liveexample{The following code shows how a JSON Merge Patch is applied to
- a JSON document.,merge_patch}
-
- @sa @ref patch -- apply a JSON patch
- @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
-
- @since version 3.0.0
- */
- void merge_patch(const basic_json& patch)
- {
- if (patch.is_object())
- {
- if (not is_object())
- {
- *this = object();
- }
- for (auto it = patch.begin(); it != patch.end(); ++it)
- {
- if (it.value().is_null())
- {
- erase(it.key());
- }
- else
- {
- operator[](it.key()).merge_patch(it.value());
- }
- }
- }
- else
- {
- *this = patch;
- }
- }
-
- /// @}
-};
-} // namespace nlohmann
-
-///////////////////////
-// nonmember support //
-///////////////////////
-
-// specialization of std::swap, and std::hash
-namespace std
-{
-/*!
-@brief exchanges the values of two JSON objects
-
-@since version 1.0.0
-*/
-template<>
-inline void swap(nlohmann::json& j1,
- nlohmann::json& j2) noexcept(
- is_nothrow_move_constructible<nlohmann::json>::value and
- is_nothrow_move_assignable<nlohmann::json>::value
- )
-{
- j1.swap(j2);
-}
-
-/// hash value for JSON objects
-template<>
-struct hash<nlohmann::json>
-{
- /*!
- @brief return a hash value for a JSON object
-
- @since version 1.0.0
- */
- std::size_t operator()(const nlohmann::json& j) const
- {
- // a naive hashing via the string representation
- const auto& h = hash<nlohmann::json::string_t>();
- return h(j.dump());
- }
-};
-
-/// specialization for std::less<value_t>
-/// @note: do not remove the space after '<',
-/// see https://github.com/nlohmann/json/pull/679
-template<>
-struct less< ::nlohmann::detail::value_t>
-{
- /*!
- @brief compare two value_t enum values
- @since version 3.0.0
- */
- bool operator()(nlohmann::detail::value_t lhs,
- nlohmann::detail::value_t rhs) const noexcept
- {
- return nlohmann::detail::operator<(lhs, rhs);
- }
-};
-
-} // namespace std
-
-/*!
-@brief user-defined string literal for JSON values
-
-This operator implements a user-defined string literal for JSON objects. It
-can be used by adding `"_json"` to a string literal and returns a JSON object
-if no parse error occurred.
-
-@param[in] s a string representation of a JSON object
-@param[in] n the length of string @a s
-@return a JSON object
-
-@since version 1.0.0
-*/
-inline nlohmann::json operator "" _json(const char* s, std::size_t n)
-{
- return nlohmann::json::parse(s, s + n);
-}
-
-/*!
-@brief user-defined string literal for JSON pointer
-
-This operator implements a user-defined string literal for JSON Pointers. It
-can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer
-object if no parse error occurred.
-
-@param[in] s a string representation of a JSON Pointer
-@param[in] n the length of string @a s
-@return a JSON pointer object
-
-@since version 2.0.0
-*/
-inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
-{
- return nlohmann::json::json_pointer(std::string(s, n));
-}
-
-// #include <nlohmann/detail/macro_unscope.hpp>
-
-
-// restore GCC/clang diagnostic settings
-#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
- #pragma GCC diagnostic pop
-#endif
-#if defined(__clang__)
- #pragma GCC diagnostic pop
-#endif
-
-// clean up
-#undef JSON_CATCH
-#undef JSON_THROW
-#undef JSON_TRY
-#undef JSON_LIKELY
-#undef JSON_UNLIKELY
-#undef JSON_DEPRECATED
-#undef JSON_HAS_CPP_14
-#undef JSON_HAS_CPP_17
-#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION
-#undef NLOHMANN_BASIC_JSON_TPL
-#undef NLOHMANN_JSON_HAS_HELPER
-
-
-#endif
diff --git a/externals/libressl b/externals/libressl
-Subproject 7d01cb01cb1a926ecb4c9c98b107ef3c26f59df
+Subproject 8289d0d07de6553bf4b900bf60e808ea3f7f59d
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
new file mode 100644
index 000000000..c0d24b126
--- /dev/null
+++ b/externals/libusb/CMakeLists.txt
@@ -0,0 +1,150 @@
+add_library(usb STATIC EXCLUDE_FROM_ALL
+ libusb/libusb/core.c
+ libusb/libusb/core.c
+ libusb/libusb/descriptor.c
+ libusb/libusb/hotplug.c
+ libusb/libusb/io.c
+ libusb/libusb/strerror.c
+ libusb/libusb/sync.c
+)
+set_target_properties(usb PROPERTIES VERSION 1.0.23)
+if(WIN32)
+ target_include_directories(usb
+ BEFORE
+ PUBLIC
+ libusb/libusb
+
+ PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ )
+
+ if (NOT MINGW)
+ target_include_directories(usb BEFORE PRIVATE libusb/msvc)
+ endif()
+
+ # Works around other libraries providing their own definition of USB GUIDs (e.g. SDL2)
+ target_compile_definitions(usb PRIVATE "-DGUID_DEVINTERFACE_USB_DEVICE=(GUID){ 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}")
+else()
+target_include_directories(usb
+ # turns out other projects also have "config.h", so make sure the
+ # LibUSB one comes first
+ BEFORE
+
+ PUBLIC
+ libusb/libusb
+
+ PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}"
+)
+endif()
+
+if(WIN32 OR CYGWIN)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/threads_windows.c
+ libusb/libusb/os/windows_winusb.c
+ libusb/libusb/os/windows_usbdk.c
+ libusb/libusb/os/windows_nt_common.c
+ )
+ set(OS_WINDOWS TRUE)
+elseif(APPLE)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/darwin_usb.c
+ )
+ find_library(COREFOUNDATION_LIBRARY CoreFoundation)
+ find_library(IOKIT_LIBRARY IOKit)
+ find_library(OBJC_LIBRARY objc)
+ target_link_libraries(usb PRIVATE
+ ${COREFOUNDATION_LIBRARY}
+ ${IOKIT_LIBRARY}
+ ${OBJC_LIBRARY}
+ )
+ set(OS_DARWIN TRUE)
+elseif(ANDROID)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_usbfs.c
+ libusb/libusb/os/linux_netlink.c
+ )
+ find_library(LOG_LIBRARY log)
+ target_link_libraries(usb PRIVATE ${LOG_LIBRARY})
+ set(OS_LINUX TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_usbfs.c
+ )
+ find_package(Libudev)
+ if(LIBUDEV_FOUND)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_udev.c
+ )
+ target_link_libraries(usb PRIVATE "${LIBUDEV_LIBRARIES}")
+ target_include_directories(usb PRIVATE "${LIBUDEV_INCLUDE_DIR}")
+ set(HAVE_LIBUDEV TRUE)
+ set(USE_UDEV TRUE)
+ else()
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_netlink.c
+ )
+ endif()
+ set(OS_LINUX TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/netbsd_usb.c
+ )
+ set(OS_NETBSD TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/openbsd_usb.c
+ )
+ set(OS_OPENBSD TRUE)
+endif()
+
+if(UNIX)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/poll_posix.c
+ libusb/libusb/os/threads_posix.c
+ )
+ find_package(Threads REQUIRED)
+ if(THREADS_HAVE_PTHREAD_ARG)
+ target_compile_options(usb PUBLIC "-pthread")
+ endif()
+ if(CMAKE_THREAD_LIBS_INIT)
+ target_link_libraries(usb PRIVATE "${CMAKE_THREAD_LIBS_INIT}")
+ endif()
+ set(THREADS_POSIX TRUE)
+elseif(WIN32)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/poll_windows.c
+ libusb/libusb/os/threads_windows.c
+ )
+endif()
+
+include(CheckFunctionExists)
+include(CheckIncludeFiles)
+include(CheckTypeSize)
+check_include_files(asm/types.h HAVE_ASM_TYPES_H)
+check_function_exists(gettimeofday HAVE_GETTIMEOFDAY)
+check_include_files(linux/filter.h HAVE_LINUX_FILTER_H)
+check_include_files(linux/netlink.h HAVE_LINUX_NETLINK_H)
+check_include_files(poll.h HAVE_POLL_H)
+check_include_files(signal.h HAVE_SIGNAL_H)
+check_include_files(strings.h HAVE_STRINGS_H)
+check_type_size("struct timespec" STRUCT_TIMESPEC)
+check_function_exists(syslog HAVE_SYSLOG_FUNC)
+check_include_files(syslog.h HAVE_SYSLOG_H)
+check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
+check_include_files(sys/time.h HAVE_SYS_TIME_H)
+check_include_files(sys/types.h HAVE_SYS_TYPES_H)
+
+set(CMAKE_EXTRA_INCLUDE_FILES poll.h)
+check_type_size("nfds_t" nfds_t)
+unset(CMAKE_EXTRA_INCLUDE_FILES)
+if(HAVE_NFDS_T)
+ set(POLL_NFDS_TYPE "nfds_t")
+else()
+ set(POLL_NFDS_TYPE "unsigned int")
+endif()
+
+check_include_files(sys/timerfd.h USBI_TIMERFD_AVAILABLE)
+
+
+configure_file(config.h.in config.h)
diff --git a/externals/libusb/config.h.in b/externals/libusb/config.h.in
new file mode 100644
index 000000000..915b7390f
--- /dev/null
+++ b/externals/libusb/config.h.in
@@ -0,0 +1,90 @@
+/* Default visibility */
+#if defined(__GNUC__) || defined(__clang__)
+ #define DEFAULT_VISIBILITY __attribute__((visibility("default")))
+#elif defined(_MSC_VER)
+ #define DEFAULT_VISIBILITY __declspec(dllexport)
+#endif
+
+/* Start with debug message logging enabled */
+#undef ENABLE_DEBUG_LOGGING
+
+/* Message logging */
+#undef ENABLE_LOGGING
+
+/* Define to 1 if you have the <asm/types.h> header file. */
+#cmakedefine HAVE_ASM_TYPES_H 1
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#cmakedefine HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the `udev' library (-ludev). */
+#cmakedefine HAVE_LIBUDEV 1
+
+/* Define to 1 if you have the <linux/filter.h> header file. */
+#cmakedefine HAVE_LINUX_FILTER_H 1
+
+/* Define to 1 if you have the <linux/netlink.h> header file. */
+#cmakedefine HAVE_LINUX_NETLINK_H 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#cmakedefine HAVE_POLL_H 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#cmakedefine HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#cmakedefine HAVE_STRINGS_H 1
+
+/* Define to 1 if the system has the type `struct timespec'. */
+#cmakedefine HAVE_STRUCT_TIMESPEC 1
+
+/* syslog() function available */
+#cmakedefine HAVE_SYSLOG_FUNC 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#cmakedefine HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#cmakedefine HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#cmakedefine HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#cmakedefine HAVE_SYS_TYPES_H 1
+
+/* Darwin backend */
+#cmakedefine OS_DARWIN 1
+
+/* Linux backend */
+#cmakedefine OS_LINUX 1
+
+/* NetBSD backend */
+#cmakedefine OS_NETBSD 1
+
+/* OpenBSD backend */
+#cmakedefine OS_OPENBSD 1
+
+/* Windows backend */
+#cmakedefine OS_WINDOWS 1
+
+/* type of second poll() argument */
+#define POLL_NFDS_TYPE @POLL_NFDS_TYPE@
+
+/* Use POSIX Threads */
+#cmakedefine THREADS_POSIX
+
+/* timerfd headers available */
+#cmakedefine USBI_TIMERFD_AVAILABLE 1
+
+/* Enable output to system log */
+#define USE_SYSTEM_LOGGING_FACILITY 1
+
+/* Use udev for device enumeration/hotplug */
+#cmakedefine USE_UDEV 1
+
+/* Use GNU extensions */
+#define _GNU_SOURCE
+
+/* Oldest Windows version supported */
+#define WINVER 0x0501
diff --git a/externals/libusb/libusb b/externals/libusb/libusb
new file mode 160000
+Subproject e782eeb2514266f6738e242cdcb18e3ae1ed06f
diff --git a/externals/lurlparser/CMakeLists.txt b/externals/lurlparser/CMakeLists.txt
deleted file mode 100644
index 45046ffd3..000000000
--- a/externals/lurlparser/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-add_library(lurlparser
- LUrlParser.cpp
- LUrlParser.h
-)
-
-create_target_directory_groups(lurlparser)
-
-target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/externals/lurlparser/LUrlParser.cpp b/externals/lurlparser/LUrlParser.cpp
deleted file mode 100644
index 9c134e330..000000000
--- a/externals/lurlparser/LUrlParser.cpp
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Lightweight URL & URI parser (RFC 1738, RFC 3986)
- * https://github.com/corporateshark/LUrlParser
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "LUrlParser.h"
-
-#include <algorithm>
-#include <cstring>
-#include <stdlib.h>
-
-// check if the scheme name is valid
-static bool IsSchemeValid( const std::string& SchemeName )
-{
- for ( auto c : SchemeName )
- {
- if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
- }
-
- return true;
-}
-
-bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
-{
- if ( !IsValid() ) { return false; }
-
- int Port = atoi( m_Port.c_str() );
-
- if ( Port <= 0 || Port > 65535 ) { return false; }
-
- if ( OutPort ) { *OutPort = Port; }
-
- return true;
-}
-
-// based on RFC 1738 and RFC 3986
-LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
-{
- LUrlParser::clParseURL Result;
-
- const char* CurrentString = URL.c_str();
-
- /*
- * <scheme>:<scheme-specific-part>
- * <scheme> := [a-z\+\-\.]+
- * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
- */
-
- // try to read scheme
- {
- const char* LocalString = strchr( CurrentString, ':' );
-
- if ( !LocalString )
- {
- return clParseURL( LUrlParserError_NoUrlCharacter );
- }
-
- // save the scheme name
- Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
-
- if ( !IsSchemeValid( Result.m_Scheme ) )
- {
- return clParseURL( LUrlParserError_InvalidSchemeName );
- }
-
- // scheme should be lowercase
- std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
-
- // skip ':'
- CurrentString = LocalString+1;
- }
-
- /*
- * //<user>:<password>@<host>:<port>/<url-path>
- * any ":", "@" and "/" must be normalized
- */
-
- // skip "//"
- if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
- if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
-
- // check if the user name and password are specified
- bool bHasUserName = false;
-
- const char* LocalString = CurrentString;
-
- while ( *LocalString )
- {
- if ( *LocalString == '@' )
- {
- // user name and password are specified
- bHasUserName = true;
- break;
- }
- else if ( *LocalString == '/' )
- {
- // end of <host>:<port> specification
- bHasUserName = false;
- break;
- }
-
- LocalString++;
- }
-
- // user name and password
- LocalString = CurrentString;
-
- if ( bHasUserName )
- {
- // read user name
- while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
-
- Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
-
- // proceed with the current pointer
- CurrentString = LocalString;
-
- if ( *CurrentString == ':' )
- {
- // skip ':'
- CurrentString++;
-
- // read password
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '@' ) LocalString++;
-
- Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // skip '@'
- if ( *CurrentString != '@' )
- {
- return clParseURL( LUrlParserError_NoAtSign );
- }
-
- CurrentString++;
- }
-
- bool bHasBracket = ( *CurrentString == '[' );
-
- // go ahead, read the host name
- LocalString = CurrentString;
-
- while ( *LocalString )
- {
- if ( bHasBracket && *LocalString == ']' )
- {
- // end of IPv6 address
- LocalString++;
- break;
- }
- else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
- {
- // port number is specified
- break;
- }
-
- LocalString++;
- }
-
- Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
-
- // is port number specified?
- if ( *CurrentString == ':' )
- {
- CurrentString++;
-
- // read port number
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '/' ) LocalString++;
-
- Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // end of string
- if ( !*CurrentString )
- {
- Result.m_ErrorCode = LUrlParserError_Ok;
-
- return Result;
- }
-
- // skip '/'
- if ( *CurrentString != '/' )
- {
- return clParseURL( LUrlParserError_NoSlash );
- }
-
- CurrentString++;
-
- // parse the path
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
-
- Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
-
- // check for query
- if ( *CurrentString == '?' )
- {
- // skip '?'
- CurrentString++;
-
- // read query
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '#' ) LocalString++;
-
- Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // check for fragment
- if ( *CurrentString == '#' )
- {
- // skip '#'
- CurrentString++;
-
- // read fragment
- LocalString = CurrentString;
-
- while ( *LocalString ) LocalString++;
-
- Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- Result.m_ErrorCode = LUrlParserError_Ok;
-
- return Result;
-}
diff --git a/externals/lurlparser/LUrlParser.h b/externals/lurlparser/LUrlParser.h
deleted file mode 100644
index 25d210981..000000000
--- a/externals/lurlparser/LUrlParser.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Lightweight URL & URI parser (RFC 1738, RFC 3986)
- * https://github.com/corporateshark/LUrlParser
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#pragma once
-
-#include <string>
-
-namespace LUrlParser
-{
-enum LUrlParserError
-{
- LUrlParserError_Ok = 0,
- LUrlParserError_Uninitialized = 1,
- LUrlParserError_NoUrlCharacter = 2,
- LUrlParserError_InvalidSchemeName = 3,
- LUrlParserError_NoDoubleSlash = 4,
- LUrlParserError_NoAtSign = 5,
- LUrlParserError_UnexpectedEndOfLine = 6,
- LUrlParserError_NoSlash = 7,
-};
-
-class clParseURL
-{
-public:
- LUrlParserError m_ErrorCode;
- std::string m_Scheme;
- std::string m_Host;
- std::string m_Port;
- std::string m_Path;
- std::string m_Query;
- std::string m_Fragment;
- std::string m_UserName;
- std::string m_Password;
-
- clParseURL()
- : m_ErrorCode( LUrlParserError_Uninitialized )
- {}
-
- /// return 'true' if the parsing was successful
- bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
-
- /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
- bool GetPort( int* OutPort ) const;
-
- /// parse the URL
- static clParseURL ParseURL( const std::string& URL );
-
-private:
- explicit clParseURL( LUrlParserError ErrorCode )
- : m_ErrorCode( ErrorCode )
- {}
-};
-
-} // namespace LUrlParser
diff --git a/externals/lurlparser/README.md b/externals/lurlparser/README.md
deleted file mode 100644
index be7f0135a..000000000
--- a/externals/lurlparser/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
-
-MIT License
-
-===
-
-Lightweight URL & URI parser (RFC 1738, RFC 3986)
-
-(C) Sergey Kosarevsky, 2015
-
-@corporateshark sk@linderdaum.com
-
-http://www.linderdaum.com
-
-http://blog.linderdaum.com
-
-=============================
-
-A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.
diff --git a/externals/lz4 b/externals/lz4
deleted file mode 160000
-Subproject 4db65c1d99280a757823914efaa96d2e87f4184
diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h
index 9d830f7bf..a06f6457d 100644
--- a/externals/microprofile/microprofile.h
+++ b/externals/microprofile/microprofile.h
@@ -152,9 +152,11 @@ typedef uint16_t MicroProfileGroupId;
#include <stdint.h>
#include <string.h>
-#include <thread>
-#include <mutex>
+#include <algorithm>
+#include <array>
#include <atomic>
+#include <mutex>
+#include <thread>
#ifndef MICROPROFILE_API
#define MICROPROFILE_API
@@ -605,28 +607,45 @@ struct MicroProfileFrameState
struct MicroProfileThreadLog
{
- MicroProfileLogEntry Log[MICROPROFILE_BUFFER_SIZE];
+ std::array<MicroProfileLogEntry, MICROPROFILE_BUFFER_SIZE> Log{};
- std::atomic<uint32_t> nPut;
- std::atomic<uint32_t> nGet;
- uint32_t nActive;
- uint32_t nGpu;
- ThreadIdType nThreadId;
+ std::atomic<uint32_t> nPut{0};
+ std::atomic<uint32_t> nGet{0};
+ uint32_t nActive = 0;
+ uint32_t nGpu = 0;
+ ThreadIdType nThreadId{};
- uint32_t nStack[MICROPROFILE_STACK_MAX];
- int64_t nChildTickStack[MICROPROFILE_STACK_MAX];
- uint32_t nStackPos;
+ std::array<uint32_t, MICROPROFILE_STACK_MAX> nStack{};
+ std::array<int64_t, MICROPROFILE_STACK_MAX> nChildTickStack{};
+ uint32_t nStackPos = 0;
- uint8_t nGroupStackPos[MICROPROFILE_MAX_GROUPS];
- int64_t nGroupTicks[MICROPROFILE_MAX_GROUPS];
- int64_t nAggregateGroupTicks[MICROPROFILE_MAX_GROUPS];
+ std::array<uint8_t, MICROPROFILE_MAX_GROUPS> nGroupStackPos{};
+ std::array<int64_t, MICROPROFILE_MAX_GROUPS> nGroupTicks{};
+ std::array<int64_t, MICROPROFILE_MAX_GROUPS> nAggregateGroupTicks{};
enum
{
THREAD_MAX_LEN = 64,
};
- char ThreadName[64];
- int nFreeListNext;
+ char ThreadName[64]{};
+ int nFreeListNext = 0;
+
+ void Reset() {
+ Log.fill({});
+ nPut = 0;
+ nGet = 0;
+ nActive = 0;
+ nGpu = 0;
+ nThreadId = {};
+ nStack.fill(0);
+ nChildTickStack.fill(0);
+ nStackPos = 0;
+ nGroupStackPos.fill(0);
+ nGroupTicks.fill(0);
+ nAggregateGroupTicks.fill(0);
+ std::fill(std::begin(ThreadName), std::end(ThreadName), '\0');
+ nFreeListNext = 0;
+ }
};
#if MICROPROFILE_GPU_TIMERS_D3D11
@@ -883,8 +902,10 @@ inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t)
#include <windows.h>
#define snprintf _snprintf
+#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
+#endif
int64_t MicroProfileTicksPerSecondCpu()
{
static int64_t nTicksPerSecond = 0;
@@ -910,14 +931,14 @@ typedef void* (*MicroProfileThreadFunc)(void*);
#ifndef _WIN32
typedef pthread_t MicroProfileThread;
-void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
+inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
{
pthread_attr_t Attr;
int r = pthread_attr_init(&Attr);
MP_ASSERT(r == 0);
pthread_create(pThread, &Attr, Func, 0);
}
-void MicroProfileThreadJoin(MicroProfileThread* pThread)
+inline void MicroProfileThreadJoin(MicroProfileThread* pThread)
{
int r = pthread_join(*pThread, 0);
MP_ASSERT(r == 0);
@@ -927,14 +948,18 @@ typedef HANDLE MicroProfileThread;
DWORD _stdcall ThreadTrampoline(void* pFunc)
{
MicroProfileThreadFunc F = (MicroProfileThreadFunc)pFunc;
- return (uint32_t)F(0);
+
+ // The return value of F will always return a void*, however, this is for
+ // compatibility with pthreads. The underlying "address" of the pointer
+ // is always a 32-bit value, so this cast is safe to perform.
+ return static_cast<DWORD>(reinterpret_cast<uint64_t>(F(0)));
}
-void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
+inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
{
*pThread = CreateThread(0, 0, ThreadTrampoline, Func, 0, 0);
}
-void MicroProfileThreadJoin(MicroProfileThread* pThread)
+inline void MicroProfileThreadJoin(MicroProfileThread* pThread)
{
WaitForSingleObject(*pThread, INFINITE);
CloseHandle(*pThread);
@@ -1018,7 +1043,7 @@ static void MicroProfileCreateThreadLogKey()
#else
MP_THREAD_LOCAL MicroProfileThreadLog* g_MicroProfileThreadLog = 0;
#endif
-static bool g_bUseLock = false; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
+static std::atomic<bool> g_bUseLock{false}; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
MICROPROFILE_DEFINE(g_MicroProfileFlip, "MicroProfile", "MicroProfileFlip", 0x3355ee);
@@ -1131,7 +1156,7 @@ inline void MicroProfileSetThreadLog(MicroProfileThreadLog* pLog)
pthread_setspecific(g_MicroProfileThreadLogKey, pLog);
}
#else
-MicroProfileThreadLog* MicroProfileGetThreadLog()
+inline MicroProfileThreadLog* MicroProfileGetThreadLog()
{
return g_MicroProfileThreadLog;
}
@@ -1151,6 +1176,7 @@ MicroProfileThreadLog* MicroProfileCreateThreadLog(const char* pName)
MP_ASSERT(pLog->nPut.load() == 0);
MP_ASSERT(pLog->nGet.load() == 0);
S.nFreeListHead = S.Pool[S.nFreeListHead]->nFreeListNext;
+ pLog->Reset();
}
else
{
@@ -1158,7 +1184,6 @@ MicroProfileThreadLog* MicroProfileCreateThreadLog(const char* pName)
S.nMemUsage += sizeof(MicroProfileThreadLog);
S.Pool[S.nNumLogs++] = pLog;
}
- memset(pLog, 0, sizeof(*pLog));
int len = (int)strlen(pName);
int maxlen = sizeof(pLog->ThreadName)-1;
len = len < maxlen ? len : maxlen;
@@ -1206,8 +1231,8 @@ void MicroProfileOnThreadExit()
{
S.Frames[i].nLogStart[nLogIndex] = 0;
}
- memset(pLog->nGroupStackPos, 0, sizeof(pLog->nGroupStackPos));
- memset(pLog->nGroupTicks, 0, sizeof(pLog->nGroupTicks));
+ pLog->nGroupStackPos.fill(0);
+ pLog->nGroupTicks.fill(0);
}
}
@@ -1247,7 +1272,7 @@ MicroProfileToken MicroProfileFindToken(const char* pGroup, const char* pName)
return MICROPROFILE_INVALID_TOKEN;
}
-uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
+inline uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
{
for(uint32_t i = 0; i < S.nGroupCount; ++i)
{
@@ -1276,7 +1301,7 @@ uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type)
return nGroupIndex;
}
-void MicroProfileRegisterGroup(const char* pGroup, const char* pCategory, uint32_t nColor)
+inline void MicroProfileRegisterGroup(const char* pGroup, const char* pCategory, uint32_t nColor)
{
int nCategoryIndex = -1;
for(uint32_t i = 0; i < S.nCategoryCount; ++i)
@@ -1442,7 +1467,7 @@ void MicroProfileGpuLeave(MicroProfileToken nToken_, uint64_t nTickStart)
}
}
-void MicroProfileContextSwitchPut(MicroProfileContextSwitch* pContextSwitch)
+inline void MicroProfileContextSwitchPut(MicroProfileContextSwitch* pContextSwitch)
{
if(S.nRunning || pContextSwitch->nTicks <= S.nPauseTicks)
{
@@ -1723,10 +1748,10 @@ void MicroProfileFlip()
}
}
}
- for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i)
+ for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- pLog->nGroupTicks[i] += nGroupTicks[i];
- pFrameGroup[i] += nGroupTicks[i];
+ pLog->nGroupTicks[j] += nGroupTicks[j];
+ pFrameGroup[j] += nGroupTicks[j];
}
pLog->nStackPos = nStackPos;
}
@@ -1894,7 +1919,7 @@ void MicroProfileSetEnableAllGroups(bool bEnableAllGroups)
S.nAllGroupsWanted = bEnableAllGroups ? 1 : 0;
}
-void MicroProfileEnableCategory(const char* pCategory, bool bEnabled)
+inline void MicroProfileEnableCategory(const char* pCategory, bool bEnabled)
{
int nCategoryIndex = -1;
for(uint32_t i = 0; i < S.nCategoryCount; ++i)
@@ -2004,7 +2029,7 @@ void MicroProfileForceDisableGroup(const char* pGroup, MicroProfileTokenType Typ
}
-void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, float* pTotal, uint32_t nSize)
+inline void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, float* pTotal, uint32_t nSize)
{
for(uint32_t i = 0; i < S.nTotalTimers && i < nSize; ++i)
{
@@ -3309,7 +3334,7 @@ bool MicroProfileIsLocalThread(uint32_t nThreadId)
#endif
#else
-bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;}
+bool MicroProfileIsLocalThread([[maybe_unused]] uint32_t nThreadId) { return false; }
void MicroProfileStopContextSwitchTrace(){}
void MicroProfileStartContextSwitchTrace(){}
@@ -3557,7 +3582,7 @@ int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu)
#undef S
-#ifdef _WIN32
+#ifdef _MSC_VER
#pragma warning(pop)
#endif
diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h
index ddaebe55b..85fbf2cb9 100644
--- a/externals/microprofile/microprofileui.h
+++ b/externals/microprofile/microprofileui.h
@@ -169,14 +169,13 @@ MICROPROFILEUI_API void MicroProfileCustomGroup(const char* pCustomName, uint32_
MICROPROFILEUI_API void MicroProfileCustomGroupAddTimer(const char* pCustomName, const char* pGroup, const char* pTimer);
#ifdef MICROPROFILEUI_IMPL
-#ifdef _WIN32
-#define snprintf _snprintf
-#endif
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <algorithm>
+#include <array>
MICROPROFILE_DEFINE(g_MicroProfileDetailed, "MicroProfile", "Detailed View", 0x8888000);
MICROPROFILE_DEFINE(g_MicroProfileDrawGraph, "MicroProfile", "Draw Graph", 0xff44ee00);
@@ -227,10 +226,10 @@ struct SOptionDesc
uint8_t nIndex;
bool bSelected;
};
-static uint32_t g_MicroProfileAggregatePresets[] = {0, 10, 20, 30, 60, 120};
-static float g_MicroProfileReferenceTimePresets[] = {5.f, 10.f, 15.f,20.f, 33.33f, 66.66f, 100.f, 250.f, 500.f, 1000.f};
-static uint32_t g_MicroProfileOpacityPresets[] = {0x40, 0x80, 0xc0, 0xff};
-static const char* g_MicroProfilePresetNames[] =
+static const std::array<uint32_t, 6> g_MicroProfileAggregatePresets{0, 10, 20, 30, 60, 120};
+static const std::array<float, 10> g_MicroProfileReferenceTimePresets{5.f, 10.f, 15.f,20.f, 33.33f, 66.66f, 100.f, 250.f, 500.f, 1000.f};
+static const std::array<uint32_t, 4> g_MicroProfileOpacityPresets{0x40, 0x80, 0xc0, 0xff};
+static const std::array<const char*, 7> g_MicroProfilePresetNames
{
MICROPROFILE_DEFAULT_PRESET,
"Render",
@@ -243,8 +242,8 @@ static const char* g_MicroProfilePresetNames[] =
enum
{
- MICROPROFILE_NUM_REFERENCE_PRESETS = sizeof(g_MicroProfileReferenceTimePresets)/sizeof(g_MicroProfileReferenceTimePresets[0]),
- MICROPROFILE_NUM_OPACITY_PRESETS = sizeof(g_MicroProfileOpacityPresets)/sizeof(g_MicroProfileOpacityPresets[0]),
+ MICROPROFILE_NUM_REFERENCE_PRESETS = g_MicroProfileReferenceTimePresets.size(),
+ MICROPROFILE_NUM_OPACITY_PRESETS = g_MicroProfileOpacityPresets.size(),
#if MICROPROFILE_CONTEXT_SWITCH_TRACE
MICROPROFILE_OPTION_SIZE = MICROPROFILE_NUM_REFERENCE_PRESETS + MICROPROFILE_NUM_OPACITY_PRESETS * 2 + 2 + 7,
#else
@@ -326,9 +325,9 @@ struct MicroProfileUI
MicroProfileUI g_MicroProfileUI;
#define UI g_MicroProfileUI
-static uint32_t g_nMicroProfileBackColors[2] = { 0x474747, 0x313131 };
+static const std::array<uint32_t, 2> g_nMicroProfileBackColors{ 0x474747, 0x313131 };
#define MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS 16
-static uint32_t g_nMicroProfileContextSwitchThreadColors[MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS] = //palette generated by http://tools.medialab.sciences-po.fr/iwanthue/index.php
+static const std::array<uint32_t, MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS> g_nMicroProfileContextSwitchThreadColors //palette generated by http://tools.medialab.sciences-po.fr/iwanthue/index.php
{
0x63607B,
0x755E2B,
@@ -356,7 +355,7 @@ void MicroProfileInitUI()
{
bInitialized = true;
memset(&g_MicroProfileUI, 0, sizeof(g_MicroProfileUI));
- UI.nActiveMenu = (uint32_t)-1;
+ UI.nActiveMenu = UINT32_MAX;
UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f;
UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f;
@@ -368,7 +367,7 @@ void MicroProfileInitUI()
UI.nWidth = 100;
UI.nHeight = 100;
- UI.nCustomActive = (uint32_t)-1;
+ UI.nCustomActive = UINT32_MAX;
UI.nCustomTimerCount = 0;
UI.nCustomCount = 0;
@@ -417,19 +416,19 @@ void MicroProfileToggleDisplayMode()
}
-void MicroProfileStringArrayClear(MicroProfileStringArray* pArray)
+inline void MicroProfileStringArrayClear(MicroProfileStringArray* pArray)
{
pArray->nNumStrings = 0;
pArray->pBufferPos = &pArray->Buffer[0];
}
-void MicroProfileStringArrayAddLiteral(MicroProfileStringArray* pArray, const char* pLiteral)
+inline void MicroProfileStringArrayAddLiteral(MicroProfileStringArray* pArray, const char* pLiteral)
{
MP_ASSERT(pArray->nNumStrings < MICROPROFILE_TOOLTIP_MAX_STRINGS);
pArray->ppStrings[pArray->nNumStrings++] = pLiteral;
}
-void MicroProfileStringArrayFormat(MicroProfileStringArray* pArray, const char* fmt, ...)
+inline void MicroProfileStringArrayFormat(MicroProfileStringArray* pArray, const char* fmt, ...)
{
MP_ASSERT(pArray->nNumStrings < MICROPROFILE_TOOLTIP_MAX_STRINGS);
pArray->ppStrings[pArray->nNumStrings++] = pArray->pBufferPos;
@@ -439,7 +438,7 @@ void MicroProfileStringArrayFormat(MicroProfileStringArray* pArray, const char*
va_end(args);
MP_ASSERT(pArray->pBufferPos < pArray->Buffer + MICROPROFILE_TOOLTIP_STRING_BUFFER_SIZE);
}
-void MicroProfileStringArrayCopy(MicroProfileStringArray* pDest, MicroProfileStringArray* pSrc)
+inline void MicroProfileStringArrayCopy(MicroProfileStringArray* pDest, MicroProfileStringArray* pSrc)
{
memcpy(&pDest->ppStrings[0], &pSrc->ppStrings[0], sizeof(pDest->ppStrings));
memcpy(&pDest->Buffer[0], &pSrc->Buffer[0], sizeof(pDest->Buffer));
@@ -456,7 +455,7 @@ void MicroProfileStringArrayCopy(MicroProfileStringArray* pDest, MicroProfileStr
pDest->nNumStrings = pSrc->nNumStrings;
}
-void MicroProfileFloatWindowSize(const char** ppStrings, uint32_t nNumStrings, uint32_t* pColors, uint32_t& nWidth, uint32_t& nHeight, uint32_t* pStringLengths = 0)
+inline void MicroProfileFloatWindowSize(const char** ppStrings, uint32_t nNumStrings, uint32_t* pColors, uint32_t& nWidth, uint32_t& nHeight, uint32_t* pStringLengths = 0)
{
uint32_t* nStringLengths = pStringLengths ? pStringLengths : (uint32_t*)alloca(nNumStrings * sizeof(uint32_t));
uint32_t nTextCount = nNumStrings/2;
@@ -474,7 +473,7 @@ void MicroProfileFloatWindowSize(const char** ppStrings, uint32_t nNumStrings, u
nHeight = (MICROPROFILE_TEXT_HEIGHT+1) * nTextCount + 2 * MICROPROFILE_BORDER_SIZE;
}
-void MicroProfileDrawFloatWindow(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0)
+inline void MicroProfileDrawFloatWindow(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0)
{
uint32_t nWidth = 0, nHeight = 0;
uint32_t* nStringLengths = (uint32_t*)alloca(nNumStrings * sizeof(uint32_t));
@@ -498,12 +497,12 @@ void MicroProfileDrawFloatWindow(uint32_t nX, uint32_t nY, const char** ppString
{
MicroProfileDrawBox(nX-MICROPROFILE_TEXT_WIDTH, nY, nX, nY + MICROPROFILE_TEXT_WIDTH, pColors[i]|0xff000000);
}
- MicroProfileDrawText(nX + 1, nY + 1, (uint32_t)-1, ppStrings[i0], (uint32_t)strlen(ppStrings[i0]));
- MicroProfileDrawText(nX + nWidth - nStringLengths[i0+1] * (MICROPROFILE_TEXT_WIDTH+1), nY + 1, (uint32_t)-1, ppStrings[i0+1], (uint32_t)strlen(ppStrings[i0+1]));
+ MicroProfileDrawText(nX + 1, nY + 1, UINT32_MAX, ppStrings[i0], (uint32_t)strlen(ppStrings[i0]));
+ MicroProfileDrawText(nX + nWidth - nStringLengths[i0+1] * (MICROPROFILE_TEXT_WIDTH+1), nY + 1, UINT32_MAX, ppStrings[i0+1], (uint32_t)strlen(ppStrings[i0+1]));
nY += (MICROPROFILE_TEXT_HEIGHT+1);
}
}
-void MicroProfileDrawTextBox(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0)
+inline void MicroProfileDrawTextBox(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0)
{
uint32_t nWidth = 0, nHeight = 0;
uint32_t* nStringLengths = (uint32_t*)alloca(nNumStrings * sizeof(uint32_t));
@@ -522,14 +521,14 @@ void MicroProfileDrawTextBox(uint32_t nX, uint32_t nY, const char** ppStrings, u
MicroProfileDrawBox(nX, nY, nX + nWidth, nY + nHeight, 0xff000000);
for(uint32_t i = 0; i < nNumStrings; ++i)
{
- MicroProfileDrawText(nX + 1, nY + 1, (uint32_t)-1, ppStrings[i], (uint32_t)strlen(ppStrings[i]));
+ MicroProfileDrawText(nX + 1, nY + 1, UINT32_MAX, ppStrings[i], (uint32_t)strlen(ppStrings[i]));
nY += (MICROPROFILE_TEXT_HEIGHT+1);
}
}
-void MicroProfileToolTipMeta(MicroProfileStringArray* pToolTip)
+inline void MicroProfileToolTipMeta(MicroProfileStringArray* pToolTip)
{
MicroProfile& S = *MicroProfileGet();
if(UI.nRangeBeginIndex != UI.nRangeEndIndex && UI.pRangeLog)
@@ -608,7 +607,7 @@ void MicroProfileToolTipMeta(MicroProfileStringArray* pToolTip)
}
}
-void MicroProfileDrawFloatTooltip(uint32_t nX, uint32_t nY, uint32_t nToken, uint64_t nTime)
+inline void MicroProfileDrawFloatTooltip(uint32_t nX, uint32_t nY, uint32_t nToken, uint64_t nTime)
{
MicroProfile& S = *MicroProfileGet();
@@ -718,7 +717,7 @@ void MicroProfileDrawFloatTooltip(uint32_t nX, uint32_t nY, uint32_t nToken, uin
}
-void MicroProfileZoomTo(int64_t nTickStart, int64_t nTickEnd)
+inline void MicroProfileZoomTo(int64_t nTickStart, int64_t nTickEnd)
{
MicroProfile& S = *MicroProfileGet();
@@ -728,7 +727,7 @@ void MicroProfileZoomTo(int64_t nTickStart, int64_t nTickEnd)
UI.fDetailedRangeTarget = MicroProfileLogTickDifference(nTickStart, nTickEnd) * fToMs;
}
-void MicroProfileCenter(int64_t nTickCenter)
+inline void MicroProfileCenter(int64_t nTickCenter)
{
MicroProfile& S = *MicroProfileGet();
int64_t nStart = S.Frames[S.nFrameCurrent].nFrameStartCpu;
@@ -739,7 +738,7 @@ void MicroProfileCenter(int64_t nTickCenter)
#ifdef MICROPROFILE_DEBUG
uint64_t* g_pMicroProfileDumpStart = 0;
uint64_t* g_pMicroProfileDumpEnd = 0;
-void MicroProfileDebugDumpRange()
+inline void MicroProfileDebugDumpRange()
{
MicroProfile& S = *MicroProfileGet();
if(g_pMicroProfileDumpStart != g_pMicroProfileDumpEnd)
@@ -777,11 +776,11 @@ void MicroProfileDebugDumpRange()
#define MICROPROFILE_HOVER_DIST 0.5f
-void MicroProfileDrawDetailedContextSwitchBars(uint32_t nY, uint32_t nThreadId, uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, int64_t nBaseTicks, uint32_t nBaseY)
+inline void MicroProfileDrawDetailedContextSwitchBars(uint32_t nY, uint32_t nThreadId, uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, int64_t nBaseTicks, uint32_t nBaseY)
{
MicroProfile& S = *MicroProfileGet();
int64_t nTickIn = -1;
- uint32_t nThreadBefore = -1;
+ uint32_t nThreadBefore = UINT32_MAX;
float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu());
float fMsToScreen = UI.nWidth / UI.fDetailedRange;
float fMouseX = (float)UI.nMouseX;
@@ -841,7 +840,7 @@ void MicroProfileDrawDetailedContextSwitchBars(uint32_t nY, uint32_t nThreadId,
}
}
-void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, int nSelectedFrame)
+inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, int nSelectedFrame)
{
MicroProfile& S = *MicroProfileGet();
MP_DEBUG_DUMP_RANGE();
@@ -949,10 +948,10 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
uint32_t nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadAfter;
uint32_t nContextSwitchHoverThreadBefore = S.nContextSwitchHoverThreadBefore;
- S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = -1;
+ S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = UINT32_MAX;
- uint32_t nContextSwitchStart = -1;
- uint32_t nContextSwitchEnd = -1;
+ uint32_t nContextSwitchStart = UINT32_MAX;
+ uint32_t nContextSwitchEnd = UINT32_MAX;
S.nContextSwitchHoverCpuNext = 0xff;
S.nContextSwitchHoverTickIn = -1;
S.nContextSwitchHoverTickOut = -1;
@@ -1005,9 +1004,10 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
}while(pFrameLogFirst != pFrameFirst);
- if(nGet == (uint32_t)-1)
+ if (nGet == UINT32_MAX) {
continue;
- MP_ASSERT(nGet != (uint32_t)-1);
+ }
+ MP_ASSERT(nGet != UINT32_MAX);
nPut = pFrameLogLast->nLogStart[i];
@@ -1023,9 +1023,9 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
int64_t nBaseTicks = bGpu ? nBaseTicksGpu : nBaseTicksCpu;
char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16];
uint64_t nThreadId = pLog->nThreadId;
- snprintf(ThreadName, sizeof(ThreadName)-1, "%04llx: %s", nThreadId, &pLog->ThreadName[0] );
+ snprintf(ThreadName, sizeof(ThreadName)-1, "%04" PRIx64 ": %s", nThreadId, &pLog->ThreadName[0] );
nY += 3;
- uint32_t nThreadColor = -1;
+ uint32_t nThreadColor = UINT32_MAX;
if(pLog->nThreadId == nContextSwitchHoverThreadAfter || pLog->nThreadId == nContextSwitchHoverThreadBefore)
nThreadColor = UI.nHoverColorShared|0x906060;
MicroProfileDrawText(0, nY, nThreadColor, &ThreadName[0], (uint32_t)strlen(&ThreadName[0]));
@@ -1048,7 +1048,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
uint32_t nEnd = nRange[j][1];
for(uint32_t k = nStart; k < nEnd; ++k)
{
- MicroProfileLogEntry* pEntry = pLog->Log + k;
+ MicroProfileLogEntry* pEntry = &pLog->Log[k];
int nType = MicroProfileLogType(*pEntry);
if(MP_LOG_ENTER == nType)
{
@@ -1066,7 +1066,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
continue;
}
- MicroProfileLogEntry* pEntryEnter = pLog->Log + nStack[nStackPos-1];
+ MicroProfileLogEntry* pEntryEnter = &pLog->Log[nStack[nStackPos-1]];
if(MicroProfileLogTimerIndex(*pEntryEnter) != MicroProfileLogTimerIndex(*pEntry))
{
//uprintf("mismatch %llx %llx\n", pEntryEnter->nToken, pEntry->nToken);
@@ -1126,7 +1126,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
uint32_t nIntegerWidth = (uint32_t)(fXEnd - fXStart);
if(nIntegerWidth)
{
- if(bHover && UI.nActiveMenu == -1)
+ if(bHover && UI.nActiveMenu == UINT32_MAX)
{
nHoverToken = MicroProfileLogTimerIndex(*pEntry);
#if MICROPROFILE_DEBUG
@@ -1146,7 +1146,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
int nCharacters = (nTextWidth - 2*MICROPROFILE_TEXT_WIDTH) / MICROPROFILE_TEXT_WIDTH;
if(nCharacters>0)
{
- MicroProfileDrawText(fXStartText+1, fYStart+1, -1, S.TimerInfo[nTimerIndex].pName, MicroProfileMin<uint32_t>(S.TimerInfo[nTimerIndex].nNameLen, nCharacters));
+ MicroProfileDrawText(fXStartText + 1, fYStart + 1, UINT32_MAX, S.TimerInfo[nTimerIndex].pName, MicroProfileMin<uint32_t>(S.TimerInfo[nTimerIndex].nNameLen, nCharacters));
}
}
#endif
@@ -1158,7 +1158,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
int nLineX = (int)floor(fXAvg+0.5f);
if(nLineX != (int)nLinesDrawn[nStackPos])
{
- if(bHover && UI.nActiveMenu == -1)
+ if(bHover && UI.nActiveMenu == UINT32_MAX)
{
nHoverToken = (uint32_t)MicroProfileLogTimerIndex(*pEntry);
nHoverTime = MicroProfileLogTickDifference(nTickStart, nTickEnd);
@@ -1235,9 +1235,9 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
// nThreadId is 32-bit on Windows
int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04x: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) );
#else
- int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04llx: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) );
+ int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04" PRIx64 ": %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) );
#endif
- uint32_t nThreadColor = -1;
+ uint32_t nThreadColor = UINT32_MAX;
if(nThreadId == nContextSwitchHoverThreadAfter || nThreadId == nContextSwitchHoverThreadBefore)
nThreadColor = UI.nHoverColorShared|0x906060;
MicroProfileDrawDetailedContextSwitchBars(nY+2, nThreadId, nContextSwitchStart, nContextSwitchEnd, nBaseTicksCpu, nBaseY);
@@ -1249,9 +1249,6 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
S.nContextSwitchHoverCpu = S.nContextSwitchHoverCpuNext;
-
-
-
UI.pDisplayMouseOver = pMouseOverNext;
if(!S.nRunning)
@@ -1286,10 +1283,10 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
float fStartTextWidth = (float)((1+MICROPROFILE_TEXT_WIDTH) * nLenStart);
float fStartTextX = fXStart - fStartTextWidth - 2;
MicroProfileDrawBox(fStartTextX, nBaseY, fStartTextX + fStartTextWidth + 2, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat);
- MicroProfileDrawText(fStartTextX+1, nBaseY, (uint32_t)-1, sBuffer, nLenStart);
+ MicroProfileDrawText(fStartTextX+1, nBaseY, UINT32_MAX, sBuffer, nLenStart);
uint32_t nLenEnd = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsEnd);
MicroProfileDrawBox(fXEnd+1, nBaseY, fXEnd+1+(1+MICROPROFILE_TEXT_WIDTH) * nLenEnd + 3, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat);
- MicroProfileDrawText(fXEnd+2, nBaseY+1, (uint32_t)-1, sBuffer, nLenEnd);
+ MicroProfileDrawText(fXEnd+2, nBaseY+1, UINT32_MAX, sBuffer, nLenEnd);
if(UI.nMouseRight)
{
@@ -1316,16 +1313,16 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
float fStartTextWidth = (float)((1+MICROPROFILE_TEXT_WIDTH) * nLenStart);
float fStartTextX = fXStart - fStartTextWidth - 2;
MicroProfileDrawBox(fStartTextX, nBaseY, fStartTextX + fStartTextWidth + 2, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat);
- MicroProfileDrawText(fStartTextX+1, nBaseY, (uint32_t)-1, sBuffer, nLenStart);
+ MicroProfileDrawText(fStartTextX+1, nBaseY, UINT32_MAX, sBuffer, nLenStart);
uint32_t nLenEnd = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsEnd);
MicroProfileDrawBox(fXEnd+1, nBaseY, fXEnd+1+(1+MICROPROFILE_TEXT_WIDTH) * nLenEnd + 3, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat);
- MicroProfileDrawText(fXEnd+2, nBaseY+1, (uint32_t)-1, sBuffer, nLenEnd);
+ MicroProfileDrawText(fXEnd+2, nBaseY+1, UINT32_MAX, sBuffer, nLenEnd);
}
}
}
-void MicroProfileDrawDetailedFrameHistory(uint32_t nWidth, uint32_t nHeight, uint32_t nBaseY, uint32_t nSelectedFrame)
+inline void MicroProfileDrawDetailedFrameHistory(uint32_t nWidth, uint32_t nHeight, uint32_t nBaseY, uint32_t nSelectedFrame)
{
MicroProfile& S = *MicroProfileGet();
@@ -1365,7 +1362,7 @@ void MicroProfileDrawDetailedFrameHistory(uint32_t nWidth, uint32_t nHeight, uin
fBaseX = fXStart;
uint32_t nColor = MICROPROFILE_FRAME_HISTORY_COLOR_CPU;
if(nIndex == nSelectedFrame)
- nColor = (uint32_t)-1;
+ nColor = UINT32_MAX;
MicroProfileDrawBox(fXStart, nBaseY + fScale * nBarHeight, fXEnd, nBaseY+MICROPROFILE_FRAME_HISTORY_HEIGHT, nColor, MicroProfileBoxTypeBar);
if(pNext->nFrameStartCpu > nCpuStart)
{
@@ -1379,7 +1376,7 @@ void MicroProfileDrawDetailedFrameHistory(uint32_t nWidth, uint32_t nHeight, uin
}
MicroProfileDrawBox(fSelectionStart, nBaseY, fSelectionEnd, nBaseY+MICROPROFILE_FRAME_HISTORY_HEIGHT, MICROPROFILE_FRAME_HISTORY_COLOR_HIGHTLIGHT, MicroProfileBoxTypeFlat);
}
-void MicroProfileDrawDetailedView(uint32_t nWidth, uint32_t nHeight)
+inline void MicroProfileDrawDetailedView(uint32_t nWidth, uint32_t nHeight)
{
MicroProfile& S = *MicroProfileGet();
@@ -1387,7 +1384,7 @@ void MicroProfileDrawDetailedView(uint32_t nWidth, uint32_t nHeight)
uint32_t nBaseY = MICROPROFILE_TEXT_HEIGHT + 1;
int nSelectedFrame = -1;
- if(UI.nMouseY > nBaseY && UI.nMouseY <= nBaseY + MICROPROFILE_FRAME_HISTORY_HEIGHT && UI.nActiveMenu == -1)
+ if(UI.nMouseY > nBaseY && UI.nMouseY <= nBaseY + MICROPROFILE_FRAME_HISTORY_HEIGHT && UI.nActiveMenu == UINT32_MAX)
{
nSelectedFrame = ((MICROPROFILE_NUM_FRAMES) * (UI.nWidth-UI.nMouseX) / UI.nWidth);
@@ -1416,23 +1413,23 @@ void MicroProfileDrawDetailedView(uint32_t nWidth, uint32_t nHeight)
MicroProfileDrawDetailedFrameHistory(nWidth, nHeight, nBaseY, nSelectedFrame);
}
-void MicroProfileDrawTextRight(uint32_t nX, uint32_t nY, uint32_t nColor, const char* pStr, uint32_t nStrLen)
+inline void MicroProfileDrawTextRight(uint32_t nX, uint32_t nY, uint32_t nColor, const char* pStr, uint32_t nStrLen)
{
MicroProfileDrawText(nX - nStrLen * (MICROPROFILE_TEXT_WIDTH+1), nY, nColor, pStr, nStrLen);
}
-void MicroProfileDrawHeader(int32_t nX, uint32_t nWidth, const char* pName)
+inline void MicroProfileDrawHeader(int32_t nX, uint32_t nWidth, const char* pName)
{
if(pName)
{
MicroProfileDrawBox(nX-8, MICROPROFILE_TEXT_HEIGHT + 2, nX + nWidth+5, MICROPROFILE_TEXT_HEIGHT + 2 + (MICROPROFILE_TEXT_HEIGHT+1), 0xff000000|g_nMicroProfileBackColors[1]);
- MicroProfileDrawText(nX, MICROPROFILE_TEXT_HEIGHT + 2, (uint32_t)-1, pName, (uint32_t)strlen(pName));
+ MicroProfileDrawText(nX, MICROPROFILE_TEXT_HEIGHT + 2, UINT32_MAX, pName, (uint32_t)strlen(pName));
}
}
typedef void (*MicroProfileLoopGroupCallback)(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pData);
-void MicroProfileLoopActiveGroupsDraw(int32_t nX, int32_t nY, const char* pName, MicroProfileLoopGroupCallback CB, void* pData)
+inline void MicroProfileLoopActiveGroupsDraw(int32_t nX, int32_t nY, const char* pName, MicroProfileLoopGroupCallback CB, void* pData)
{
MicroProfile& S = *MicroProfileGet();
nY += MICROPROFILE_TEXT_HEIGHT + 2;
@@ -1440,7 +1437,7 @@ void MicroProfileLoopActiveGroupsDraw(int32_t nX, int32_t nY, const char* pName,
uint32_t nCount = 0;
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- uint64_t nMask = 1ll << j;
+ uint64_t nMask = 1ULL << j;
if(nMask & nGroup)
{
nY += MICROPROFILE_TEXT_HEIGHT + 1;
@@ -1465,7 +1462,7 @@ void MicroProfileLoopActiveGroupsDraw(int32_t nX, int32_t nY, const char* pName,
}
-void MicroProfileCalcTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, uint64_t nGroup, uint32_t nSize)
+inline void MicroProfileCalcTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, uint64_t nGroup, uint32_t nSize)
{
MicroProfile& S = *MicroProfileGet();
@@ -1521,13 +1518,13 @@ void MicroProfileCalcTimers(float* pTimers, float* pAverage, float* pMax, float*
}
}
}
- nMask <<= 1ll;
+ nMask <<= 1;
}
}
#define SBUF_MAX 32
-void MicroProfileDrawBarArrayCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
+inline void MicroProfileDrawBarArrayCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
{
const uint32_t nHeight = MICROPROFILE_TEXT_HEIGHT;
const uint32_t nTextWidth = 6 * (1+MICROPROFILE_TEXT_WIDTH);
@@ -1543,11 +1540,11 @@ void MicroProfileDrawBarArrayCallback(uint32_t nTimer, uint32_t nIdx, uint64_t n
snprintf(sBuffer, SBUF_MAX-1, "%5.2f", pTimers[nIdx]);
if (!pTimers2)
MicroProfileDrawBox(nX + nTextWidth, nY, nX + nTextWidth + fWidth * pTimers[nIdx+1], nY + nHeight, UI.nOpacityForeground|S.TimerInfo[nTimer].nColor, MicroProfileBoxTypeBar);
- MicroProfileDrawText(nX, nY, (uint32_t)-1, sBuffer, (uint32_t)strlen(sBuffer));
+ MicroProfileDrawText(nX, nY, UINT32_MAX, sBuffer, (uint32_t)strlen(sBuffer));
}
-uint32_t MicroProfileDrawBarArray(int32_t nX, int32_t nY, float* pTimers, const char* pName, uint32_t nTotalHeight, float* pTimers2 = NULL)
+inline uint32_t MicroProfileDrawBarArray(int32_t nX, int32_t nY, float* pTimers, const char* pName, uint32_t nTotalHeight, float* pTimers2 = NULL)
{
const uint32_t nTextWidth = 6 * (1+MICROPROFILE_TEXT_WIDTH);
const uint32_t nWidth = MICROPROFILE_BAR_WIDTH;
@@ -1559,15 +1556,15 @@ uint32_t MicroProfileDrawBarArray(int32_t nX, int32_t nY, float* pTimers, const
return nWidth + 5 + nTextWidth;
}
-void MicroProfileDrawBarCallCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
+inline void MicroProfileDrawBarCallCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
{
MicroProfile& S = *MicroProfileGet();
char sBuffer[SBUF_MAX];
int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5d", S.Frame[nTimer].nCount);//fix
- MicroProfileDrawText(nX, nY, (uint32_t)-1, sBuffer, nLen);
+ MicroProfileDrawText(nX, nY, UINT32_MAX, sBuffer, nLen);
}
-uint32_t MicroProfileDrawBarCallCount(int32_t nX, int32_t nY, const char* pName)
+inline uint32_t MicroProfileDrawBarCallCount(int32_t nX, int32_t nY, const char* pName)
{
MicroProfileLoopActiveGroupsDraw(nX, nY, pName, MicroProfileDrawBarCallCountCallback, 0);
const uint32_t nTextWidth = 6 * MICROPROFILE_TEXT_WIDTH;
@@ -1581,17 +1578,17 @@ struct MicroProfileMetaAverageArgs
float fRcpFrames;
};
-void MicroProfileDrawBarMetaAverageCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
+inline void MicroProfileDrawBarMetaAverageCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
{
MicroProfileMetaAverageArgs* pArgs = (MicroProfileMetaAverageArgs*)pExtra;
uint64_t* pCounters = pArgs->pCounters;
float fRcpFrames = pArgs->fRcpFrames;
char sBuffer[SBUF_MAX];
int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5.2f", pCounters[nTimer] * fRcpFrames);
- MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, (uint32_t)-1, sBuffer, nLen);
+ MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, UINT32_MAX, sBuffer, nLen);
}
-uint32_t MicroProfileDrawBarMetaAverage(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight)
+inline uint32_t MicroProfileDrawBarMetaAverage(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight)
{
if(!pName)
return 0;
@@ -1605,15 +1602,15 @@ uint32_t MicroProfileDrawBarMetaAverage(int32_t nX, int32_t nY, uint64_t* pCount
}
-void MicroProfileDrawBarMetaCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
+inline void MicroProfileDrawBarMetaCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
{
uint64_t* pCounters = (uint64_t*)pExtra;
char sBuffer[SBUF_MAX];
- int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5llu", pCounters[nTimer]);
- MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, (uint32_t)-1, sBuffer, nLen);
+ int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5" PRIu64, pCounters[nTimer]);
+ MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, UINT32_MAX, sBuffer, nLen);
}
-uint32_t MicroProfileDrawBarMetaCount(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight)
+inline uint32_t MicroProfileDrawBarMetaCount(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight)
{
if(!pName)
return 0;
@@ -1625,7 +1622,7 @@ uint32_t MicroProfileDrawBarMetaCount(int32_t nX, int32_t nY, uint64_t* pCounter
return 5 + nTextWidth;
}
-void MicroProfileDrawBarLegendCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
+inline void MicroProfileDrawBarLegendCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra)
{
MicroProfile& S = *MicroProfileGet();
if (S.TimerInfo[nTimer].bGraph)
@@ -1640,7 +1637,7 @@ void MicroProfileDrawBarLegendCallback(uint32_t nTimer, uint32_t nIdx, uint64_t
}
}
-uint32_t MicroProfileDrawBarLegend(int32_t nX, int32_t nY, uint32_t nTotalHeight, uint32_t nMaxWidth)
+inline uint32_t MicroProfileDrawBarLegend(int32_t nX, int32_t nY, uint32_t nTotalHeight, uint32_t nMaxWidth)
{
MicroProfileDrawLineVertical(nX-5, nY, nTotalHeight, UI.nOpacityBackground | g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]);
MicroProfileLoopActiveGroupsDraw(nMaxWidth, nY, 0, MicroProfileDrawBarLegendCallback, 0);
@@ -1667,7 +1664,7 @@ bool MicroProfileDrawGraph(uint32_t nScreenWidth, uint32_t nScreenHeight)
if(bMouseOver)
{
float fXAvg = fMouseXPrc * MICROPROFILE_GRAPH_WIDTH + nX;
- MicroProfileDrawLineVertical(fXAvg, nY, nY + MICROPROFILE_GRAPH_HEIGHT, (uint32_t)-1);
+ MicroProfileDrawLineVertical(fXAvg, nY, nY + MICROPROFILE_GRAPH_HEIGHT, UINT32_MAX);
}
@@ -1706,7 +1703,7 @@ bool MicroProfileDrawGraph(uint32_t nScreenWidth, uint32_t nScreenHeight)
char buf[32];
int nLen = snprintf(buf, sizeof(buf)-1, "%5.2fms", S.fReferenceTime);
- MicroProfileDrawText(nX+1, fY1 - (2+MICROPROFILE_TEXT_HEIGHT), (uint32_t)-1, buf, nLen);
+ MicroProfileDrawText(nX+1, fY1 - (2+MICROPROFILE_TEXT_HEIGHT), UINT32_MAX, buf, nLen);
}
@@ -1782,7 +1779,7 @@ void MicroProfileDumpTimers()
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- uint64_t nMask = 1ll << j;
+ uint64_t nMask = 1ULL << j;
if(nMask & nActiveGroup)
{
MICROPROFILE_PRINTF("%s\n", S.GroupInfo[j].pName);
@@ -1807,7 +1804,7 @@ void MicroProfileDumpTimers()
}
}
-void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight)
+inline void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight)
{
MicroProfile& S = *MicroProfileGet();
@@ -1823,7 +1820,7 @@ void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight)
uint32_t nNumGroups = 0;
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- if(nActiveGroup & (1ll << j))
+ if(nActiveGroup & (1ULL << j))
{
nNumTimers += S.GroupInfo[j].nNumTimers;
nNumGroups += 1;
@@ -1878,7 +1875,7 @@ void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight)
for(uint32_t i = 0; i < nNumTimers+nNumGroups+1; ++i)
{
uint32_t nY0 = nY + i * (nHeight + 1);
- bool bInside = (UI.nActiveMenu == -1) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1)));
+ bool bInside = (UI.nActiveMenu == UINT32_MAX) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1)));
MicroProfileDrawBox(nX, nY0, nWidth+nX, nY0 + (nHeight+1)+1, UI.nOpacityBackground | (g_nMicroProfileBackColors[nColorIndex++ & 1] + ((bInside) ? 0x002c2c2c : 0)));
}
nX += 10;
@@ -1927,22 +1924,22 @@ void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight)
nY = nHeight + 3 - UI.nOffsetY;
for(uint32_t i = 0; i < nNumTimers+nNumGroups+1; ++i)
{
- uint32_t nY0 = nY + i * (nHeight + 1);
- bool bInside = (UI.nActiveMenu == -1) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1)));
+ const uint32_t nY0 = nY + i * (nHeight + 1);
+ const bool bInside = (UI.nActiveMenu == UINT32_MAX) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1)));
MicroProfileDrawBox(nX, nY0, nTimerWidth, nY0 + (nHeight+1)+1, 0xff0000000 | (g_nMicroProfileBackColors[nColorIndex++ & 1] + ((bInside) ? 0x002c2c2c : 0)));
}
nX += MicroProfileDrawBarLegend(nX, nY, nTotalHeight, nTimerWidth-5) + 1;
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- if(nActiveGroup & (1ll << j))
+ if(nActiveGroup & (1ULL << j))
{
- MicroProfileDrawText(nX, nY + (1+nHeight) * nLegendOffset, (uint32_t)-1, S.GroupInfo[j].pName, S.GroupInfo[j].nNameLen);
+ MicroProfileDrawText(nX, nY + (1+nHeight) * nLegendOffset, UINT32_MAX, S.GroupInfo[j].pName, S.GroupInfo[j].nNameLen);
nLegendOffset += S.GroupInfo[j].nNumTimers+1;
}
}
MicroProfileDrawHeader(nX, nTimerWidth-5, "Group");
- MicroProfileDrawTextRight(nTimerWidth-3, MICROPROFILE_TEXT_HEIGHT + 2, (uint32_t)-1, "Timer", 5);
+ MicroProfileDrawTextRight(nTimerWidth-3, MICROPROFILE_TEXT_HEIGHT + 2, UINT32_MAX, "Timer", 5);
MicroProfileDrawLineVertical(nTimerWidth, 0, nTotalHeight+nY, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]);
MicroProfileDrawLineHorizontal(0, nWidth, 2*MICROPROFILE_TEXT_HEIGHT + 3, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]);
}
@@ -1951,7 +1948,7 @@ typedef const char* (*MicroProfileSubmenuCallback)(int, bool* bSelected);
typedef void (*MicroProfileClickCallback)(int);
-const char* MicroProfileUIMenuMode(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuMode(int nIndex, bool* bSelected)
{
MicroProfile& S = *MicroProfileGet();
switch(nIndex)
@@ -1979,7 +1976,7 @@ const char* MicroProfileUIMenuMode(int nIndex, bool* bSelected)
}
}
-const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
{
MicroProfile& S = *MicroProfileGet();
*bSelected = false;
@@ -2003,7 +2000,7 @@ const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
}
else
{
- *bSelected = 0 != (S.nActiveGroupWanted & (1ll << Item.nIndex));
+ *bSelected = 0 != (S.nActiveGroupWanted & (1ULL << Item.nIndex));
snprintf(buffer, sizeof(buffer)-1, " %s", Item.pName);
}
return buffer;
@@ -2012,19 +2009,21 @@ const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
}
}
-const char* MicroProfileUIMenuAggregate(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuAggregate(int nIndex, bool* bSelected)
{
MicroProfile& S = *MicroProfileGet();
- if(nIndex < sizeof(g_MicroProfileAggregatePresets)/sizeof(g_MicroProfileAggregatePresets[0]))
+ if(static_cast<uint32_t>(nIndex) < g_MicroProfileAggregatePresets.size())
{
- int val = g_MicroProfileAggregatePresets[nIndex];
- *bSelected = (int)S.nAggregateFlip == val;
- if(0 == val)
+ uint32_t val = g_MicroProfileAggregatePresets[nIndex];
+ *bSelected = S.nAggregateFlip == val;
+ if (0 == val)
+ {
return "Infinite";
+ }
else
{
static char buf[128];
- snprintf(buf, sizeof(buf)-1, "%7d", val);
+ snprintf(buf, sizeof(buf)-1, "%7u", val);
return buf;
}
}
@@ -2032,7 +2031,7 @@ const char* MicroProfileUIMenuAggregate(int nIndex, bool* bSelected)
}
-const char* MicroProfileUIMenuTimers(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuTimers(int nIndex, bool* bSelected)
{
MicroProfile& S = *MicroProfileGet();
*bSelected = 0 != (S.nBars & (1 << nIndex));
@@ -2054,7 +2053,7 @@ const char* MicroProfileUIMenuTimers(int nIndex, bool* bSelected)
return 0;
}
-const char* MicroProfileUIMenuOptions(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuOptions(int nIndex, bool* bSelected)
{
MicroProfile& S = *MicroProfileGet();
if(nIndex >= MICROPROFILE_OPTION_SIZE) return 0;
@@ -2094,15 +2093,17 @@ const char* MicroProfileUIMenuOptions(int nIndex, bool* bSelected)
return UI.Options[nIndex].Text;
}
-const char* MicroProfileUIMenuPreset(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuPreset(int nIndex, bool* bSelected)
{
static char buf[128];
*bSelected = false;
- int nNumPresets = sizeof(g_MicroProfilePresetNames) / sizeof(g_MicroProfilePresetNames[0]);
+ int nNumPresets = static_cast<int>(g_MicroProfilePresetNames.size());
int nIndexSave = nIndex - nNumPresets - 1;
- if(nIndex == nNumPresets)
+ if (nIndex == nNumPresets)
+ {
return "--";
- else if(nIndexSave >=0 && nIndexSave <nNumPresets)
+ }
+ else if(nIndexSave >=0 && nIndexSave < nNumPresets)
{
snprintf(buf, sizeof(buf)-1, "Save '%s'", g_MicroProfilePresetNames[nIndexSave]);
return buf;
@@ -2118,15 +2119,15 @@ const char* MicroProfileUIMenuPreset(int nIndex, bool* bSelected)
}
}
-const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
{
- if((uint32_t)-1 == UI.nCustomActive)
+ if(UINT32_MAX == UI.nCustomActive)
{
*bSelected = nIndex == 0;
}
else
{
- *bSelected = nIndex-2 == UI.nCustomActive;
+ *bSelected = nIndex-2 == static_cast<int>(UI.nCustomActive);
}
switch(nIndex)
{
@@ -2145,13 +2146,13 @@ const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
}
}
-const char* MicroProfileUIMenuEmpty(int nIndex, bool* bSelected)
+inline const char* MicroProfileUIMenuEmpty(int nIndex, bool* bSelected)
{
return 0;
}
-void MicroProfileUIClickMode(int nIndex)
+inline void MicroProfileUIClickMode(int nIndex)
{
MicroProfile& S = *MicroProfileGet();
switch(nIndex)
@@ -2176,7 +2177,7 @@ void MicroProfileUIClickMode(int nIndex)
}
}
-void MicroProfileUIClickGroups(int nIndex)
+inline void MicroProfileUIClickGroups(int nIndex)
{
MicroProfile& S = *MicroProfileGet();
if(nIndex == 0)
@@ -2202,13 +2203,13 @@ void MicroProfileUIClickGroups(int nIndex)
else
{
MP_ASSERT(Item.nIndex < S.nGroupCount);
- S.nActiveGroupWanted ^= (1ll << Item.nIndex);
+ S.nActiveGroupWanted ^= (1ULL << Item.nIndex);
}
}
}
}
-void MicroProfileUIClickAggregate(int nIndex)
+inline void MicroProfileUIClickAggregate(int nIndex)
{
MicroProfile& S = *MicroProfileGet();
S.nAggregateFlip = g_MicroProfileAggregatePresets[nIndex];
@@ -2218,13 +2219,13 @@ void MicroProfileUIClickAggregate(int nIndex)
}
}
-void MicroProfileUIClickTimers(int nIndex)
+inline void MicroProfileUIClickTimers(int nIndex)
{
MicroProfile& S = *MicroProfileGet();
S.nBars ^= (1 << nIndex);
}
-void MicroProfileUIClickOptions(int nIndex)
+inline void MicroProfileUIClickOptions(int nIndex)
{
MicroProfile& S = *MicroProfileGet();
switch(UI.Options[nIndex].nSubType)
@@ -2271,9 +2272,9 @@ void MicroProfileUIClickOptions(int nIndex)
}
}
-void MicroProfileUIClickPreset(int nIndex)
+inline void MicroProfileUIClickPreset(int nIndex)
{
- int nNumPresets = sizeof(g_MicroProfilePresetNames) / sizeof(g_MicroProfilePresetNames[0]);
+ int nNumPresets = static_cast<int>(g_MicroProfilePresetNames.size());
int nIndexSave = nIndex - nNumPresets - 1;
if(nIndexSave >= 0 && nIndexSave < nNumPresets)
{
@@ -2285,7 +2286,7 @@ void MicroProfileUIClickPreset(int nIndex)
}
}
-void MicroProfileUIClickCustom(int nIndex)
+inline void MicroProfileUIClickCustom(int nIndex)
{
if(nIndex == 0)
{
@@ -2298,19 +2299,19 @@ void MicroProfileUIClickCustom(int nIndex)
}
-void MicroProfileUIClickEmpty(int nIndex)
+inline void MicroProfileUIClickEmpty(int nIndex)
{
}
-void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
+inline void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
{
MicroProfile& S = *MicroProfileGet();
uint32_t nX = 0;
uint32_t nY = 0;
- bool bMouseOver = UI.nMouseY < MICROPROFILE_TEXT_HEIGHT + 1;
+
#define SBUF_SIZE 256
char buffer[256];
MicroProfileDrawBox(nX, nY, nX + nWidth, nY + (MICROPROFILE_TEXT_HEIGHT+1)+1, 0xff000000|g_nMicroProfileBackColors[1]);
@@ -2321,7 +2322,7 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
uint32_t nNumMenuItems = 0;
int nLen = snprintf(buffer, 127, "MicroProfile");
- MicroProfileDrawText(nX, nY, (uint32_t)-1, buffer, nLen);
+ MicroProfileDrawText(nX, nY, UINT32_MAX, buffer, nLen);
nX += (sizeof("MicroProfile")+2) * (MICROPROFILE_TEXT_WIDTH+1);
pMenuText[nNumMenuItems++] = "Mode";
pMenuText[nNumMenuItems++] = "Groups";
@@ -2409,7 +2410,7 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
};
- uint32_t nSelectMenu = (uint32_t)-1;
+ uint32_t nSelectMenu = UINT32_MAX;
for(uint32_t i = 0; i < nNumMenuItems; ++i)
{
nMenuX[i] = nX;
@@ -2419,17 +2420,17 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
{
MicroProfileDrawBox(nX-1, nY, nX + nLen * (MICROPROFILE_TEXT_WIDTH+1), nY +(MICROPROFILE_TEXT_HEIGHT+1)+1, 0xff888888);
nSelectMenu = i;
- if((UI.nMouseLeft || UI.nMouseRight) && i == (int)nPauseIndex)
+ if((UI.nMouseLeft || UI.nMouseRight) && i == (uint32_t)nPauseIndex)
{
S.nToggleRunning = 1;
}
}
- MicroProfileDrawText(nX, nY, (uint32_t)-1, pMenuText[i], (uint32_t)strlen(pMenuText[i]));
+ MicroProfileDrawText(nX, nY, UINT32_MAX, pMenuText[i], (uint32_t)strlen(pMenuText[i]));
nX += (nLen+1) * (MICROPROFILE_TEXT_WIDTH+1);
}
- uint32_t nMenu = nSelectMenu != (uint32_t)-1 ? nSelectMenu : UI.nActiveMenu;
+ uint32_t nMenu = nSelectMenu != UINT32_MAX ? nSelectMenu : UI.nActiveMenu;
UI.nActiveMenu = nMenu;
- if((uint32_t)-1 != nMenu)
+ if(UINT32_MAX != nMenu)
{
nX = nMenuX[nMenu];
nY += MICROPROFILE_TEXT_HEIGHT+1;
@@ -2450,9 +2451,9 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
{
UI.nActiveMenu = nMenu;
}
- else if(nSelectMenu == (uint32_t)-1)
+ else if(nSelectMenu == UINT32_MAX)
{
- UI.nActiveMenu = (uint32_t)-1;
+ UI.nActiveMenu = UINT32_MAX;
}
MicroProfileDrawBox(nX, nY, nX + nWidth, nY + nHeight, 0xff000000|g_nMicroProfileBackColors[1]);
for(int i = 0; i < nNumLines; ++i)
@@ -2461,7 +2462,6 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
const char* pString = CB(i, &bSelected);
if(UI.nMouseY >= nY && UI.nMouseY < nY + MICROPROFILE_TEXT_HEIGHT + 1)
{
- bMouseOver = true;
if(UI.nMouseLeft || UI.nMouseRight)
{
CBClick[nMenu](i);
@@ -2469,7 +2469,7 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
MicroProfileDrawBox(nX, nY, nX + nWidth, nY + MICROPROFILE_TEXT_HEIGHT + 1, 0xff888888);
}
int nLen = snprintf(buffer, SBUF_SIZE-1, "%c %s", bSelected ? '*' : ' ' ,pString);
- MicroProfileDrawText(nX, nY, (uint32_t)-1, buffer, nLen);
+ MicroProfileDrawText(nX, nY, UINT32_MAX, buffer, nLen);
nY += MICROPROFILE_TEXT_HEIGHT+1;
}
}
@@ -2484,12 +2484,12 @@ void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight)
float fMaxMs = fToMs * S.nFlipMaxDisplay;
int nLen = snprintf(FrameTimeMessage, sizeof(FrameTimeMessage)-1, "Time[%6.2f] Avg[%6.2f] Max[%6.2f]", fMs, fAverageMs, fMaxMs);
pMenuText[nNumMenuItems++] = &FrameTimeMessage[0];
- MicroProfileDrawText(nWidth - nLen * (MICROPROFILE_TEXT_WIDTH+1), 0, -1, FrameTimeMessage, nLen);
+ MicroProfileDrawText(nWidth - nLen * (MICROPROFILE_TEXT_WIDTH+1), 0, UINT32_MAX, FrameTimeMessage, nLen);
}
}
-void MicroProfileMoveGraph()
+inline void MicroProfileMoveGraph()
{
int nZoom = UI.nMouseWheelDelta;
@@ -2536,9 +2536,9 @@ void MicroProfileMoveGraph()
UI.nOffsetY = 0;
}
-void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
+inline void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
{
- if((uint32_t)-1 != UI.nCustomActive)
+ if(UINT32_MAX != UI.nCustomActive)
{
MicroProfile& S = *MicroProfileGet();
MP_ASSERT(UI.nCustomActive < MICROPROFILE_CUSTOM_MAX);
@@ -2571,8 +2571,8 @@ void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
pColors[i] = S.TimerInfo[nTimerIndex].nColor;
}
- MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 3*MICROPROFILE_TEXT_WIDTH, nOffsetY, (uint32_t)-1, "Avg", sizeof("Avg")-1);
- MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 13*MICROPROFILE_TEXT_WIDTH, nOffsetY, (uint32_t)-1, "Max", sizeof("Max")-1);
+ MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 3*MICROPROFILE_TEXT_WIDTH, nOffsetY, UINT32_MAX, "Avg", sizeof("Avg")-1);
+ MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 13*MICROPROFILE_TEXT_WIDTH, nOffsetY, UINT32_MAX, "Max", sizeof("Max")-1);
for(uint32_t i = 0; i < nCount; ++i)
{
nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT);
@@ -2582,10 +2582,10 @@ void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
int nSize;
uint32_t nOffsetX = MICROPROFILE_CUSTOM_PADDING;
nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2f", pTimeAvg[i]);
- MicroProfileDrawText(nOffsetX, nOffsetY, (uint32_t)-1, Buffer, nSize);
+ MicroProfileDrawText(nOffsetX, nOffsetY, UINT32_MAX, Buffer, nSize);
nOffsetX += (nSize+2) * (MICROPROFILE_TEXT_WIDTH+1);
nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2f", pTimeMax[i]);
- MicroProfileDrawText(nOffsetX, nOffsetY, (uint32_t)-1, Buffer, nSize);
+ MicroProfileDrawText(nOffsetX, nOffsetY, UINT32_MAX, Buffer, nSize);
nOffsetX += (nSize+2) * (MICROPROFILE_TEXT_WIDTH+1);
nSize = snprintf(Buffer, sizeof(Buffer)-1, "%s:%s", S.GroupInfo[nGroupIndex].pName, pTimerInfo->pName);
MicroProfileDrawText(nOffsetX, nOffsetY, pTimerInfo->nColor, Buffer, nSize);
@@ -2599,9 +2599,9 @@ void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
nOffsetY = nOffsetYBase;
float* pMs = pCustom->nFlags & MICROPROFILE_CUSTOM_BAR_SOURCE_MAX ? pTimeMax : pTimeAvg;
const char* pString = pCustom->nFlags & MICROPROFILE_CUSTOM_BAR_SOURCE_MAX ? "Max" : "Avg";
- MicroProfileDrawText(nMaxOffsetX, nOffsetY, (uint32_t)-1, pString, static_cast<uint32_t>(strlen(pString)));
+ MicroProfileDrawText(nMaxOffsetX, nOffsetY, UINT32_MAX, pString, static_cast<uint32_t>(strlen(pString)));
int nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2fms", fReference);
- MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, (uint32_t)-1, Buffer, nSize);
+ MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, UINT32_MAX, Buffer, nSize);
for(uint32_t i = 0; i < nCount; ++i)
{
nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT);
@@ -2613,9 +2613,9 @@ void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
{
nOffsetY += 2*(1+MICROPROFILE_TEXT_HEIGHT);
const char* pString = pCustom->nFlags & MICROPROFILE_CUSTOM_STACK_SOURCE_MAX ? "Max" : "Avg";
- MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING, nOffsetY, (uint32_t)-1, pString, static_cast<uint32_t>(strlen(pString)));
+ MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING, nOffsetY, UINT32_MAX, pString, static_cast<uint32_t>(strlen(pString)));
int nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2fms", fReference);
- MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, (uint32_t)-1, Buffer, nSize);
+ MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, UINT32_MAX, Buffer, nSize);
nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT);
float fPosX = MICROPROFILE_CUSTOM_PADDING;
float* pMs = pCustom->nFlags & MICROPROFILE_CUSTOM_STACK_SOURCE_MAX ? pTimeMax : pTimeAvg;
@@ -2633,7 +2633,7 @@ void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight)
}
}
}
-void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
+inline void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
{
MICROPROFILE_SCOPE(g_MicroProfileDraw);
MicroProfile& S = *MicroProfileGet();
@@ -2668,7 +2668,7 @@ void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
UI.nHoverTime = 0;
UI.nHoverFrame = -1;
if(S.nDisplay != MP_DRAW_DETAILED)
- S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = -1;
+ S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = UINT32_MAX;
MicroProfileMoveGraph();
@@ -2798,13 +2798,13 @@ void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
- if(UI.nActiveMenu == -1 && !bMouseOverGraph)
+ if(UI.nActiveMenu == UINT32_MAX && !bMouseOverGraph)
{
if(UI.nHoverToken != MICROPROFILE_INVALID_TOKEN)
{
MicroProfileDrawFloatTooltip(UI.nMouseX, UI.nMouseY, UI.nHoverToken, UI.nHoverTime);
}
- else if(S.nContextSwitchHoverThreadAfter != -1 && S.nContextSwitchHoverThreadBefore != -1)
+ else if(S.nContextSwitchHoverThreadAfter != UINT32_MAX && S.nContextSwitchHoverThreadBefore != UINT32_MAX)
{
float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu());
MicroProfileStringArray ToolTip;
@@ -2820,7 +2820,7 @@ void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
MicroProfileStringArrayFormat(&ToolTip, "%6.2fms", fToMs * nDifference );
MicroProfileStringArrayAddLiteral(&ToolTip, "CPU");
MicroProfileStringArrayFormat(&ToolTip, "%d", S.nContextSwitchHoverCpu);
- MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, -1);
+ MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, UINT32_MAX);
}
@@ -2858,7 +2858,7 @@ void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
}
}
#endif
- MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, -1);
+ MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, UINT32_MAX);
}
if(UI.nMouseLeft)
{
@@ -2883,7 +2883,7 @@ void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight)
#endif
m.unlock();
}
- else if(UI.nCustomActive != (uint32_t)-1)
+ else if(UI.nCustomActive != UINT32_MAX)
{
std::recursive_mutex& m = MicroProfileGetMutex();
m.lock();
@@ -3179,7 +3179,7 @@ void MicroProfileLoadPreset(const char* pSuffix)
{
if(0 == MP_STRCASECMP(pGroupName, S.GroupInfo[j].pName))
{
- S.nActiveGroupWanted |= (1ll << j);
+ S.nActiveGroupWanted |= (1ULL << j);
}
}
}
@@ -3212,7 +3212,7 @@ void MicroProfileLoadPreset(const char* pSuffix)
uint64_t nGroupIndex = S.TimerInfo[j].nGroupIndex;
if(0 == MP_STRCASECMP(pGraphName, S.TimerInfo[j].pName) && 0 == MP_STRCASECMP(pGraphGroupName, S.GroupInfo[nGroupIndex].pName))
{
- MicroProfileToken nToken = MicroProfileMakeToken(1ll << nGroupIndex, (uint16_t)j);
+ MicroProfileToken nToken = MicroProfileMakeToken(1ULL << nGroupIndex, (uint16_t)j);
S.Graph[i].nToken = nToken; // note: group index is stored here but is checked without in MicroProfileToggleGraph()!
S.TimerInfo[j].bGraph = true;
if(nToken != nPrevToken)
@@ -3226,7 +3226,7 @@ void MicroProfileLoadPreset(const char* pSuffix)
}
}
-uint32_t MicroProfileCustomGroupFind(const char* pCustomName)
+inline uint32_t MicroProfileCustomGroupFind(const char* pCustomName)
{
for(uint32_t i = 0; i < UI.nCustomCount; ++i)
{
@@ -3235,10 +3235,10 @@ uint32_t MicroProfileCustomGroupFind(const char* pCustomName)
return i;
}
}
- return (uint32_t)-1;
+ return UINT32_MAX;
}
-uint32_t MicroProfileCustomGroup(const char* pCustomName)
+inline uint32_t MicroProfileCustomGroup(const char* pCustomName)
{
for(uint32_t i = 0; i < UI.nCustomCount; ++i)
{
@@ -3251,7 +3251,7 @@ uint32_t MicroProfileCustomGroup(const char* pCustomName)
uint32_t nIndex = UI.nCustomCount;
UI.nCustomCount++;
memset(&UI.Custom[nIndex], 0, sizeof(UI.Custom[nIndex]));
- uint32_t nLen = (uint32_t)strlen(pCustomName);
+ size_t nLen = strlen(pCustomName);
if(nLen > MICROPROFILE_NAME_MAX_LEN-1)
nLen = MICROPROFILE_NAME_MAX_LEN-1;
memcpy(&UI.Custom[nIndex].pName[0], pCustomName, nLen);
@@ -3271,7 +3271,7 @@ void MicroProfileCustomGroup(const char* pCustomName, uint32_t nMaxTimers, uint3
UI.Custom[nIndex].nAggregateFlip = nAggregateFlip;
}
-void MicroProfileCustomGroupEnable(uint32_t nIndex)
+inline void MicroProfileCustomGroupEnable(uint32_t nIndex)
{
if(nIndex < UI.nCustomCount)
{
@@ -3309,7 +3309,7 @@ void MicroProfileCustomGroupEnable(uint32_t nIndex)
void MicroProfileCustomGroupToggle(const char* pCustomName)
{
uint32_t nIndex = MicroProfileCustomGroupFind(pCustomName);
- if(nIndex == (uint32_t)-1 || nIndex == UI.nCustomActive)
+ if(nIndex == UINT32_MAX || nIndex == UI.nCustomActive)
{
MicroProfileCustomGroupDisable();
}
@@ -3328,13 +3328,13 @@ void MicroProfileCustomGroupDisable()
{
MicroProfile& S = *MicroProfileGet();
S.nForceGroupUI = 0;
- UI.nCustomActive = (uint32_t)-1;
+ UI.nCustomActive = UINT32_MAX;
}
void MicroProfileCustomGroupAddTimer(const char* pCustomName, const char* pGroup, const char* pTimer)
{
uint32_t nIndex = MicroProfileCustomGroupFind(pCustomName);
- if((uint32_t)-1 == nIndex)
+ if(UINT32_MAX == nIndex)
{
return;
}
@@ -3344,7 +3344,7 @@ void MicroProfileCustomGroupAddTimer(const char* pCustomName, const char* pGroup
MP_ASSERT(nToken != MICROPROFILE_INVALID_TOKEN); //Timer must be registered first.
UI.Custom[nIndex].pTimers[nTimerIndex] = nToken;
uint16_t nGroup = MicroProfileGetGroupIndex(nToken);
- UI.Custom[nIndex].nGroupMask |= (1ll << nGroup);
+ UI.Custom[nIndex].nGroupMask |= (1ULL << nGroup);
UI.Custom[nIndex].nNumTimers++;
}
diff --git a/externals/opus/CMakeLists.txt b/externals/opus/CMakeLists.txt
index cbb393272..94a86551f 100644
--- a/externals/opus/CMakeLists.txt
+++ b/externals/opus/CMakeLists.txt
@@ -203,7 +203,11 @@ endif()
target_compile_definitions(opus PRIVATE OPUS_BUILD ENABLE_HARDENING)
if(NOT MSVC)
- target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=2)
+ if(MINGW)
+ target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=0)
+ else()
+ target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=2)
+ endif()
endif()
# It is strongly recommended to uncomment one of these VAR_ARRAYS: Use C99
diff --git a/externals/sirit b/externals/sirit
-Subproject a712959f1e373a33b48042b5934e288a243d595
+Subproject eefca56afd49379bdebc97ded8b480839f93088
diff --git a/externals/unicorn b/externals/unicorn
deleted file mode 160000
-Subproject 73f45735354396766a4bfb26d0b96b06e5cf31b
diff --git a/externals/xbyak b/externals/xbyak
new file mode 160000
+Subproject c306b8e5786eeeb87b8925a8af5c3bf057ff5a9
diff --git a/externals/zlib/CMakeLists.txt b/externals/zlib/CMakeLists.txt
deleted file mode 100644
index 0ca32aae4..000000000
--- a/externals/zlib/CMakeLists.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-project(zlib C)
-
-include(CheckTypeSize)
-include(CheckFunctionExists)
-include(CheckIncludeFile)
-
-check_include_file(sys/types.h HAVE_SYS_TYPES_H)
-check_include_file(stdint.h HAVE_STDINT_H)
-check_include_file(stddef.h HAVE_STDDEF_H)
-
-# Check to see if we have large file support
-set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1)
-# We add these other definitions here because CheckTypeSize.cmake
-# in CMake 2.4.x does not automatically do so and we want
-# compatibility with CMake 2.4.x.
-if(HAVE_SYS_TYPES_H)
- list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H)
-endif()
-if(HAVE_STDINT_H)
- list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H)
-endif()
-if(HAVE_STDDEF_H)
- list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H)
-endif()
-check_type_size(off64_t OFF64_T)
-if(HAVE_OFF64_T)
- add_definitions(-D_LARGEFILE64_SOURCE=1)
-endif()
-set(CMAKE_REQUIRED_DEFINITIONS) # clear variable
-
-# Check for fseeko
-check_function_exists(fseeko HAVE_FSEEKO)
-if(NOT HAVE_FSEEKO)
- add_definitions(-DNO_FSEEKO)
-endif()
-
-# Check for unistd.h
-check_include_file(unistd.h HAVE_UNISTD_H)
-if(HAVE_UNISTD_H)
- add_definitions(-DHAVE_UNISTD_H)
-endif()
-
-if(MSVC)
- add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
- add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
-endif()
-
-add_library(z STATIC
- zlib/adler32.c
- zlib/compress.c
- zlib/crc32.c
- zlib/crc32.h
- zlib/deflate.c
- zlib/deflate.h
- zlib/gzclose.c
- zlib/gzguts.h
- zlib/gzlib.c
- zlib/gzread.c
- zlib/gzwrite.c
- zlib/inffast.h
- zlib/inffixed.h
- zlib/inflate.c
- zlib/inflate.h
- zlib/infback.c
- zlib/inftrees.c
- zlib/inftrees.h
- zlib/inffast.c
- zlib/trees.c
- zlib/trees.h
- zlib/uncompr.c
- zlib/zconf.h
- zlib/zlib.h
- zlib/zutil.c
- zlib/zutil.h
-)
-add_library(ZLIB::ZLIB ALIAS z)
-
-target_include_directories(z
-PUBLIC
- zlib/
-)
diff --git a/externals/zlib/zlib b/externals/zlib/zlib
deleted file mode 160000
-Subproject cacf7f1d4e3d44d871b605da3b647f07d718623
diff --git a/externals/zstd b/externals/zstd
deleted file mode 160000
-Subproject 470344d33e1d52a2ada75d278466da8d4ee2faf
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e40e9b0a5..dbda528ce 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,7 +32,6 @@ if (MSVC)
# /Zc:inline - Let codegen omit inline functions in object files
# /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
add_compile_options(
- /W3
/MP
/Zi
/Zo
@@ -43,6 +42,13 @@ if (MSVC)
/Zc:externConstexpr
/Zc:inline
/Zc:throwingNew
+
+ # Warnings
+ /W3
+ /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
+ /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
+ /we4555 # Expression has no effect; expected expression with side-effect
+ /we4834 # Discarding return value of function with 'nodiscard' attribute
)
# /GS- - No stack buffer overflow checks
@@ -53,10 +59,26 @@ if (MSVC)
else()
add_compile_options(
-Wall
+ -Werror=implicit-fallthrough
+ -Werror=missing-declarations
-Werror=reorder
+ -Werror=unused-result
+ -Wextra
+ -Wmissing-declarations
-Wno-attributes
+ -Wno-unused-parameter
)
+ # TODO: Remove when we update to a GCC compiler that enables this
+ # by default (i.e. GCC 10 or newer).
+ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
+ add_compile_options(-fconcepts)
+ endif()
+
+ if (ARCHITECTURE_x86_64)
+ add_compile_options("-mcx16")
+ endif()
+
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
add_compile_options("-stdlib=libc++")
endif()
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index c381dbe1d..68c67507b 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -7,24 +7,59 @@ add_library(audio_core STATIC
audio_out.h
audio_renderer.cpp
audio_renderer.h
+ behavior_info.cpp
+ behavior_info.h
buffer.h
codec.cpp
codec.h
+ command_generator.cpp
+ command_generator.h
+ common.h
+ effect_context.cpp
+ effect_context.h
+ info_updater.cpp
+ info_updater.h
+ memory_pool.cpp
+ memory_pool.h
+ mix_context.cpp
+ mix_context.h
null_sink.h
sink.h
+ sink_context.cpp
+ sink_context.h
sink_details.cpp
sink_details.h
sink_stream.h
+ splitter_context.cpp
+ splitter_context.h
stream.cpp
stream.h
time_stretch.cpp
time_stretch.h
+ voice_context.cpp
+ voice_context.h
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
)
create_target_directory_groups(audio_core)
+if (NOT MSVC)
+ target_compile_options(audio_core PRIVATE
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=reorder
+ -Werror=sign-compare
+ -Werror=unused-variable
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+
+ -Wno-sign-conversion
+ )
+endif()
+
target_link_libraries(audio_core PUBLIC common core)
target_link_libraries(audio_core PRIVATE SoundTouch)
diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp
index f65bf64f7..f34a5b9f3 100644
--- a/src/audio_core/algorithm/filter.cpp
+++ b/src/audio_core/algorithm/filter.cpp
@@ -55,7 +55,8 @@ void Filter::Process(std::vector<s16>& signal) {
/// @param total_count The total number of biquads to be cascaded.
/// @param index 0-index of the biquad to calculate the Q value for.
static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
- const double pole = M_PI * (2 * index + 1) / (4.0 * total_count);
+ const auto pole =
+ M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
return 1.0 / (2.0 * std::cos(pole));
}
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
index 49ab9d3e1..699fcb84c 100644
--- a/src/audio_core/algorithm/interpolate.cpp
+++ b/src/audio_core/algorithm/interpolate.cpp
@@ -146,7 +146,7 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return {};
if (ratio <= 0) {
- LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio);
+ LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
return input;
}
@@ -164,7 +164,8 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
const std::size_t num_frames{input.size() / 2};
std::vector<s16> output;
- output.reserve(static_cast<std::size_t>(input.size() / ratio + InterpolationState::taps));
+ output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
+ InterpolationState::taps));
for (std::size_t frame{}; frame < num_frames; ++frame) {
const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
@@ -197,4 +198,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return output;
}
+void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
+ const std::array<s16, 512>& lut = [pitch] {
+ if (pitch > 0xaaaa) {
+ return curve_lut0;
+ }
+ if (pitch <= 0x8000) {
+ return curve_lut1;
+ }
+ return curve_lut2;
+ }();
+
+ std::size_t index{};
+
+ for (std::size_t i = 0; i < sample_count; i++) {
+ const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
+ const auto l0 = lut[lut_index + 0];
+ const auto l1 = lut[lut_index + 1];
+ const auto l2 = lut[lut_index + 2];
+ const auto l3 = lut[lut_index + 3];
+
+ const auto s0 = static_cast<s32>(input[index]);
+ const auto s1 = static_cast<s32>(input[index + 1]);
+ const auto s2 = static_cast<s32>(input[index + 2]);
+ const auto s3 = static_cast<s32>(input[index + 3]);
+
+ output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
+ fraction += pitch;
+ index += (fraction >> 15);
+ fraction &= 0x7fff;
+ }
+}
+
} // 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>
return Interpolate(state, std::move(input), ratio);
}
+/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
+void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
+
} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index c187d8ac5..a7e851bb8 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -2,92 +2,46 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "audio_core/algorithm/interpolate.h"
+#include <vector>
+
#include "audio_core/audio_out.h"
#include "audio_core/audio_renderer.h"
-#include "audio_core/codec.h"
-#include "common/assert.h"
+#include "audio_core/common.h"
+#include "audio_core/info_updater.h"
+#include "audio_core/voice_context.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
+#include "core/settings.h"
namespace AudioCore {
-
-constexpr u32 STREAM_SAMPLE_RATE{48000};
-constexpr u32 STREAM_NUM_CHANNELS{2};
-
-class AudioRenderer::VoiceState {
-public:
- bool IsPlaying() const {
- return is_in_use && info.play_state == PlayState::Started;
- }
-
- const VoiceOutStatus& GetOutStatus() const {
- return out_status;
- }
-
- const VoiceInfo& GetInfo() const {
- return info;
- }
-
- VoiceInfo& GetInfo() {
- return info;
- }
-
- void SetWaveIndex(std::size_t index);
- std::vector<s16> DequeueSamples(std::size_t sample_count, Memory::Memory& memory);
- void UpdateState();
- void RefreshBuffer(Memory::Memory& memory);
-
-private:
- bool is_in_use{};
- bool is_refresh_pending{};
- std::size_t wave_index{};
- std::size_t offset{};
- Codec::ADPCMState adpcm_state{};
- InterpolationState interp_state{};
- std::vector<s16> samples;
- VoiceOutStatus out_status{};
- VoiceInfo info{};
-};
-
-class AudioRenderer::EffectState {
-public:
- const EffectOutStatus& GetOutStatus() const {
- return out_status;
- }
-
- const EffectInStatus& GetInfo() const {
- return info;
- }
-
- EffectInStatus& GetInfo() {
- return info;
- }
-
- void UpdateState(Memory::Memory& memory);
-
-private:
- EffectOutStatus out_status{};
- EffectInStatus info{};
-};
-AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Memory::Memory& memory_,
- AudioRendererParameter params,
+AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
+ AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event,
std::size_t instance_number)
- : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
- effects(params.effect_count), memory{memory_} {
-
+ : worker_params{params}, buffer_event{buffer_event},
+ memory_pool_info(params.effect_count + params.voice_count * 4),
+ voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
+ sink_context(params.sink_count), splitter_context(),
+ voices(params.voice_count), memory{memory_},
+ command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
+ memory),
+ temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
+ behavior_info.SetUserRevision(params.revision);
+ splitter_context.Initialize(behavior_info, params.splitter_count,
+ params.num_splitter_send_channels);
+ mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>();
- stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
- fmt::format("AudioRenderer-Instance{}", instance_number),
- [=]() { buffer_event->Signal(); });
+ stream =
+ audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
+ fmt::format("AudioRenderer-Instance{}", instance_number),
+ [=]() { buffer_event->Signal(); });
audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
QueueMixedBuffer(2);
+ QueueMixedBuffer(3);
}
AudioRenderer::~AudioRenderer() = default;
@@ -108,257 +62,200 @@ Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
-static constexpr u32 VersionFromRevision(u32_le rev) {
- // "REV7" -> 7
- return ((rev >> 24) & 0xff) - 0x30;
+static constexpr s16 ClampToS16(s32 value) {
+ return static_cast<s16>(std::clamp(value, -32768, 32767));
}
-std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
- // Copy UpdateDataHeader struct
- UpdateDataHeader config{};
- std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
- u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
-
- // Copy MemoryPoolInfo structs
- std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
- std::memcpy(mem_pool_info.data(),
- input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
- memory_pool_count * sizeof(MemoryPoolInfo));
-
- // Copy VoiceInfo structs
- std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
- config.memory_pools_size + config.voice_resource_size};
- for (auto& voice : voices) {
- std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
- voice_offset += sizeof(VoiceInfo);
- }
+ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
+ std::vector<u8>& output_params) {
- std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
- config.memory_pools_size + config.voice_resource_size +
- config.voices_size};
- for (auto& effect : effects) {
- std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
- effect_offset += sizeof(EffectInStatus);
- }
+ InfoUpdater info_updater{input_params, output_params, behavior_info};
- // Update memory pool state
- std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
- for (std::size_t index = 0; index < memory_pool.size(); ++index) {
- if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
- memory_pool[index].state = MemoryPoolStates::Attached;
- } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
- memory_pool[index].state = MemoryPoolStates::Detached;
- }
+ if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
+ LOG_ERROR(Audio, "Failed to update behavior info input parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Update voices
- for (auto& voice : voices) {
- voice.UpdateState();
- if (!voice.GetInfo().is_in_use) {
- continue;
- }
- if (voice.GetInfo().is_new) {
- voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
- }
+ if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
+ LOG_ERROR(Audio, "Failed to update memory pool parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- for (auto& effect : effects) {
- effect.UpdateState(memory);
+ if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
+ LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Release previous buffers and queue next ones for playback
- ReleaseAndQueueBuffers();
-
- // Copy output header
- UpdateDataHeader response_data{worker_params};
- std::vector<u8> output_params(response_data.total_size);
- const auto audren_revision = VersionFromRevision(config.revision);
- if (audren_revision >= 5) {
- response_data.frame_count = 0x10;
- response_data.total_size += 0x10;
- }
- std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
-
- // Copy output memory pool entries
- std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
- response_data.memory_pools_size);
-
- // Copy output voice status
- std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
- for (const auto& voice : voices) {
- std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
- sizeof(VoiceOutStatus));
- voice_out_status_offset += sizeof(VoiceOutStatus);
+ if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
+ LOG_ERROR(Audio, "Failed to update voice parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- std::size_t effect_out_status_offset{
- sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
- response_data.voice_resource_size};
- for (const auto& effect : effects) {
- std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
- sizeof(EffectOutStatus));
- effect_out_status_offset += sizeof(EffectOutStatus);
+ // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
+ if (!info_updater.UpdateEffects(effect_context, true)) {
+ LOG_ERROR(Audio, "Failed to update effect parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- return output_params;
-}
-
-void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
- wave_index = index & 3;
- is_refresh_pending = true;
-}
-std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(std::size_t sample_count,
- Memory::Memory& memory) {
- if (!IsPlaying()) {
- return {};
+ if (behavior_info.IsSplitterSupported()) {
+ if (!info_updater.UpdateSplitterInfo(splitter_context)) {
+ LOG_ERROR(Audio, "Failed to update splitter parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
}
- if (is_refresh_pending) {
- RefreshBuffer(memory);
- }
+ auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
+ splitter_context, effect_context);
- const std::size_t max_size{samples.size() - offset};
- const std::size_t dequeue_offset{offset};
- std::size_t size{sample_count * STREAM_NUM_CHANNELS};
- if (size > max_size) {
- size = max_size;
+ if (mix_result.IsError()) {
+ LOG_ERROR(Audio, "Failed to update mix parameters");
+ return mix_result;
}
- out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
- offset += size;
-
- const auto& wave_buffer{info.wave_buffer[wave_index]};
- if (offset == samples.size()) {
- offset = 0;
-
- if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
- SetWaveIndex(wave_index + 1);
- }
-
- if (wave_buffer.buffer_sz) {
- out_status.wave_buffer_consumed++;
- }
-
- if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
- info.play_state = PlayState::Paused;
- }
+ // TODO(ogniK): Sinks
+ if (!info_updater.UpdateSinks(sink_context)) {
+ LOG_ERROR(Audio, "Failed to update sink parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
-}
-
-void AudioRenderer::VoiceState::UpdateState() {
- if (is_in_use && !info.is_in_use) {
- // No longer in use, reset state
- is_refresh_pending = true;
- wave_index = 0;
- offset = 0;
- out_status = {};
+ // TODO(ogniK): Performance buffer
+ if (!info_updater.UpdatePerformanceBuffer()) {
+ LOG_ERROR(Audio, "Failed to update performance buffer parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- is_in_use = info.is_in_use;
-}
-void AudioRenderer::VoiceState::RefreshBuffer(Memory::Memory& memory) {
- const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
- const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
- std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
- memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
-
- switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
- case Codec::PcmFormat::Int16: {
- // PCM16 is played as-is
- break;
- }
- case Codec::PcmFormat::Adpcm: {
- // Decode ADPCM to PCM16
- Codec::ADPCM_Coeff coeffs;
- memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
- new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
- new_samples.size() * sizeof(s16), coeffs, adpcm_state);
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
- break;
+ if (!info_updater.UpdateErrorInfo(behavior_info)) {
+ LOG_ERROR(Audio, "Failed to update error info");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- switch (info.channel_count) {
- case 1:
- // 1 channel is upsampled to 2 channel
- samples.resize(new_samples.size() * 2);
- for (std::size_t index = 0; index < new_samples.size(); ++index) {
- samples[index * 2] = new_samples[index];
- samples[index * 2 + 1] = new_samples[index];
+ if (behavior_info.IsElapsedFrameCountSupported()) {
+ if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
+ LOG_ERROR(Audio, "Failed to update renderer info");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- break;
- case 2: {
- // 2 channel is played as is
- samples = std::move(new_samples);
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
- break;
}
+ // TODO(ogniK): Statistics
- // Only interpolate when necessary, expensive.
- if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
- samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
- STREAM_SAMPLE_RATE);
+ if (!info_updater.WriteOutputHeader()) {
+ LOG_ERROR(Audio, "Failed to write output header");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- is_refresh_pending = false;
-}
+ // TODO(ogniK): Check when all sections are implemented
-void AudioRenderer::EffectState::UpdateState(Memory::Memory& memory) {
- if (info.is_new) {
- out_status.state = EffectStatus::New;
- } else {
- if (info.type == Effect::Aux) {
- ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
- "Aux buffers tried to update");
- }
+ if (!info_updater.CheckConsumedSize()) {
+ LOG_ERROR(Audio, "Audio buffers were not consumed!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
-}
-static constexpr s16 ClampToS16(s32 value) {
- return static_cast<s16>(std::clamp(value, -32768, 32767));
+ ReleaseAndQueueBuffers();
+
+ return RESULT_SUCCESS;
}
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
- constexpr std::size_t BUFFER_SIZE{512};
- std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
+ command_generator.PreCommand();
+ // Clear mix buffers before our next operation
+ command_generator.ClearMixBuffers();
- for (auto& voice : voices) {
- if (!voice.IsPlaying()) {
- continue;
+ // If the splitter is not in use, sort our mixes
+ if (!splitter_context.UsingSplitter()) {
+ mix_context.SortInfo();
+ }
+ // Sort our voices
+ voice_context.SortInfo();
+
+ // Handle samples
+ command_generator.GenerateVoiceCommands();
+ command_generator.GenerateSubMixCommands();
+ command_generator.GenerateFinalMixCommands();
+
+ command_generator.PostCommand();
+ // Base sample size
+ std::size_t BUFFER_SIZE{worker_params.sample_count};
+ // Samples
+ std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
+ // Make sure to clear our samples
+ std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
+
+ if (sink_context.InUse()) {
+ const auto stream_channel_count = stream->GetNumChannels();
+ const auto buffer_offsets = sink_context.OutputBuffers();
+ const auto channel_count = buffer_offsets.size();
+ const auto& final_mix = mix_context.GetFinalMixInfo();
+ const auto& in_params = final_mix.GetInParams();
+ std::vector<s32*> mix_buffers(channel_count);
+ for (std::size_t i = 0; i < channel_count; i++) {
+ mix_buffers[i] =
+ command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
}
- std::size_t offset{};
- s64 samples_remaining{BUFFER_SIZE};
- while (samples_remaining > 0) {
- const std::vector<s16> samples{voice.DequeueSamples(samples_remaining, memory)};
-
- if (samples.empty()) {
- break;
- }
-
- samples_remaining -= samples.size() / stream->GetNumChannels();
-
- for (const auto& sample : samples) {
- const s32 buffer_sample{buffer[offset]};
- buffer[offset++] =
- ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
+ for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
+ if (channel_count == 1) {
+ const auto sample = ClampToS16(mix_buffers[0][i]);
+ buffer[i * stream_channel_count + 0] = sample;
+ if (stream_channel_count > 1) {
+ buffer[i * stream_channel_count + 1] = sample;
+ }
+ if (stream_channel_count == 6) {
+ buffer[i * stream_channel_count + 2] = sample;
+ buffer[i * stream_channel_count + 4] = sample;
+ buffer[i * stream_channel_count + 5] = sample;
+ }
+ } else if (channel_count == 2) {
+ const auto l_sample = ClampToS16(mix_buffers[0][i]);
+ const auto r_sample = ClampToS16(mix_buffers[1][i]);
+ if (stream_channel_count == 1) {
+ buffer[i * stream_channel_count + 0] = l_sample;
+ } else if (stream_channel_count == 2) {
+ buffer[i * stream_channel_count + 0] = l_sample;
+ buffer[i * stream_channel_count + 1] = r_sample;
+ } else if (stream_channel_count == 6) {
+ buffer[i * stream_channel_count + 0] = l_sample;
+ buffer[i * stream_channel_count + 1] = r_sample;
+
+ buffer[i * stream_channel_count + 2] =
+ ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
+
+ buffer[i * stream_channel_count + 4] = l_sample;
+ buffer[i * stream_channel_count + 5] = r_sample;
+ }
+
+ } else if (channel_count == 6) {
+ const auto fl_sample = ClampToS16(mix_buffers[0][i]);
+ const auto fr_sample = ClampToS16(mix_buffers[1][i]);
+ const auto fc_sample = ClampToS16(mix_buffers[2][i]);
+ const auto lf_sample = ClampToS16(mix_buffers[3][i]);
+ const auto bl_sample = ClampToS16(mix_buffers[4][i]);
+ const auto br_sample = ClampToS16(mix_buffers[5][i]);
+
+ if (stream_channel_count == 1) {
+ buffer[i * stream_channel_count + 0] = fc_sample;
+ } else if (stream_channel_count == 2) {
+ buffer[i * stream_channel_count + 0] =
+ static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
+ 0.2612f * static_cast<float>(fc_sample) +
+ 0.3694f * static_cast<float>(bl_sample));
+ buffer[i * stream_channel_count + 1] =
+ static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
+ 0.2612f * static_cast<float>(fc_sample) +
+ 0.3694f * static_cast<float>(br_sample));
+ } else if (stream_channel_count == 6) {
+ buffer[i * stream_channel_count + 0] = fl_sample;
+ buffer[i * stream_channel_count + 1] = fr_sample;
+ buffer[i * stream_channel_count + 2] = fc_sample;
+ buffer[i * stream_channel_count + 3] = lf_sample;
+ buffer[i * stream_channel_count + 4] = bl_sample;
+ buffer[i * stream_channel_count + 5] = br_sample;
+ }
}
}
}
+
audio_out->QueueBuffer(stream, tag, std::move(buffer));
+ elapsed_frame_count++;
+ voice_context.UpdateStateByDspShared();
}
void AudioRenderer::ReleaseAndQueueBuffers() {
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index c0fae669e..2fd93e058 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -8,11 +8,20 @@
#include <memory>
#include <vector>
+#include "audio_core/behavior_info.h"
+#include "audio_core/command_generator.h"
+#include "audio_core/common.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/memory_pool.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/sink_context.h"
+#include "audio_core/splitter_context.h"
#include "audio_core/stream.h"
+#include "audio_core/voice_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/hle/kernel/object.h"
+#include "core/hle/result.h"
namespace Core::Timing {
class CoreTiming;
@@ -22,211 +31,30 @@ namespace Kernel {
class WritableEvent;
}
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
namespace AudioCore {
+using DSPStateHolder = std::array<VoiceState*, 6>;
class AudioOut;
-enum class PlayState : u8 {
- Started = 0,
- Stopped = 1,
- Paused = 2,
+struct RendererInfo {
+ u64_le elasped_frame_count{};
+ INSERT_PADDING_WORDS(2);
};
-
-enum class Effect : u8 {
- None = 0,
- Aux = 2,
-};
-
-enum class EffectStatus : u8 {
- None = 0,
- New = 1,
-};
-
-struct AudioRendererParameter {
- u32_le sample_rate;
- u32_le sample_count;
- u32_le mix_buffer_count;
- u32_le submix_count;
- u32_le voice_count;
- u32_le sink_count;
- u32_le effect_count;
- u32_le performance_frame_count;
- u8 is_voice_drop_enabled;
- u8 unknown_21;
- u8 unknown_22;
- u8 execution_mode;
- u32_le splitter_count;
- u32_le num_splitter_send_channels;
- u32_le unknown_30;
- u32_le revision;
-};
-static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
-
-enum class MemoryPoolStates : u32 { // Should be LE
- Invalid = 0x0,
- Unknown = 0x1,
- RequestDetach = 0x2,
- Detached = 0x3,
- RequestAttach = 0x4,
- Attached = 0x5,
- Released = 0x6,
-};
-
-struct MemoryPoolEntry {
- MemoryPoolStates state;
- u32_le unknown_4;
- u32_le unknown_8;
- u32_le unknown_c;
-};
-static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
-
-struct MemoryPoolInfo {
- u64_le pool_address;
- u64_le pool_size;
- MemoryPoolStates pool_state;
- INSERT_PADDING_WORDS(3); // Unknown
-};
-static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
-struct BiquadFilter {
- u8 enable;
- INSERT_PADDING_BYTES(1);
- std::array<s16_le, 3> numerator;
- std::array<s16_le, 2> denominator;
-};
-static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
-
-struct WaveBuffer {
- u64_le buffer_addr;
- u64_le buffer_sz;
- s32_le start_sample_offset;
- s32_le end_sample_offset;
- u8 is_looping;
- u8 end_of_stream;
- u8 sent_to_server;
- INSERT_PADDING_BYTES(5);
- u64 context_addr;
- u64 context_sz;
- INSERT_PADDING_BYTES(8);
-};
-static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
-
-struct VoiceInfo {
- u32_le id;
- u32_le node_id;
- u8 is_new;
- u8 is_in_use;
- PlayState play_state;
- u8 sample_format;
- u32_le sample_rate;
- u32_le priority;
- u32_le sorting_order;
- u32_le channel_count;
- float_le pitch;
- float_le volume;
- std::array<BiquadFilter, 2> biquad_filter;
- u32_le wave_buffer_count;
- u32_le wave_buffer_head;
- INSERT_PADDING_WORDS(1);
- u64_le additional_params_addr;
- u64_le additional_params_sz;
- u32_le mix_id;
- u32_le splitter_info_id;
- std::array<WaveBuffer, 4> wave_buffer;
- std::array<u32_le, 6> voice_channel_resource_ids;
- INSERT_PADDING_BYTES(24);
-};
-static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
-
-struct VoiceOutStatus {
- u64_le played_sample_count;
- u32_le wave_buffer_consumed;
- u32_le voice_drops_count;
-};
-static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
-
-struct AuxInfo {
- std::array<u8, 24> input_mix_buffers;
- std::array<u8, 24> output_mix_buffers;
- u32_le mix_buffer_count;
- u32_le sample_rate; // Stored in the aux buffer currently
- u32_le sample_count;
- u64_le send_buffer_info;
- u64_le send_buffer_base;
-
- u64_le return_buffer_info;
- u64_le return_buffer_base;
-};
-static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
-
-struct EffectInStatus {
- Effect type;
- u8 is_new;
- u8 is_enabled;
- INSERT_PADDING_BYTES(1);
- u32_le mix_id;
- u64_le buffer_base;
- u64_le buffer_sz;
- s32_le priority;
- INSERT_PADDING_BYTES(4);
- union {
- std::array<u8, 0xa0> raw;
- AuxInfo aux_info;
- };
-};
-static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
-
-struct EffectOutStatus {
- EffectStatus state;
- INSERT_PADDING_BYTES(0xf);
-};
-static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
-
-struct UpdateDataHeader {
- UpdateDataHeader() {}
-
- explicit UpdateDataHeader(const AudioRendererParameter& config) {
- revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
- behavior_size = 0xb0;
- memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
- voices_size = config.voice_count * 0x10;
- voice_resource_size = 0x0;
- effects_size = config.effect_count * 0x10;
- mixes_size = 0x0;
- sinks_size = config.sink_count * 0x20;
- performance_manager_size = 0x10;
- frame_count = 0;
- total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
- effects_size + sinks_size + performance_manager_size;
- }
-
- u32_le revision{};
- u32_le behavior_size{};
- u32_le memory_pools_size{};
- u32_le voices_size{};
- u32_le voice_resource_size{};
- u32_le effects_size{};
- u32_le mixes_size{};
- u32_le sinks_size{};
- u32_le performance_manager_size{};
- INSERT_PADDING_WORDS(1);
- u32_le frame_count{};
- INSERT_PADDING_WORDS(4);
- u32_le total_size{};
-};
-static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
+static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
class AudioRenderer {
public:
- AudioRenderer(Core::Timing::CoreTiming& core_timing, Memory::Memory& memory_,
- AudioRendererParameter params,
+ AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
+ AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
~AudioRenderer();
- std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params);
+ ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
+ std::vector<u8>& output_params);
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
u32 GetSampleRate() const;
@@ -235,16 +63,23 @@ public:
Stream::State GetStreamState() const;
private:
- class EffectState;
- class VoiceState;
+ BehaviorInfo behavior_info{};
- AudioRendererParameter worker_params;
+ AudioCommon::AudioRendererParameter worker_params;
std::shared_ptr<Kernel::WritableEvent> buffer_event;
+ std::vector<ServerMemoryPoolInfo> memory_pool_info;
+ VoiceContext voice_context;
+ EffectContext effect_context;
+ MixContext mix_context;
+ SinkContext sink_context;
+ SplitterContext splitter_context;
std::vector<VoiceState> voices;
- std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
StreamPtr stream;
- Memory::Memory& memory;
+ Core::Memory::Memory& memory;
+ CommandGenerator command_generator;
+ std::size_t elapsed_frame_count{};
+ std::vector<s32> temp_mix_buffer{};
};
} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
new file mode 100644
index 000000000..3c2e3e6f1
--- /dev/null
+++ b/src/audio_core/behavior_info.cpp
@@ -0,0 +1,105 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "audio_core/behavior_info.h"
+#include "audio_core/common.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
+BehaviorInfo::~BehaviorInfo() = default;
+
+bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
+ if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ OutParams params{};
+ std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size());
+ params.error_count = static_cast<u32_le>(error_count);
+ std::memcpy(buffer.data() + offset, &params, sizeof(OutParams));
+ return true;
+}
+
+void BehaviorInfo::ClearError() {
+ error_count = 0;
+}
+
+void BehaviorInfo::UpdateFlags(u64_le dest_flags) {
+ flags = dest_flags;
+}
+
+void BehaviorInfo::SetUserRevision(u32_le revision) {
+ user_revision = revision;
+}
+
+u32_le BehaviorInfo::GetUserRevision() const {
+ return user_revision;
+}
+
+u32_le BehaviorInfo::GetProcessRevision() const {
+ return process_revision;
+}
+
+bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
+ return AudioCommon::IsRevisionSupported(2, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterSupported() const {
+ return AudioCommon::IsRevisionSupported(2, user_revision);
+}
+
+bool BehaviorInfo::IsLongSizePreDelaySupported() const {
+ return AudioCommon::IsRevisionSupported(3, user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(4, user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(1, user_revision);
+}
+
+bool BehaviorInfo::IsElapsedFrameCountSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
+ return (flags & 1) != 0;
+}
+
+bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
+ return AudioCommon::IsRevisionSupported(7, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterBugFixed() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
+ dst.error_count = static_cast<u32>(error_count);
+ std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
new file mode 100644
index 000000000..512a4ebe3
--- /dev/null
+++ b/src/audio_core/behavior_info.h
@@ -0,0 +1,72 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+class BehaviorInfo {
+public:
+ struct ErrorInfo {
+ u32_le result{};
+ INSERT_PADDING_WORDS(1);
+ u64_le result_info{};
+ };
+ static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
+
+ struct InParams {
+ u32_le revision{};
+ u32_le padding{};
+ u64_le flags{};
+ };
+ static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
+
+ struct OutParams {
+ std::array<ErrorInfo, 10> errors{};
+ u32_le error_count{};
+ INSERT_PADDING_BYTES(12);
+ };
+ static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
+
+ explicit BehaviorInfo();
+ ~BehaviorInfo();
+
+ bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
+
+ void ClearError();
+ void UpdateFlags(u64_le dest_flags);
+ void SetUserRevision(u32_le revision);
+ u32_le GetUserRevision() const;
+ u32_le GetProcessRevision() const;
+
+ bool IsAdpcmLoopContextBugFixed() const;
+ bool IsSplitterSupported() const;
+ bool IsLongSizePreDelaySupported() const;
+ bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
+ bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
+ bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
+ bool IsElapsedFrameCountSupported() const;
+ bool IsMemoryPoolForceMappingEnabled() const;
+ bool IsFlushVoiceWaveBuffersSupported() const;
+ bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
+ bool IsVoicePitchAndSrcSkippedSupported() const;
+ bool IsMixInParameterDirtyOnlyUpdateSupported() const;
+ bool IsSplitterBugFixed() const;
+ void CopyErrorInfo(OutParams& dst);
+
+private:
+ u32_le process_revision{};
+ u32_le user_revision{};
+ u64_le flags{};
+ std::array<ErrorInfo, 10> errors{};
+ std::size_t error_count{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
index c5a0d98ce..2fb91c13a 100644
--- a/src/audio_core/codec.cpp
+++ b/src/audio_core/codec.cpp
@@ -16,8 +16,9 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
- constexpr std::array<int, 16> SIGNED_NIBBLES = {
- {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
+ static constexpr std::array<int, 16> SIGNED_NIBBLES{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
const std::size_t ret_size =
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
index ef2ce01a8..9507abb1b 100644
--- a/src/audio_core/codec.h
+++ b/src/audio_core/codec.h
@@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array<s16, 16>;
* @param state ADPCM state, this is updated with new state
* @return Decoded stereo signed PCM16 data, sample_count in length
*/
-std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
+std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
ADPCMState& state);
}; // namespace AudioCore::Codec
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
new file mode 100644
index 000000000..fb8700ccf
--- /dev/null
+++ b/src/audio_core/command_generator.cpp
@@ -0,0 +1,977 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/algorithm/interpolate.h"
+#include "audio_core/command_generator.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/voice_context.h"
+#include "core/memory.h"
+
+namespace AudioCore {
+namespace {
+constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
+constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
+
+template <std::size_t N>
+void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
+ for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
+ for (std::size_t j = 0; j < N; j++) {
+ output[i + j] +=
+ static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
+ }
+ }
+}
+
+s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
+ s32 x = 0;
+ for (s32 i = 0; i < sample_count; i++) {
+ x = static_cast<s32>(static_cast<float>(input[i]) * gain);
+ output[i] += x;
+ gain += delta;
+ }
+ return x;
+}
+
+void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
+ for (s32 i = 0; i < sample_count; i++) {
+ output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
+ gain += delta;
+ }
+}
+
+void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
+ for (s32 i = 0; i < sample_count; i++) {
+ output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
+ }
+}
+
+s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
+ const bool positive = first_sample > 0;
+ auto final_sample = std::abs(first_sample);
+ for (s32 i = 0; i < sample_count; i++) {
+ final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
+ if (positive) {
+ output[i] += final_sample;
+ } else {
+ output[i] -= final_sample;
+ }
+ }
+ if (positive) {
+ return final_sample;
+ } else {
+ return -final_sample;
+ }
+}
+
+} // namespace
+
+CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
+ VoiceContext& voice_context, MixContext& mix_context,
+ SplitterContext& splitter_context, EffectContext& effect_context,
+ Core::Memory::Memory& memory)
+ : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
+ splitter_context(splitter_context), effect_context(effect_context), memory(memory),
+ mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
+ worker_params.sample_count),
+ sample_buffer(MIX_BUFFER_SIZE),
+ depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
+ worker_params.sample_count) {}
+CommandGenerator::~CommandGenerator() = default;
+
+void CommandGenerator::ClearMixBuffers() {
+ std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
+ std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
+ // std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
+}
+
+void CommandGenerator::GenerateVoiceCommands() {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
+ }
+ // Grab all our voices
+ const auto voice_count = voice_context.GetVoiceCount();
+ for (std::size_t i = 0; i < voice_count; i++) {
+ auto& voice_info = voice_context.GetSortedInfo(i);
+ // Update voices and check if we should queue them
+ if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
+ continue;
+ }
+
+ // Queue our voice
+ GenerateVoiceCommand(voice_info);
+ }
+ // Update our splitters
+ splitter_context.UpdateInternalState();
+}
+
+void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
+ auto& in_params = voice_info.GetInParams();
+ const auto channel_count = in_params.channel_count;
+
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ const auto resource_id = in_params.voice_channel_resource_id[channel];
+ auto& dsp_state = voice_context.GetDspSharedState(resource_id);
+ auto& channel_resource = voice_context.GetChannelResource(resource_id);
+
+ // Decode our samples for our channel
+ GenerateDataSourceCommand(voice_info, dsp_state, channel);
+
+ if (in_params.should_depop) {
+ in_params.last_volume = 0.0f;
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
+ in_params.mix_id != AudioCommon::NO_MIX) {
+ // Apply a biquad filter if needed
+ GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
+ worker_params.mix_buffer_count, channel);
+ // Base voice volume ramping
+ GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
+ in_params.node_id);
+ in_params.last_volume = in_params.volume;
+
+ if (in_params.mix_id != AudioCommon::NO_MIX) {
+ // If we're using a mix id
+ auto& mix_info = mix_context.GetInfo(in_params.mix_id);
+ const auto& dest_mix_params = mix_info.GetInParams();
+
+ // Voice Mixing
+ GenerateVoiceMixCommand(
+ channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
+ dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
+ worker_params.mix_buffer_count + channel, in_params.node_id);
+
+ // Update last mix volumes
+ channel_resource.UpdateLastMixVolumes();
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
+ s32 base = channel;
+ while (auto* destination_data =
+ GetDestinationData(in_params.splitter_info_id, base)) {
+ base += channel_count;
+
+ if (!destination_data->IsConfigured()) {
+ continue;
+ }
+ if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) {
+ continue;
+ }
+
+ const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
+ const auto& dest_mix_params = mix_info.GetInParams();
+ GenerateVoiceMixCommand(
+ destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
+ dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
+ worker_params.mix_buffer_count + channel, in_params.node_id);
+ destination_data->MarkDirty();
+ }
+ }
+ // Update biquad filter enabled states
+ for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
+ in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommands() {
+ const auto mix_count = mix_context.GetCount();
+ for (std::size_t i = 0; i < mix_count; i++) {
+ auto& mix_info = mix_context.GetSortedInfo(i);
+ const auto& in_params = mix_info.GetInParams();
+ if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
+ continue;
+ }
+ GenerateSubMixCommand(mix_info);
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommands() {
+ GenerateFinalMixCommand();
+}
+
+void CommandGenerator::PreCommand() {
+ if (!dumping_frame) {
+ return;
+ }
+ for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
+ const auto& base = splitter_context.GetInfo(i);
+ std::string graph = fmt::format("b[{}]", i);
+ const auto* head = base.GetHead();
+ while (head != nullptr) {
+ graph += fmt::format("->{}", head->GetMixId());
+ head = head->GetNextDestination();
+ }
+ LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
+ }
+}
+
+void CommandGenerator::PostCommand() {
+ if (!dumping_frame) {
+ return;
+ }
+ dumping_frame = false;
+}
+
+void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 channel) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto depop = in_params.should_depop;
+
+ if (depop) {
+ if (in_params.mix_id != AudioCommon::NO_MIX) {
+ auto& mix_info = mix_context.GetInfo(in_params.mix_id);
+ const auto& mix_in = mix_info.GetInParams();
+ GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
+ s32 index{};
+ while (const auto* destination =
+ GetDestinationData(in_params.splitter_info_id, index++)) {
+ if (!destination->IsConfigured()) {
+ continue;
+ }
+ auto& mix_info = mix_context.GetInfo(destination->GetMixId());
+ const auto& mix_in = mix_info.GetInParams();
+ GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
+ }
+ }
+ } else {
+ switch (in_params.sample_format) {
+ case SampleFormat::Pcm16:
+ DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
+ worker_params.sample_rate, worker_params.sample_count,
+ in_params.node_id);
+ break;
+ case SampleFormat::Adpcm:
+ ASSERT(channel == 0 && in_params.channel_count == 1);
+ DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
+ worker_params.sample_rate, worker_params.sample_count,
+ in_params.node_id);
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
+ VoiceState& dsp_state,
+ s32 mix_buffer_count, s32 channel) {
+ for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
+ const auto& in_params = voice_info.GetInParams();
+ auto& biquad_filter = in_params.biquad_filter[i];
+ // Check if biquad filter is actually used
+ if (!biquad_filter.enabled) {
+ continue;
+ }
+
+ // Reinitialize our biquad filter state if it was enabled previously
+ if (!in_params.was_biquad_filter_enabled[i]) {
+ dsp_state.biquad_filter_state.fill(0);
+ }
+
+ // Generate biquad filter
+ // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
+ // dsp_state.biquad_filter_state,
+ // mix_buffer_count + channel, mix_buffer_count +
+ // channel, worker_params.sample_count,
+ // voice_info.GetInParams().node_id);
+ }
+}
+
+void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
+ s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
+ std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
+ "input_mix_buffer={}, output_mix_buffer={}",
+ node_id, input_offset, output_offset);
+ }
+ const auto* input = GetMixBuffer(input_offset);
+ auto* output = GetMixBuffer(output_offset);
+
+ // Biquad filter parameters
+ const auto [n0, n1, n2] = params.numerator;
+ const auto [d0, d1] = params.denominator;
+
+ // Biquad filter states
+ auto [s0, s1] = state;
+
+ constexpr s64 int32_min = std::numeric_limits<s32>::min();
+ constexpr s64 int32_max = std::numeric_limits<s32>::max();
+
+ for (int i = 0; i < sample_count; ++i) {
+ const auto sample = static_cast<s64>(input[i]);
+ const auto f = (sample * n0 + s0 + 0x4000) >> 15;
+ const auto y = std::clamp(f, int32_min, int32_max);
+ s0 = sample * n1 + y * d0 + s1;
+ s1 = sample * n2 + y * d1;
+ output[i] = static_cast<s32>(y);
+ }
+
+ state = {s0, s1};
+}
+
+void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
+ std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset) {
+ for (std::size_t i = 0; i < mix_buffer_count; i++) {
+ auto& sample = dsp_state.previous_samples[i];
+ if (sample != 0) {
+ depop_buffer[mix_buffer_offset + i] += sample;
+ sample = 0;
+ }
+ }
+}
+
+void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset,
+ s32 sample_rate) {
+ const std::size_t end_offset =
+ std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
+ const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
+ for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
+ if (depop_buffer[i] == 0) {
+ continue;
+ }
+
+ depop_buffer[i] =
+ ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
+ }
+}
+
+void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
+ const std::size_t effect_count = effect_context.GetCount();
+ const auto buffer_offset = mix_info.GetInParams().buffer_offset;
+ for (std::size_t i = 0; i < effect_count; i++) {
+ const auto index = mix_info.GetEffectOrder(i);
+ if (index == AudioCommon::NO_EFFECT_ORDER) {
+ break;
+ }
+ auto* info = effect_context.GetInfo(index);
+ const auto type = info->GetType();
+
+ // TODO(ogniK): Finish remaining effects
+ switch (type) {
+ case EffectType::Aux:
+ GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::I3dl2Reverb:
+ GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::BiquadFilter:
+ GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ default:
+ break;
+ }
+
+ info->UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ if (!enabled) {
+ return;
+ }
+ const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
+ const auto channel_count = params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ // TODO(ogniK): Actually implement reverb
+ if (params.input[i] != params.output[i]) {
+ const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ ApplyMix<1>(output, input, 32768, worker_params.sample_count);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ if (!enabled) {
+ return;
+ }
+ const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
+ const auto channel_count = params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ // TODO(ogniK): Actually implement biquad filter
+ if (params.input[i] != params.output[i]) {
+ const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ ApplyMix<1>(output, input, 32768, worker_params.sample_count);
+ }
+ }
+}
+
+void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
+ auto* aux = dynamic_cast<EffectAuxInfo*>(info);
+ const auto& params = aux->GetParams();
+ if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
+ const auto max_channels = params.count;
+ u32 offset{};
+ for (u32 channel = 0; channel < max_channels; channel++) {
+ u32 write_count = 0;
+ if (channel == (max_channels - 1)) {
+ write_count = offset + worker_params.sample_count;
+ }
+
+ const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
+ const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
+
+ if (enabled) {
+ AuxInfoDSP send_info{};
+ AuxInfoDSP recv_info{};
+ memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+ memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
+ GetMixBuffer(input_index), worker_params.sample_count, offset,
+ write_count);
+ memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+
+ const auto samples_read = ReadAuxBuffer(
+ recv_info, aux->GetRecvBuffer(), params.sample_count,
+ GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
+ memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ if (samples_read != static_cast<int>(worker_params.sample_count) &&
+ samples_read <= params.sample_count) {
+ std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
+ }
+ } else {
+ AuxInfoDSP empty{};
+ memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
+ memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
+ if (output_index != input_index) {
+ std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
+ worker_params.sample_count * sizeof(s32));
+ }
+ }
+
+ offset += worker_params.sample_count;
+ }
+ }
+}
+
+ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
+ if (splitter_id == AudioCommon::NO_SPLITTER) {
+ return nullptr;
+ }
+ return splitter_context.GetDestinationData(splitter_id, index);
+}
+
+s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
+ const s32* data, u32 sample_count, u32 write_offset,
+ u32 write_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+ u32 offset = dsp_info.write_offset + write_offset;
+ if (send_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ std::size_t data_offset{};
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ // Get position in buffer
+ const auto base = send_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ // Write to output
+ memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ data_offset += samples_to_grab;
+ }
+
+ if (write_count != 0) {
+ dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
+ }
+ return sample_count;
+}
+
+s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
+ s32* out_data, u32 sample_count, u32 read_offset,
+ u32 read_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+
+ u32 offset = recv_info.read_offset + read_offset;
+ if (recv_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ const auto base = recv_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ std::vector<s32> buffer(samples_to_grab);
+ memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
+ std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
+ out_data += samples_to_grab;
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ }
+
+ if (read_count != 0) {
+ recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
+ }
+ return sample_count;
+}
+
+void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
+ s32 channel, s32 node_id) {
+ const auto last = static_cast<s32>(last_volume * 32768.0f);
+ const auto current = static_cast<s32>(current_volume * 32768.0f);
+ const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
+ static_cast<float>(worker_params.sample_count));
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
+ "last_volume={}, current_volume={}",
+ node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
+ last_volume, current_volume);
+ }
+ // Apply generic gain on samples
+ ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
+ worker_params.sample_count);
+}
+
+void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
+ const MixVolumeBuffer& last_mix_volumes,
+ VoiceState& dsp_state, s32 mix_buffer_offset,
+ s32 mix_buffer_count, s32 voice_index, s32 node_id) {
+ // Loop all our mix buffers
+ for (s32 i = 0; i < mix_buffer_count; i++) {
+ if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
+ const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
+ static_cast<float>(worker_params.sample_count);
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
+ "output={}, last_volume={}, current_volume={}",
+ node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
+ mix_volumes[i]);
+ }
+
+ dsp_state.previous_samples[i] =
+ ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
+ last_mix_volumes[i], delta, worker_params.sample_count);
+ } else {
+ dsp_state.previous_samples[i] = 0;
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
+ }
+ const auto& in_params = mix_info.GetInParams();
+ GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
+ in_params.sample_rate);
+
+ GenerateEffectCommand(mix_info);
+
+ GenerateMixCommands(mix_info);
+}
+
+void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
+ if (!mix_info.HasAnyConnection()) {
+ return;
+ }
+ const auto& in_params = mix_info.GetInParams();
+ if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
+ const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
+ const auto& dest_in_params = dest_mix.GetInParams();
+
+ const auto buffer_count = in_params.buffer_count;
+
+ for (s32 i = 0; i < buffer_count; i++) {
+ for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
+ const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
+ if (mixed_volume != 0.0f) {
+ GenerateMixCommand(dest_in_params.buffer_offset + j,
+ in_params.buffer_offset + i, mixed_volume,
+ in_params.node_id);
+ }
+ }
+ }
+ } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
+ s32 base{};
+ while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
+ if (!destination_data->IsConfigured()) {
+ continue;
+ }
+
+ const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
+ const auto& dest_in_params = dest_mix.GetInParams();
+ const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
+ for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count);
+ i++) {
+ const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
+ if (mixed_volume != 0.0f) {
+ GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
+ in_params.node_id);
+ }
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
+ float volume, s32 node_id) {
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
+ node_id, input_offset, output_offset, volume);
+ }
+
+ auto* output = GetMixBuffer(output_offset);
+ const auto* input = GetMixBuffer(input_offset);
+
+ const s32 gain = static_cast<s32>(volume * 32768.0f);
+ // Mix with loop unrolling
+ if (worker_params.sample_count % 4 == 0) {
+ ApplyMix<4>(output, input, gain, worker_params.sample_count);
+ } else if (worker_params.sample_count % 2 == 0) {
+ ApplyMix<2>(output, input, gain, worker_params.sample_count);
+ } else {
+ ApplyMix<1>(output, input, gain, worker_params.sample_count);
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommand() {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
+ }
+ auto& mix_info = mix_context.GetFinalMixInfo();
+ const auto& in_params = mix_info.GetInParams();
+
+ GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
+ in_params.sample_rate);
+
+ GenerateEffectCommand(mix_info);
+
+ for (s32 i = 0; i < in_params.buffer_count; i++) {
+ const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
+ if (dumping_frame) {
+ LOG_DEBUG(
+ Audio,
+ "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
+ in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
+ in_params.volume);
+ }
+ ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
+ GetMixBuffer(in_params.buffer_offset + i), gain,
+ worker_params.sample_count);
+ }
+}
+
+s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 sample_count, s32 channel, std::size_t mix_offset) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ if (wave_buffer.buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer.buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ return 0;
+ }
+ const auto samples_remaining =
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto start_offset =
+ ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
+ sizeof(s16);
+ const auto buffer_pos = wave_buffer.buffer_address + start_offset;
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+
+ if (in_params.channel_count == 1) {
+ std::vector<s16> buffer(samples_processed);
+ memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
+ for (std::size_t i = 0; i < buffer.size(); i++) {
+ sample_buffer[mix_offset + i] = buffer[i];
+ }
+ } else {
+ const auto channel_count = in_params.channel_count;
+ std::vector<s16> buffer(samples_processed * channel_count);
+ memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
+
+ for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
+ sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
+ }
+ }
+
+ return samples_processed;
+}
+
+s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 sample_count, s32 channel, std::size_t mix_offset) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ if (wave_buffer.buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer.buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ return 0;
+ }
+
+ static constexpr std::array<int, 16> SIGNED_NIBBLES{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
+
+ constexpr std::size_t FRAME_LEN = 8;
+ constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
+ constexpr std::size_t SAMPLES_PER_FRAME = 14;
+
+ auto frame_header = dsp_state.context.header;
+ s32 idx = (frame_header >> 4) & 0xf;
+ s32 scale = frame_header & 0xf;
+ s16 yn1 = dsp_state.context.yn1;
+ s16 yn2 = dsp_state.context.yn2;
+
+ Codec::ADPCM_Coeff coeffs;
+ memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
+ sizeof(Codec::ADPCM_Coeff));
+
+ s32 coef1 = coeffs[idx * 2];
+ s32 coef2 = coeffs[idx * 2 + 1];
+
+ const auto samples_remaining =
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+ const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
+
+ const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
+ auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
+ samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
+
+ const auto decode_sample = [&](const int nibble) -> s16 {
+ const int xn = nibble * (1 << scale);
+ // We first transform everything into 11 bit fixed point, perform the second order
+ // digital filter, then transform back.
+ // 0x400 == 0.5 in 11 bit fixed point.
+ // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
+ int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
+ // Clamp to output range.
+ val = std::clamp<s32>(val, -32768, 32767);
+ // Advance output feedback.
+ yn2 = yn1;
+ yn1 = static_cast<s16>(val);
+ return yn1;
+ };
+
+ std::size_t buffer_offset{};
+ std::vector<u8> buffer(
+ std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
+ memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
+ buffer.size());
+ std::size_t cur_mix_offset = mix_offset;
+
+ auto remaining_samples = samples_processed;
+ while (remaining_samples > 0) {
+ if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
+ // Read header
+ frame_header = buffer[buffer_offset++];
+ idx = (frame_header >> 4) & 0xf;
+ scale = frame_header & 0xf;
+ coef1 = coeffs[idx * 2];
+ coef2 = coeffs[idx * 2 + 1];
+ position_in_frame += 2;
+
+ // Decode entire frame
+ if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) {
+ for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
+ // Sample 1
+ const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
+ const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
+ const s16 sample_1 = decode_sample(s0);
+ const s16 sample_2 = decode_sample(s1);
+ sample_buffer[cur_mix_offset++] = sample_1;
+ sample_buffer[cur_mix_offset++] = sample_2;
+ }
+ remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME);
+ position_in_frame += SAMPLES_PER_FRAME;
+ continue;
+ }
+ }
+ // Decode mid frame
+ s32 current_nibble = buffer[buffer_offset];
+ if (position_in_frame++ & 0x1) {
+ current_nibble &= 0xf;
+ buffer_offset++;
+ } else {
+ current_nibble >>= 4;
+ }
+ const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
+ sample_buffer[cur_mix_offset++] = sample;
+ remaining_samples--;
+ }
+
+ dsp_state.context.header = frame_header;
+ dsp_state.context.yn1 = yn1;
+ dsp_state.context.yn2 = yn2;
+
+ return samples_processed;
+}
+
+s32* CommandGenerator::GetMixBuffer(std::size_t index) {
+ return mix_buffer.data() + (index * worker_params.sample_count);
+}
+
+const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
+ return mix_buffer.data() + (index * worker_params.sample_count);
+}
+
+std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
+ return worker_params.mix_buffer_count + channel;
+}
+
+std::size_t CommandGenerator::GetTotalMixBufferCount() const {
+ return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
+}
+
+s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
+ return GetMixBuffer(worker_params.mix_buffer_count + channel);
+}
+
+const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
+ return GetMixBuffer(worker_params.mix_buffer_count + channel);
+}
+
+void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
+ VoiceState& dsp_state, s32 channel,
+ s32 target_sample_rate, s32 sample_count,
+ s32 node_id) {
+ const auto& in_params = voice_info.GetInParams();
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
+ "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
+ node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
+ in_params.mix_id, in_params.splitter_info_id);
+ }
+ ASSERT_OR_EXECUTE(output != nullptr, { return; });
+
+ const auto resample_rate = static_cast<s32>(
+ static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
+ static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
+ if (dsp_state.fraction + sample_count * resample_rate >
+ static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
+ return;
+ }
+
+ auto min_required_samples =
+ std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
+ if (min_required_samples >= sample_count) {
+ min_required_samples = sample_count;
+ }
+
+ std::size_t temp_mix_offset{};
+ bool is_buffer_completed{false};
+ auto samples_remaining = sample_count;
+ while (samples_remaining > 0 && !is_buffer_completed) {
+ const auto samples_to_output = std::min(samples_remaining, min_required_samples);
+ const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
+
+ if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
+ // Append sample histtory for resampler
+ for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
+ sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
+ }
+ temp_mix_offset += 4;
+ }
+
+ s32 samples_read{};
+ while (samples_read < samples_to_read) {
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ // No more data can be read
+ if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
+ is_buffer_completed = true;
+ break;
+ }
+
+ if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
+ wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
+ // TODO(ogniK): ADPCM loop context
+ }
+
+ s32 samples_decoded{0};
+ switch (in_params.sample_format) {
+ case SampleFormat::Pcm16:
+ samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
+ channel, temp_mix_offset);
+ break;
+ case SampleFormat::Adpcm:
+ samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
+ channel, temp_mix_offset);
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
+ }
+
+ temp_mix_offset += samples_decoded;
+ samples_read += samples_decoded;
+ dsp_state.offset += samples_decoded;
+ dsp_state.played_sample_count += samples_decoded;
+
+ if (dsp_state.offset >=
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
+ samples_decoded == 0) {
+ // Reset our sample offset
+ dsp_state.offset = 0;
+ if (wave_buffer.is_looping) {
+ if (samples_decoded == 0) {
+ // End of our buffer
+ is_buffer_completed = true;
+ break;
+ }
+
+ if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
+ dsp_state.played_sample_count = 0;
+ }
+ } else {
+
+ // Update our wave buffer states
+ dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
+ dsp_state.wave_buffer_consumed++;
+ dsp_state.wave_buffer_index =
+ (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ if (wave_buffer.end_of_stream) {
+ dsp_state.played_sample_count = 0;
+ }
+ }
+ }
+ }
+
+ if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
+ // No need to resample
+ std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
+ } else {
+ std::fill(sample_buffer.begin() + temp_mix_offset,
+ sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
+ 0);
+ AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
+ samples_to_output);
+ // Resample
+ for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
+ dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
+ }
+ }
+ output += samples_to_output;
+ samples_remaining -= samples_to_output;
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
new file mode 100644
index 000000000..53e57748b
--- /dev/null
+++ b/src/audio_core/command_generator.h
@@ -0,0 +1,102 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "audio_core/common.h"
+#include "audio_core/voice_context.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore {
+class MixContext;
+class SplitterContext;
+class ServerSplitterDestinationData;
+class ServerMixInfo;
+class EffectContext;
+class EffectBase;
+struct AuxInfoDSP;
+using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
+
+class CommandGenerator {
+public:
+ explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
+ VoiceContext& voice_context, MixContext& mix_context,
+ SplitterContext& splitter_context, EffectContext& effect_context,
+ Core::Memory::Memory& memory);
+ ~CommandGenerator();
+
+ void ClearMixBuffers();
+ void GenerateVoiceCommands();
+ void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
+ void GenerateSubMixCommands();
+ void GenerateFinalMixCommands();
+ void PreCommand();
+ void PostCommand();
+
+ s32* GetChannelMixBuffer(s32 channel);
+ const s32* GetChannelMixBuffer(s32 channel) const;
+ s32* GetMixBuffer(std::size_t index);
+ const s32* GetMixBuffer(std::size_t index) const;
+ std::size_t GetMixChannelBufferOffset(s32 channel) const;
+
+ std::size_t GetTotalMixBufferCount() const;
+
+private:
+ void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
+ void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 mix_buffer_count, s32 channel);
+ void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
+ s32 node_id);
+ void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
+ const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
+ s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
+ s32 node_id);
+ void GenerateSubMixCommand(ServerMixInfo& mix_info);
+ void GenerateMixCommands(ServerMixInfo& mix_info);
+ void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
+ s32 node_id);
+ void GenerateFinalMixCommand();
+ void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
+ std::array<s64, 2>& state, std::size_t input_offset,
+ std::size_t output_offset, s32 sample_count, s32 node_id);
+ void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset);
+ void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset, s32 sample_rate);
+ void GenerateEffectCommand(ServerMixInfo& mix_info);
+ void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
+
+ s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
+ u32 sample_count, u32 write_offset, u32 write_count);
+ s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
+ u32 sample_count, u32 read_offset, u32 read_count);
+
+ // DSP Code
+ s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
+ s32 channel, std::size_t mix_offset);
+ s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
+ s32 channel, std::size_t mix_offset);
+ void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
+ s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
+
+ AudioCommon::AudioRendererParameter& worker_params;
+ VoiceContext& voice_context;
+ MixContext& mix_context;
+ SplitterContext& splitter_context;
+ EffectContext& effect_context;
+ Core::Memory::Memory& memory;
+ std::vector<s32> mix_buffer{};
+ std::vector<s32> sample_buffer{};
+ std::vector<s32> depop_buffer{};
+ bool dumping_frame{false};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
new file mode 100644
index 000000000..7b4a1e9e8
--- /dev/null
+++ b/src/audio_core/common.h
@@ -0,0 +1,108 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/result.h"
+
+namespace AudioCommon {
+namespace Audren {
+constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
+constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
+} // namespace Audren
+
+constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
+constexpr std::size_t MAX_MIX_BUFFERS = 24;
+constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
+constexpr std::size_t MAX_CHANNEL_COUNT = 6;
+constexpr std::size_t MAX_WAVE_BUFFERS = 4;
+constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
+constexpr u32 STREAM_SAMPLE_RATE = 48000;
+constexpr u32 STREAM_NUM_CHANNELS = 6;
+constexpr s32 NO_SPLITTER = -1;
+constexpr s32 NO_MIX = 0x7fffffff;
+constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
+constexpr s32 FINAL_MIX = 0;
+constexpr s32 NO_EFFECT_ORDER = -1;
+constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
+// Any size checks seem to take the sample history into account
+// and our const ends up being 0x3f04, the 4 bytes are most
+// likely the sample history
+constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
+
+static constexpr u32 VersionFromRevision(u32_le rev) {
+ // "REV7" -> 7
+ return ((rev >> 24) & 0xff) - 0x30;
+}
+
+static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) {
+ const auto base = VersionFromRevision(user_revision);
+ return required <= base;
+}
+
+static constexpr bool IsValidRevision(u32_le revision) {
+ const auto base = VersionFromRevision(revision);
+ constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION);
+ return base <= max_rev;
+}
+
+static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) {
+ if (offset > size) {
+ return false;
+ }
+ if (size < required) {
+ return false;
+ }
+ if ((size - offset) < required) {
+ return false;
+ }
+ return true;
+}
+
+struct UpdateDataSizes {
+ u32_le behavior{};
+ u32_le memory_pool{};
+ u32_le voice{};
+ u32_le voice_channel_resource{};
+ u32_le effect{};
+ u32_le mixer{};
+ u32_le sink{};
+ u32_le performance{};
+ u32_le splitter{};
+ u32_le render_info{};
+ INSERT_PADDING_WORDS(4);
+};
+static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
+
+struct UpdateDataHeader {
+ u32_le revision{};
+ UpdateDataSizes size{};
+ u32_le total_size{};
+};
+static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
+
+struct AudioRendererParameter {
+ u32_le sample_rate;
+ u32_le sample_count;
+ u32_le mix_buffer_count;
+ u32_le submix_count;
+ u32_le voice_count;
+ u32_le sink_count;
+ u32_le effect_count;
+ u32_le performance_frame_count;
+ u8 is_voice_drop_enabled;
+ u8 unknown_21;
+ u8 unknown_22;
+ u8 execution_mode;
+ u32_le splitter_count;
+ u32_le num_splitter_send_channels;
+ u32_le unknown_30;
+ u32_le revision;
+};
+static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
+
+} // namespace AudioCommon
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index c4e0e30fe..6eaa60815 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
public:
CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
- : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
+ : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
num_channels} {
cubeb_stream_params params{};
params.rate = sample_rate;
params.channels = num_channels;
params.format = CUBEB_SAMPLE_S16NE;
- params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
+ switch (num_channels) {
+ case 1:
+ params.layout = CUBEB_LAYOUT_MONO;
+ break;
+ case 2:
+ params.layout = CUBEB_LAYOUT_STEREO;
+ break;
+ case 6:
+ params.layout = CUBEB_LAYOUT_3F2_LFE;
+ break;
+ }
u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
@@ -78,13 +88,15 @@ public:
const s16 surround_left{samples[i + 4]};
const s16 surround_right{samples[i + 5]};
// Not used in the ATSC reference implementation
- [[maybe_unused]] const s16 low_frequency_effects { samples[i + 3] };
+ [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
constexpr s32 clev{707}; // center mixing level coefficient
constexpr s32 slev{707}; // surround mixing level coefficient
- buf.push_back(left + (clev * center / 1000) + (slev * surround_left / 1000));
- buf.push_back(right + (clev * center / 1000) + (slev * surround_right / 1000));
+ buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
+ (slev * surround_left / 1000)));
+ buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
+ (slev * surround_right / 1000)));
}
queue.Push(buf);
return;
@@ -182,8 +194,8 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
void* output_buffer, long num_frames) {
- CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data);
- u8* buffer = reinterpret_cast<u8*>(output_buffer);
+ auto* impl = static_cast<CubebSinkStream*>(user_data);
+ auto* buffer = static_cast<u8*>(output_buffer);
if (!impl) {
return {};
@@ -193,7 +205,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
const std::size_t samples_to_write = num_channels * num_frames;
std::size_t samples_written;
- if (Settings::values.enable_audio_stretching) {
+ /*
+ if (Settings::values.enable_audio_stretching.GetValue()) {
const std::vector<s16> in{impl->queue.Pop()};
const std::size_t num_in{in.size() / num_channels};
s16* const out{reinterpret_cast<s16*>(buffer)};
@@ -207,7 +220,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
}
} else {
samples_written = impl->queue.Pop(buffer, samples_to_write);
- }
+ }*/
+ samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
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..4d9cdf524
--- /dev/null
+++ b/src/audio_core/effect_context.cpp
@@ -0,0 +1,299 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include "audio_core/effect_context.h"
+
+namespace AudioCore {
+namespace {
+bool ValidChannelCountForEffect(s32 channel_count) {
+ return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
+}
+} // namespace
+
+EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
+ effects.reserve(effect_count);
+ std::generate_n(std::back_inserter(effects), effect_count,
+ [] { return std::make_unique<EffectStubbed>(); });
+}
+EffectContext::~EffectContext() = default;
+
+std::size_t EffectContext::GetCount() const {
+ return effect_count;
+}
+
+EffectBase* EffectContext::GetInfo(std::size_t i) {
+ return effects.at(i).get();
+}
+
+EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
+ switch (effect) {
+ case EffectType::Invalid:
+ effects[i] = std::make_unique<EffectStubbed>();
+ break;
+ case EffectType::BufferMixer:
+ effects[i] = std::make_unique<EffectBufferMixer>();
+ break;
+ case EffectType::Aux:
+ effects[i] = std::make_unique<EffectAuxInfo>();
+ break;
+ case EffectType::Delay:
+ effects[i] = std::make_unique<EffectDelay>();
+ break;
+ case EffectType::Reverb:
+ effects[i] = std::make_unique<EffectReverb>();
+ break;
+ case EffectType::I3dl2Reverb:
+ effects[i] = std::make_unique<EffectI3dl2Reverb>();
+ break;
+ case EffectType::BiquadFilter:
+ effects[i] = std::make_unique<EffectBiquadFilter>();
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented effect {}", effect);
+ effects[i] = std::make_unique<EffectStubbed>();
+ }
+ return GetInfo(i);
+}
+
+const EffectBase* EffectContext::GetInfo(std::size_t i) const {
+ return effects.at(i).get();
+}
+
+EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
+EffectStubbed::~EffectStubbed() = default;
+
+void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
+void EffectStubbed::UpdateForCommandGeneration() {}
+
+EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
+EffectBase::~EffectBase() = default;
+
+UsageState EffectBase::GetUsage() const {
+ return usage;
+}
+
+EffectType EffectBase::GetType() const {
+ return effect_type;
+}
+
+bool EffectBase::IsEnabled() const {
+ return enabled;
+}
+
+s32 EffectBase::GetMixID() const {
+ return mix_id;
+}
+
+s32 EffectBase::GetProcessingOrder() const {
+ return processing_order;
+}
+
+EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
+EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
+
+void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
+ auto& internal_params = GetParams();
+ const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
+ internal_params.channel_count = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectI3dl2Reverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
+EffectBiquadFilter::~EffectBiquadFilter() = default;
+
+void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
+ auto& internal_params = GetParams();
+ const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *biquad_params;
+ enabled = in_params.is_enabled;
+}
+
+void EffectBiquadFilter::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
+EffectAuxInfo::~EffectAuxInfo() = default;
+
+void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
+ const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *aux_params;
+ enabled = in_params.is_enabled;
+
+ if (in_params.is_new || skipped) {
+ skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
+ if (skipped) {
+ return;
+ }
+
+ // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
+ // the second is managed by the dsp. All we care about is managing the DSP one
+ send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
+ send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
+
+ recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
+ recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
+ }
+}
+
+void EffectAuxInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+VAddr EffectAuxInfo::GetSendInfo() const {
+ return send_info;
+}
+
+VAddr EffectAuxInfo::GetSendBuffer() const {
+ return send_buffer;
+}
+
+VAddr EffectAuxInfo::GetRecvInfo() const {
+ return recv_info;
+}
+
+VAddr EffectAuxInfo::GetRecvBuffer() const {
+ return recv_buffer;
+}
+
+EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
+EffectDelay::~EffectDelay() = default;
+
+void EffectDelay::Update(EffectInfo::InParams& in_params) {
+ const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
+ auto& internal_params = GetParams();
+ if (!ValidChannelCountForEffect(delay_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *delay_params;
+ if (!ValidChannelCountForEffect(delay_params->channels)) {
+ internal_params.channels = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectDelay::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
+EffectBufferMixer::~EffectBufferMixer() = default;
+
+void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
+ enabled = in_params.is_enabled;
+}
+
+void EffectBufferMixer::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
+EffectReverb::~EffectReverb() = default;
+
+void EffectReverb::Update(EffectInfo::InParams& in_params) {
+ const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
+ auto& internal_params = GetParams();
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channels)) {
+ internal_params.channels = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectReverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
new file mode 100644
index 000000000..2c4ce53ef
--- /dev/null
+++ b/src/audio_core/effect_context.h
@@ -0,0 +1,321 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <vector>
+#include "audio_core/common.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+enum class EffectType : u8 {
+ Invalid = 0,
+ BufferMixer = 1,
+ Aux = 2,
+ Delay = 3,
+ Reverb = 4,
+ I3dl2Reverb = 5,
+ BiquadFilter = 6,
+};
+
+enum class UsageStatus : u8 {
+ Invalid = 0,
+ New = 1,
+ Initialized = 2,
+ Used = 3,
+ Removed = 4,
+};
+
+enum class UsageState {
+ Invalid = 0,
+ Initialized = 1,
+ Running = 2,
+ Stopped = 3,
+};
+
+enum class ParameterStatus : u8 {
+ Initialized = 0,
+ Updating = 1,
+ Updated = 2,
+};
+
+struct BufferMixerParams {
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
+ s32_le count{};
+};
+static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
+
+struct AuxInfoDSP {
+ u32_le read_offset{};
+ u32_le write_offset{};
+ u32_le remaining{};
+ INSERT_PADDING_WORDS(13);
+};
+static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
+
+struct AuxInfo {
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
+ u32_le count{};
+ s32_le sample_rate{};
+ s32_le sample_count{};
+ s32_le mix_buffer_count{};
+ u64_le send_buffer_info{};
+ u64_le send_buffer_base{};
+
+ u64_le return_buffer_info{};
+ u64_le return_buffer_base{};
+};
+static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
+
+struct I3dl2ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channel_count{};
+ INSERT_PADDING_BYTES(1);
+ u32_le sample_rate{};
+ f32 room_hf{};
+ f32 hf_reference{};
+ f32 decay_time{};
+ f32 hf_decay_ratio{};
+ f32 room{};
+ f32 reflection{};
+ f32 reverb{};
+ f32 diffusion{};
+ f32 reflection_delay{};
+ f32 reverb_delay{};
+ f32 density{};
+ f32 dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
+
+struct BiquadFilterParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ std::array<s16_le, 3> numerator;
+ std::array<s16_le, 2> denominator;
+ s8 channel_count{};
+ ParameterStatus status{};
+};
+static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
+
+struct DelayParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le max_delay{};
+ s32_le delay{};
+ s32_le sample_rate{};
+ s32_le gain{};
+ s32_le feedback_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ s32_le channel_spread{};
+ s32_le low_pass{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
+
+struct ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le sample_rate{};
+ s32_le mode0{};
+ s32_le mode0_gain{};
+ s32_le pre_delay{};
+ s32_le mode1{};
+ s32_le mode1_gain{};
+ s32_le decay{};
+ s32_le hf_decay_ratio{};
+ s32_le coloration{};
+ s32_le reverb_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
+
+class EffectInfo {
+public:
+ struct InParams {
+ EffectType type{};
+ u8 is_new{};
+ u8 is_enabled{};
+ INSERT_PADDING_BYTES(1);
+ s32_le mix_id{};
+ u64_le buffer_address{};
+ u64_le buffer_size{};
+ s32_le processing_order{};
+ INSERT_PADDING_BYTES(4);
+ union {
+ std::array<u8, 0xa0> raw;
+ };
+ };
+ static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
+
+ struct OutParams {
+ UsageStatus status{};
+ INSERT_PADDING_BYTES(15);
+ };
+ static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
+};
+
+struct AuxAddress {
+ VAddr send_dsp_info{};
+ VAddr send_buffer_base{};
+ VAddr return_dsp_info{};
+ VAddr return_buffer_base{};
+};
+
+class EffectBase {
+public:
+ explicit EffectBase(EffectType effect_type);
+ virtual ~EffectBase();
+
+ virtual void Update(EffectInfo::InParams& in_params) = 0;
+ virtual void UpdateForCommandGeneration() = 0;
+ UsageState GetUsage() const;
+ EffectType GetType() const;
+ bool IsEnabled() const;
+ s32 GetMixID() const;
+ s32 GetProcessingOrder() const;
+
+protected:
+ UsageState usage{UsageState::Invalid};
+ EffectType effect_type{};
+ s32 mix_id{};
+ s32 processing_order{};
+ bool enabled = false;
+};
+
+template <typename T>
+class EffectGeneric : public EffectBase {
+public:
+ explicit EffectGeneric(EffectType effect_type) : EffectBase(effect_type) {}
+
+ T& GetParams() {
+ return internal_params;
+ }
+
+ const I3dl2ReverbParams& GetParams() const {
+ return internal_params;
+ }
+
+private:
+ T internal_params{};
+};
+
+class EffectStubbed : public EffectBase {
+public:
+ explicit EffectStubbed();
+ ~EffectStubbed() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
+public:
+ explicit EffectI3dl2Reverb();
+ ~EffectI3dl2Reverb() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
+public:
+ explicit EffectBiquadFilter();
+ ~EffectBiquadFilter() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectAuxInfo : public EffectGeneric<AuxInfo> {
+public:
+ explicit EffectAuxInfo();
+ ~EffectAuxInfo() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+ VAddr GetSendInfo() const;
+ VAddr GetSendBuffer() const;
+ VAddr GetRecvInfo() const;
+ VAddr GetRecvBuffer() const;
+
+private:
+ VAddr send_info{};
+ VAddr send_buffer{};
+ VAddr recv_info{};
+ VAddr recv_buffer{};
+ bool skipped = false;
+ AuxAddress addresses{};
+};
+
+class EffectDelay : public EffectGeneric<DelayParams> {
+public:
+ explicit EffectDelay();
+ ~EffectDelay() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
+public:
+ explicit EffectBufferMixer();
+ ~EffectBufferMixer() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectReverb : public EffectGeneric<ReverbParams> {
+public:
+ explicit EffectReverb();
+ ~EffectReverb() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectContext {
+public:
+ explicit EffectContext(std::size_t effect_count);
+ ~EffectContext();
+
+ std::size_t GetCount() const;
+ EffectBase* GetInfo(std::size_t i);
+ EffectBase* RetargetEffect(std::size_t i, EffectType effect);
+ const EffectBase* GetInfo(std::size_t i) const;
+
+private:
+ std::size_t effect_count{};
+ std::vector<std::unique_ptr<EffectBase>> effects;
+};
+} // namespace AudioCore
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
new file mode 100644
index 000000000..2940e53a9
--- /dev/null
+++ b/src/audio_core/info_updater.cpp
@@ -0,0 +1,516 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/info_updater.h"
+#include "audio_core/memory_pool.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/sink_context.h"
+#include "audio_core/splitter_context.h"
+#include "audio_core/voice_context.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
+ BehaviorInfo& behavior_info)
+ : in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
+ ASSERT(
+ AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
+ std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
+ output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
+}
+
+InfoUpdater::~InfoUpdater() = default;
+
+bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
+ if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
+ LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ sizeof(BehaviorInfo::InParams), input_header.size.behavior);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
+ sizeof(BehaviorInfo::InParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ BehaviorInfo::InParams behavior_in{};
+ std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
+ input_offset += sizeof(BehaviorInfo::InParams);
+
+ // Make sure it's an audio revision we can actually support
+ if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
+ LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
+ return false;
+ }
+
+ // Make sure that our behavior info revision matches the input
+ if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
+ LOG_ERROR(Audio,
+ "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
+ in_behavior_info.GetUserRevision(), behavior_in.revision);
+ return false;
+ }
+
+ // Update behavior info flags
+ in_behavior_info.ClearError();
+ in_behavior_info.UpdateFlags(behavior_in.flags);
+
+ return true;
+}
+
+bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
+ const auto memory_pool_count = memory_pool_info.size();
+ const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
+ const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
+
+ if (input_header.size.memory_pool != total_memory_pool_in) {
+ LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_memory_pool_in, input_header.size.memory_pool);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
+ std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
+
+ std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
+ input_offset += total_memory_pool_in;
+
+ // Update our memory pools
+ for (std::size_t i = 0; i < memory_pool_count; i++) {
+ if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
+ LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
+ return false;
+ }
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
+ sizeof(BehaviorInfo::InParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
+ output_offset += total_memory_pool_out;
+ output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
+ return true;
+}
+
+bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
+ const auto voice_count = voice_context.GetVoiceCount();
+ const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
+ std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
+
+ if (input_header.size.voice_channel_resource != voice_size) {
+ LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ voice_size, input_header.size.voice_channel_resource);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
+ input_offset += voice_size;
+
+ // Update our channel resources
+ for (std::size_t i = 0; i < voice_count; i++) {
+ // Grab our channel resource
+ auto& resource = voice_context.GetChannelResource(i);
+ resource.Update(resources_in[i]);
+ }
+
+ return true;
+}
+
+bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
+ std::vector<ServerMemoryPoolInfo>& memory_pool_info,
+ VAddr audio_codec_dsp_addr) {
+ const auto voice_count = voice_context.GetVoiceCount();
+ std::vector<VoiceInfo::InParams> voice_in(voice_count);
+ std::vector<VoiceInfo::OutParams> voice_out(voice_count);
+
+ const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
+ const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
+
+ if (input_header.size.voice != voice_in_size) {
+ LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ voice_in_size, input_header.size.voice);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
+ input_offset += voice_in_size;
+
+ // Set all voices to not be in use
+ for (std::size_t i = 0; i < voice_count; i++) {
+ voice_context.GetInfo(i).GetInParams().in_use = false;
+ }
+
+ // Update our voices
+ for (std::size_t i = 0; i < voice_count; i++) {
+ auto& in_params = voice_in[i];
+ const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
+ // Skip if it's not currently in use
+ if (!in_params.is_in_use) {
+ continue;
+ }
+ // Voice states for each channel
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
+ ASSERT(static_cast<std::size_t>(in_params.id) < voice_count);
+
+ // Grab our current voice info
+ auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
+
+ ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
+
+ // Get all our channel voice states
+ for (std::size_t channel = 0; channel < channel_count; channel++) {
+ voice_states[channel] =
+ &voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
+ }
+
+ if (in_params.is_new) {
+ // Default our values for our voice
+ voice_info.Initialize();
+ if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
+ continue;
+ }
+
+ // Zero out our voice states
+ for (std::size_t channel = 0; channel < channel_count; channel++) {
+ std::memset(voice_states[channel], 0, sizeof(VoiceState));
+ }
+ }
+
+ // Update our voice
+ voice_info.UpdateParameters(in_params, behavior_info);
+ // TODO(ogniK): Handle mapping errors with behavior info based on in params response
+
+ // Update our wave buffers
+ voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
+ voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
+ output_offset += voice_out_size;
+ output_header.size.voice = static_cast<u32>(voice_out_size);
+ return true;
+}
+
+bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
+ const auto effect_count = effect_context.GetCount();
+ std::vector<EffectInfo::InParams> effect_in(effect_count);
+ std::vector<EffectInfo::OutParams> effect_out(effect_count);
+
+ const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
+ const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
+
+ if (input_header.size.effect != total_effect_in) {
+ LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_effect_in, input_header.size.effect);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
+ input_offset += total_effect_in;
+
+ // Update effects
+ for (std::size_t i = 0; i < effect_count; i++) {
+ auto* info = effect_context.GetInfo(i);
+ if (effect_in[i].type != info->GetType()) {
+ info = effect_context.RetargetEffect(i, effect_in[i].type);
+ }
+
+ info->Update(effect_in[i]);
+
+ if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
+ info->GetUsage() == UsageState::Stopped) {
+ effect_out[i].status = UsageStatus::Removed;
+ } else {
+ effect_out[i].status = UsageStatus::Used;
+ }
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
+ output_offset += total_effect_out;
+ output_header.size.effect = static_cast<u32>(total_effect_out);
+
+ return true;
+}
+
+bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
+ std::size_t start_offset = input_offset;
+ std::size_t bytes_read{};
+ // Update splitter context
+ if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
+ LOG_ERROR(Audio, "Failed to update splitter context!");
+ return false;
+ }
+
+ const auto consumed = input_offset - start_offset;
+
+ if (input_header.size.splitter != consumed) {
+ LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ bytes_read, input_header.size.splitter);
+ return false;
+ }
+
+ return true;
+}
+
+ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
+ SplitterContext& splitter_context,
+ EffectContext& effect_context) {
+ std::vector<MixInfo::InParams> mix_in_params;
+
+ if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ // If we're not dirty, get ALL mix in parameters
+ const auto context_mix_count = mix_context.GetCount();
+ const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
+ if (input_header.size.mixer != total_mix_in) {
+ LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_mix_in, input_header.size.mixer);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ mix_in_params.resize(context_mix_count);
+ std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
+
+ input_offset += total_mix_in;
+ } else {
+ // Only update the "dirty" mixes
+ MixInfo::DirtyHeader dirty_header{};
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
+ sizeof(MixInfo::DirtyHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
+ input_offset += sizeof(MixInfo::DirtyHeader);
+
+ const auto total_mix_in =
+ dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
+
+ if (input_header.size.mixer != total_mix_in) {
+ LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_mix_in, input_header.size.mixer);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ if (dirty_header.mixer_count != 0) {
+ mix_in_params.resize(dirty_header.mixer_count);
+ std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
+ mix_in_params.size() * sizeof(MixInfo::InParams));
+ input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
+ }
+ }
+
+ // Get our total input count
+ const auto mix_count = mix_in_params.size();
+
+ if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ // Only verify our buffer count if we're not dirty
+ std::size_t total_buffer_count{};
+ for (std::size_t i = 0; i < mix_count; i++) {
+ const auto& in = mix_in_params[i];
+ total_buffer_count += in.buffer_count;
+ if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
+ in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
+ LOG_ERROR(
+ Audio,
+ "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
+ in.mix_id, in.dest_mix_id, mix_buffer_count);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+ }
+
+ if (total_buffer_count > mix_buffer_count) {
+ LOG_ERROR(Audio,
+ "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
+ mix_buffer_count, total_buffer_count);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+ }
+
+ if (mix_buffer_count == 0) {
+ LOG_ERROR(Audio, "No mix buffers!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ bool should_sort = false;
+ for (std::size_t i = 0; i < mix_count; i++) {
+ const auto& mix_in = mix_in_params[i];
+ std::size_t target_mix{};
+ if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ target_mix = mix_in.mix_id;
+ } else {
+ // Non dirty supported games just use i instead of the actual mix_id
+ target_mix = i;
+ }
+ auto& mix_info = mix_context.GetInfo(target_mix);
+ auto& mix_info_params = mix_info.GetInParams();
+ if (mix_info_params.in_use != mix_in.in_use) {
+ mix_info_params.in_use = mix_in.in_use;
+ mix_info.ResetEffectProcessingOrder();
+ should_sort = true;
+ }
+
+ if (mix_in.in_use) {
+ should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
+ splitter_context, effect_context);
+ }
+ }
+
+ if (should_sort && behavior_info.IsSplitterSupported()) {
+ // Sort our splitter data
+ if (!mix_context.TsortInfo(splitter_context)) {
+ return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
+ }
+ }
+
+ // TODO(ogniK): Sort when splitter is suppoorted
+
+ return RESULT_SUCCESS;
+}
+
+bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
+ const auto sink_count = sink_context.GetCount();
+ std::vector<SinkInfo::InParams> sink_in_params(sink_count);
+ const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
+
+ if (input_header.size.sink != total_sink_in) {
+ LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_sink_in, input_header.size.effect);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
+ input_offset += total_sink_in;
+
+ // TODO(ogniK): Properly update sinks
+ if (!sink_in_params.empty()) {
+ sink_context.UpdateMainSink(sink_in_params[0]);
+ }
+
+ output_header.size.sink = static_cast<u32>(0x20 * sink_count);
+ output_offset += 0x20 * sink_count;
+ return true;
+}
+
+bool InfoUpdater::UpdatePerformanceBuffer() {
+ output_header.size.performance = 0x10;
+ output_offset += 0x10;
+ return true;
+}
+
+bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
+ const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ BehaviorInfo::OutParams behavior_info_out{};
+ behavior_info.CopyErrorInfo(behavior_info_out);
+
+ std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
+ output_offset += total_beahvior_info_out;
+ output_header.size.behavior = total_beahvior_info_out;
+
+ return true;
+}
+
+struct RendererInfo {
+ u64_le elasped_frame_count{};
+ INSERT_PADDING_WORDS(2);
+};
+static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
+
+bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
+ const auto total_renderer_info_out = sizeof(RendererInfo);
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ RendererInfo out{};
+ out.elasped_frame_count = elapsed_frame_count;
+ std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
+ output_offset += total_renderer_info_out;
+ output_header.size.render_info = total_renderer_info_out;
+
+ return true;
+}
+
+bool InfoUpdater::CheckConsumedSize() const {
+ if (output_offset != out_params.size()) {
+ LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
+ output_offset, out_params.size(), out_params.size() - output_offset);
+ return false;
+ }
+ /*if (input_offset != in_params.size()) {
+ LOG_ERROR(Audio, "Input is not consumed!");
+ return false;
+ }*/
+ return true;
+}
+
+bool InfoUpdater::WriteOutputHeader() {
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
+ sizeof(AudioCommon::UpdateDataHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
+ const auto& sz = output_header.size;
+ output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
+ sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
+ sz.performance + sz.splitter + sz.render_info;
+
+ std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
+ return true;
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "audio_core/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+class BehaviorInfo;
+class ServerMemoryPoolInfo;
+class VoiceContext;
+class EffectContext;
+class MixContext;
+class SinkContext;
+class SplitterContext;
+
+class InfoUpdater {
+public:
+ // TODO(ogniK): Pass process handle when we support it
+ InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
+ BehaviorInfo& behavior_info);
+ ~InfoUpdater();
+
+ bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
+ bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
+ bool UpdateVoiceChannelResources(VoiceContext& voice_context);
+ bool UpdateVoices(VoiceContext& voice_context,
+ std::vector<ServerMemoryPoolInfo>& memory_pool_info,
+ VAddr audio_codec_dsp_addr);
+ bool UpdateEffects(EffectContext& effect_context, bool is_active);
+ bool UpdateSplitterInfo(SplitterContext& splitter_context);
+ ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
+ SplitterContext& splitter_context, EffectContext& effect_context);
+ bool UpdateSinks(SinkContext& sink_context);
+ bool UpdatePerformanceBuffer();
+ bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
+ bool UpdateRendererInfo(std::size_t elapsed_frame_count);
+ bool CheckConsumedSize() const;
+
+ bool WriteOutputHeader();
+
+private:
+ const std::vector<u8>& in_params;
+ std::vector<u8>& out_params;
+ BehaviorInfo& behavior_info;
+
+ AudioCommon::UpdateDataHeader input_header{};
+ AudioCommon::UpdateDataHeader output_header{};
+
+ std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
+ std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
+};
+
+} // 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 @@
+
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/memory_pool.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
+ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
+bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
+ ServerMemoryPoolInfo::OutParams& out_params) {
+ // Our state does not need to be changed
+ if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
+ in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
+ return true;
+ }
+
+ // Address or size is null
+ if (in_params.address == 0 || in_params.size == 0) {
+ LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
+ in_params.address, in_params.size);
+ return false;
+ }
+
+ // Address or size is not aligned
+ if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
+ LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
+ in_params.address, in_params.size);
+ return false;
+ }
+
+ if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
+ cpu_address = in_params.address;
+ size = in_params.size;
+ used = true;
+ out_params.state = ServerMemoryPoolInfo::State::Attached;
+ } else {
+ // Unexpected address
+ if (cpu_address != in_params.address) {
+ LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
+ cpu_address, in_params.address);
+ return false;
+ }
+
+ if (size != in_params.size) {
+ LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
+ in_params.size);
+ return false;
+ }
+
+ cpu_address = 0;
+ size = 0;
+ used = false;
+ out_params.state = ServerMemoryPoolInfo::State::Detached;
+ }
+ return true;
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+
+class ServerMemoryPoolInfo {
+public:
+ ServerMemoryPoolInfo();
+ ~ServerMemoryPoolInfo();
+
+ enum class State : u32_le {
+ Invalid = 0x0,
+ Aquired = 0x1,
+ RequestDetach = 0x2,
+ Detached = 0x3,
+ RequestAttach = 0x4,
+ Attached = 0x5,
+ Released = 0x6,
+ };
+
+ struct InParams {
+ u64_le address{};
+ u64_le size{};
+ ServerMemoryPoolInfo::State state{};
+ INSERT_PADDING_WORDS(3);
+ };
+ static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
+
+ struct OutParams {
+ ServerMemoryPoolInfo::State state{};
+ INSERT_PADDING_WORDS(3);
+ };
+ static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
+
+ bool Update(const ServerMemoryPoolInfo::InParams& in_params,
+ ServerMemoryPoolInfo::OutParams& out_params);
+
+private:
+ // There's another entry here which is the DSP address, however since we're not talking to the
+ // DSP we can just use the same address provided by the guest without needing to remap
+ u64_le cpu_address{};
+ u64_le size{};
+ bool used{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
new file mode 100644
index 000000000..4bca72eb0
--- /dev/null
+++ b/src/audio_core/mix_context.cpp
@@ -0,0 +1,296 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/common.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/splitter_context.h"
+
+namespace AudioCore {
+MixContext::MixContext() = default;
+MixContext::~MixContext() = default;
+
+void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count) {
+ info_count = mix_count;
+ infos.resize(info_count);
+ auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
+ final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
+ sorted_info.reserve(infos.size());
+ for (auto& info : infos) {
+ sorted_info.push_back(&info);
+ }
+
+ for (auto& info : infos) {
+ info.SetEffectCount(effect_count);
+ }
+
+ // Only initialize our edge matrix and node states if splitters are supported
+ if (behavior_info.IsSplitterSupported()) {
+ node_states.Initialize(mix_count);
+ edge_matrix.Initialize(mix_count);
+ }
+}
+
+void MixContext::UpdateDistancesFromFinalMix() {
+ // Set all distances to be invalid
+ for (std::size_t i = 0; i < info_count; i++) {
+ GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
+ }
+
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& info = GetInfo(i);
+ auto& in_params = info.GetInParams();
+ // Populate our sorted info
+ sorted_info[i] = &info;
+
+ if (!in_params.in_use) {
+ continue;
+ }
+
+ auto mix_id = in_params.mix_id;
+ // Needs to be referenced out of scope
+ s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
+ for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
+ if (mix_id == AudioCommon::FINAL_MIX) {
+ // If we're at the final mix, we're done
+ break;
+ } else if (mix_id == AudioCommon::NO_MIX) {
+ // If we have no more mix ids, we're done
+ distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
+ break;
+ } else {
+ const auto& dest_mix = GetInfo(mix_id);
+ const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
+
+ if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
+ // If our current mix isn't pointing to a final mix, follow through
+ mix_id = dest_mix.GetInParams().dest_mix_id;
+ } else {
+ // Our current mix + 1 = final distance
+ distance_to_final_mix = dest_mix_distance + 1;
+ break;
+ }
+ }
+ }
+
+ // If we're out of range for our distance, mark it as no final mix
+ if (distance_to_final_mix >= static_cast<s32>(info_count)) {
+ distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
+ }
+
+ in_params.final_mix_distance = distance_to_final_mix;
+ }
+}
+
+void MixContext::CalcMixBufferOffset() {
+ s32 offset{};
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& info = GetSortedInfo(i);
+ auto& in_params = info.GetInParams();
+ if (in_params.in_use) {
+ // Only update if in use
+ in_params.buffer_offset = offset;
+ offset += in_params.buffer_count;
+ }
+ }
+}
+
+void MixContext::SortInfo() {
+ // Get the distance to the final mix
+ UpdateDistancesFromFinalMix();
+
+ // Sort based on the distance to the final mix
+ std::sort(sorted_info.begin(), sorted_info.end(),
+ [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
+ return lhs->GetInParams().final_mix_distance >
+ rhs->GetInParams().final_mix_distance;
+ });
+
+ // Calculate the mix buffer offset
+ CalcMixBufferOffset();
+}
+
+bool MixContext::TsortInfo(SplitterContext& splitter_context) {
+ // If we're not using mixes, just calculate the mix buffer offset
+ if (!splitter_context.UsingSplitter()) {
+ CalcMixBufferOffset();
+ return true;
+ }
+ // Sort our node states
+ if (!node_states.Tsort(edge_matrix)) {
+ return false;
+ }
+
+ // Get our sorted list
+ const auto sorted_list = node_states.GetIndexList();
+ std::size_t info_id{};
+ for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
+ // Set our sorted info
+ sorted_info[info_id++] = &GetInfo(*itr);
+ }
+
+ // Calculate the mix buffer offset
+ CalcMixBufferOffset();
+ return true;
+}
+
+std::size_t MixContext::GetCount() const {
+ return info_count;
+}
+
+ServerMixInfo& MixContext::GetInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return *sorted_info.at(i);
+}
+
+const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return *sorted_info.at(i);
+}
+
+ServerMixInfo& MixContext::GetFinalMixInfo() {
+ return infos.at(AudioCommon::FINAL_MIX);
+}
+
+const ServerMixInfo& MixContext::GetFinalMixInfo() const {
+ return infos.at(AudioCommon::FINAL_MIX);
+}
+
+EdgeMatrix& MixContext::GetEdgeMatrix() {
+ return edge_matrix;
+}
+
+const EdgeMatrix& MixContext::GetEdgeMatrix() const {
+ return edge_matrix;
+}
+
+ServerMixInfo::ServerMixInfo() {
+ Cleanup();
+}
+ServerMixInfo::~ServerMixInfo() = default;
+
+const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
+ return in_params;
+}
+
+ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
+ return in_params;
+}
+
+bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context) {
+ in_params.volume = mix_in.volume;
+ in_params.sample_rate = mix_in.sample_rate;
+ in_params.buffer_count = mix_in.buffer_count;
+ in_params.in_use = mix_in.in_use;
+ in_params.mix_id = mix_in.mix_id;
+ in_params.node_id = mix_in.node_id;
+ for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
+ std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
+ in_params.mix_volume[i].begin());
+ }
+
+ bool require_sort = false;
+
+ if (behavior_info.IsSplitterSupported()) {
+ require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
+ } else {
+ in_params.dest_mix_id = mix_in.dest_mix_id;
+ in_params.splitter_id = AudioCommon::NO_SPLITTER;
+ }
+
+ ResetEffectProcessingOrder();
+ const auto effect_count = effect_context.GetCount();
+ for (std::size_t i = 0; i < effect_count; i++) {
+ auto* effect_info = effect_context.GetInfo(i);
+ if (effect_info->GetMixID() == in_params.mix_id) {
+ effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
+ }
+ }
+
+ // TODO(ogniK): Update effect processing order
+ return require_sort;
+}
+
+bool ServerMixInfo::HasAnyConnection() const {
+ return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
+ in_params.mix_id != AudioCommon::NO_MIX;
+}
+
+void ServerMixInfo::Cleanup() {
+ in_params.volume = 0.0f;
+ in_params.sample_rate = 0;
+ in_params.buffer_count = 0;
+ in_params.in_use = false;
+ in_params.mix_id = AudioCommon::NO_MIX;
+ in_params.node_id = 0;
+ in_params.buffer_offset = 0;
+ in_params.dest_mix_id = AudioCommon::NO_MIX;
+ in_params.splitter_id = AudioCommon::NO_SPLITTER;
+ std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
+}
+
+void ServerMixInfo::SetEffectCount(std::size_t count) {
+ effect_processing_order.resize(count);
+ ResetEffectProcessingOrder();
+}
+
+void ServerMixInfo::ResetEffectProcessingOrder() {
+ for (auto& order : effect_processing_order) {
+ order = AudioCommon::NO_EFFECT_ORDER;
+ }
+}
+
+s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
+ return effect_processing_order.at(i);
+}
+
+bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ SplitterContext& splitter_context) {
+ // Mixes are identical
+ if (in_params.dest_mix_id == mix_in.dest_mix_id &&
+ in_params.splitter_id == mix_in.splitter_id &&
+ ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
+ !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
+ return false;
+ }
+ // Remove current edges for mix id
+ edge_matrix.RemoveEdges(in_params.mix_id);
+ if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
+ // If we have a valid destination mix id, set our edge matrix
+ edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
+ } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
+ // Recurse our splitter linked and set our edges
+ auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
+ const auto length = splitter_info.GetLength();
+ for (s32 i = 0; i < length; i++) {
+ const auto* splitter_destination =
+ splitter_context.GetDestinationData(mix_in.splitter_id, i);
+ if (splitter_destination == nullptr) {
+ continue;
+ }
+ if (splitter_destination->ValidMixId()) {
+ edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
+ }
+ }
+ }
+ in_params.dest_mix_id = mix_in.dest_mix_id;
+ in_params.splitter_id = mix_in.splitter_id;
+ return true;
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "audio_core/common.h"
+#include "audio_core/splitter_context.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+class BehaviorInfo;
+class EffectContext;
+
+class MixInfo {
+public:
+ struct DirtyHeader {
+ u32_le magic{};
+ u32_le mixer_count{};
+ INSERT_PADDING_BYTES(0x18);
+ };
+ static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
+
+ struct InParams {
+ float_le volume{};
+ s32_le sample_rate{};
+ s32_le buffer_count{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(3);
+ s32_le mix_id{};
+ s32_le effect_count{};
+ u32_le node_id{};
+ INSERT_PADDING_WORDS(2);
+ std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
+ mix_volume{};
+ s32_le dest_mix_id{};
+ s32_le splitter_id{};
+ INSERT_PADDING_WORDS(1);
+ };
+ static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
+};
+
+class ServerMixInfo {
+public:
+ struct InParams {
+ float volume{};
+ s32 sample_rate{};
+ s32 buffer_count{};
+ bool in_use{};
+ s32 mix_id{};
+ u32 node_id{};
+ std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
+ mix_volume{};
+ s32 dest_mix_id{};
+ s32 splitter_id{};
+ s32 buffer_offset{};
+ s32 final_mix_distance{};
+ };
+ ServerMixInfo();
+ ~ServerMixInfo();
+
+ const ServerMixInfo::InParams& GetInParams() const;
+ ServerMixInfo::InParams& GetInParams();
+
+ bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context);
+ bool HasAnyConnection() const;
+ void Cleanup();
+ void SetEffectCount(std::size_t count);
+ void ResetEffectProcessingOrder();
+ s32 GetEffectOrder(std::size_t i) const;
+
+private:
+ std::vector<s32> effect_processing_order;
+ InParams in_params{};
+ bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ SplitterContext& splitter_context);
+};
+
+class MixContext {
+public:
+ MixContext();
+ ~MixContext();
+
+ void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count);
+ void SortInfo();
+ bool TsortInfo(SplitterContext& splitter_context);
+
+ std::size_t GetCount() const;
+ ServerMixInfo& GetInfo(std::size_t i);
+ const ServerMixInfo& GetInfo(std::size_t i) const;
+ ServerMixInfo& GetSortedInfo(std::size_t i);
+ const ServerMixInfo& GetSortedInfo(std::size_t i) const;
+ ServerMixInfo& GetFinalMixInfo();
+ const ServerMixInfo& GetFinalMixInfo() const;
+ EdgeMatrix& GetEdgeMatrix();
+ const EdgeMatrix& GetEdgeMatrix() const;
+
+private:
+ void CalcMixBufferOffset();
+ void UpdateDistancesFromFinalMix();
+
+ NodeStates node_states{};
+ EdgeMatrix edge_matrix{};
+ std::size_t info_count{};
+ std::vector<ServerMixInfo> infos{};
+ std::vector<ServerMixInfo*> sorted_info{};
+};
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/sink_context.h"
+
+namespace AudioCore {
+SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
+SinkContext::~SinkContext() = default;
+
+std::size_t SinkContext::GetCount() const {
+ return sink_count;
+}
+
+void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
+ in_use = in.in_use;
+ use_count = in.device.input_count;
+ std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
+}
+
+bool SinkContext::InUse() const {
+ return in_use;
+}
+
+std::vector<u8> SinkContext::OutputBuffers() const {
+ std::vector<u8> buffer_ret(use_count);
+ std::memcpy(buffer_ret.data(), buffers.data(), use_count);
+ return buffer_ret;
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "audio_core/common.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+
+enum class SinkTypes : u8 {
+ Invalid = 0,
+ Device = 1,
+ Circular = 2,
+};
+
+enum class SinkSampleFormat : u32_le {
+ None = 0,
+ Pcm8 = 1,
+ Pcm16 = 2,
+ Pcm24 = 3,
+ Pcm32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6,
+};
+
+class SinkInfo {
+public:
+ struct CircularBufferIn {
+ u64_le address;
+ u32_le size;
+ u32_le input_count;
+ u32_le sample_count;
+ u32_le previous_position;
+ SinkSampleFormat sample_format;
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
+ bool in_use;
+ INSERT_UNION_PADDING_BYTES(5);
+ };
+ static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
+ "SinkInfo::CircularBufferIn is in invalid size");
+
+ struct DeviceIn {
+ std::array<u8, 255> device_name;
+ INSERT_UNION_PADDING_BYTES(1);
+ s32_le input_count;
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
+ INSERT_UNION_PADDING_BYTES(1);
+ bool down_matrix_enabled;
+ std::array<float_le, 4> down_matrix_coef;
+ };
+ static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
+
+ struct InParams {
+ SinkTypes type{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(2);
+ u32_le node_id{};
+ INSERT_PADDING_WORDS(6);
+ union {
+ // std::array<u8, 0x120> raw{};
+ SinkInfo::DeviceIn device;
+ SinkInfo::CircularBufferIn circular_buffer;
+ };
+ };
+ static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
+};
+
+class SinkContext {
+public:
+ explicit SinkContext(std::size_t sink_count);
+ ~SinkContext();
+
+ std::size_t GetCount() const;
+
+ void UpdateMainSink(SinkInfo::InParams& in);
+ bool InUse() const;
+ std::vector<u8> OutputBuffers() const;
+
+private:
+ bool in_use{false};
+ s32 use_count{};
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
+ std::size_t sink_count{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
new file mode 100644
index 000000000..f21b53147
--- /dev/null
+++ b/src/audio_core/splitter_context.cpp
@@ -0,0 +1,617 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/splitter_context.h"
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
+ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
+
+void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
+ // Log error as these are not actually failure states
+ if (header.magic != SplitterMagic::DataHeader) {
+ LOG_ERROR(Audio, "Splitter destination header is invalid!");
+ return;
+ }
+
+ // Incorrect splitter id
+ if (header.splitter_id != id) {
+ LOG_ERROR(Audio, "Splitter destination ids do not match!");
+ return;
+ }
+
+ mix_id = header.mix_id;
+ // Copy our mix volumes
+ std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
+ if (!in_use && header.in_use) {
+ // Update mix volumes
+ std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
+ needs_update = false;
+ }
+ in_use = header.in_use;
+}
+
+ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
+ return next;
+}
+
+const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
+ return next;
+}
+
+void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
+ next = dest;
+}
+
+bool ServerSplitterDestinationData::ValidMixId() const {
+ return GetMixId() != AudioCommon::NO_MIX;
+}
+
+s32 ServerSplitterDestinationData::GetMixId() const {
+ return mix_id;
+}
+
+bool ServerSplitterDestinationData::IsConfigured() const {
+ return in_use && ValidMixId();
+}
+
+float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return current_mix_volumes.at(i);
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerSplitterDestinationData::CurrentMixVolumes() const {
+ return current_mix_volumes;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerSplitterDestinationData::LastMixVolumes() const {
+ return last_mix_volumes;
+}
+
+void ServerSplitterDestinationData::MarkDirty() {
+ needs_update = true;
+}
+
+void ServerSplitterDestinationData::UpdateInternalState() {
+ if (in_use && needs_update) {
+ std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
+ }
+ needs_update = false;
+}
+
+ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
+ServerSplitterInfo::~ServerSplitterInfo() = default;
+
+void ServerSplitterInfo::InitializeInfos() {
+ send_length = 0;
+ head = nullptr;
+ new_connection = true;
+}
+
+void ServerSplitterInfo::ClearNewConnectionFlag() {
+ new_connection = false;
+}
+
+std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
+ if (header.send_id != id) {
+ return 0;
+ }
+
+ sample_rate = header.sample_rate;
+ new_connection = true;
+ // We need to update the size here due to the splitter bug being present and providing an
+ // incorrect size. We're suppose to also update the header here but we just ignore and continue
+ return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
+}
+
+ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
+ return head;
+}
+
+const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
+ return head;
+}
+
+ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
+ auto current_head = head;
+ for (std::size_t i = 0; i < depth; i++) {
+ if (current_head == nullptr) {
+ return nullptr;
+ }
+ current_head = current_head->GetNextDestination();
+ }
+ return current_head;
+}
+
+const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
+ auto current_head = head;
+ for (std::size_t i = 0; i < depth; i++) {
+ if (current_head == nullptr) {
+ return nullptr;
+ }
+ current_head = current_head->GetNextDestination();
+ }
+ return current_head;
+}
+
+bool ServerSplitterInfo::HasNewConnection() const {
+ return new_connection;
+}
+
+s32 ServerSplitterInfo::GetLength() const {
+ return send_length;
+}
+
+void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
+ head = new_head;
+}
+
+void ServerSplitterInfo::SetHeadDepth(s32 length) {
+ send_length = length;
+}
+
+SplitterContext::SplitterContext() = default;
+SplitterContext::~SplitterContext() = default;
+
+void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
+ std::size_t _data_count) {
+ if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
+ Setup(0, 0, false);
+ return;
+ }
+ // Only initialize if we're using splitters
+ Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
+}
+
+bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ if (info_count == 0 || data_count == 0) {
+ bytes_read = 0;
+ return true;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InHeader header{};
+ std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
+ UpdateOffsets(sizeof(SplitterInfo::InHeader));
+
+ if (header.magic != SplitterMagic::SplitterHeader) {
+ LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
+ SplitterMagic::SplitterHeader, header.magic);
+ return false;
+ }
+
+ // Clear all connections
+ for (auto& info : infos) {
+ info.ClearNewConnectionFlag();
+ }
+
+ UpdateInfo(input, input_offset, bytes_read, header.info_count);
+ UpdateData(input, input_offset, bytes_read, header.data_count);
+ const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
+ input_offset += aligned_bytes_read - bytes_read;
+ bytes_read = aligned_bytes_read;
+ return true;
+}
+
+bool SplitterContext::UsingSplitter() const {
+ return info_count > 0 && data_count > 0;
+}
+
+ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
+ ASSERT(i < data_count);
+ return datas.at(i);
+}
+
+const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
+ ASSERT(i < data_count);
+ return datas.at(i);
+}
+
+ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
+ std::size_t data) {
+ ASSERT(info < info_count);
+ auto& cur_info = GetInfo(info);
+ return cur_info.GetData(data);
+}
+
+const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
+ std::size_t data) const {
+ ASSERT(info < info_count);
+ auto& cur_info = GetInfo(info);
+ return cur_info.GetData(data);
+}
+
+void SplitterContext::UpdateInternalState() {
+ if (data_count == 0) {
+ return;
+ }
+
+ for (auto& data : datas) {
+ data.UpdateInternalState();
+ }
+}
+
+std::size_t SplitterContext::GetInfoCount() const {
+ return info_count;
+}
+
+std::size_t SplitterContext::GetDataCount() const {
+ return data_count;
+}
+
+void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
+ bool is_splitter_bug_fixed) {
+
+ info_count = _info_count;
+ data_count = _data_count;
+
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& splitter = infos.emplace_back(static_cast<s32>(i));
+ splitter.InitializeInfos();
+ }
+ for (std::size_t i = 0; i < data_count; i++) {
+ datas.emplace_back(static_cast<s32>(i));
+ }
+
+ bug_fixed = is_splitter_bug_fixed;
+}
+
+bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_splitter_count) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ for (s32 i = 0; i < in_splitter_count; i++) {
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InInfoPrams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InInfoPrams header{};
+ std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
+
+ // Logged as warning as these don't actually cause a bailout for some reason
+ if (header.magic != SplitterMagic::InfoHeader) {
+ LOG_ERROR(Audio, "Bad splitter data header");
+ break;
+ }
+
+ if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
+ LOG_ERROR(Audio, "Bad splitter data id");
+ break;
+ }
+
+ UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
+ auto& info = GetInfo(header.send_id);
+ if (!RecomposeDestination(info, header, input, input_offset)) {
+ LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
+ return false;
+ }
+ const std::size_t read = info.Update(header);
+ bytes_read += read;
+ input_offset += read;
+ }
+ return true;
+}
+
+bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_data_count) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ for (s32 i = 0; i < in_data_count; i++) {
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InDestinationParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InDestinationParams header{};
+ std::memcpy(&header, input.data() + input_offset,
+ sizeof(SplitterInfo::InDestinationParams));
+ UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
+
+ // Logged as warning as these don't actually cause a bailout for some reason
+ if (header.magic != SplitterMagic::DataHeader) {
+ LOG_ERROR(Audio, "Bad splitter data header");
+ break;
+ }
+
+ if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
+ LOG_ERROR(Audio, "Bad splitter data id");
+ break;
+ }
+ GetData(header.splitter_id).Update(header);
+ }
+ return true;
+}
+
+bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
+ SplitterInfo::InInfoPrams& header,
+ const std::vector<u8>& input,
+ const std::size_t& input_offset) {
+ // Clear our current destinations
+ auto* current_head = info.GetHead();
+ while (current_head != nullptr) {
+ auto next_head = current_head->GetNextDestination();
+ current_head->SetNextDestination(nullptr);
+ current_head = next_head;
+ }
+ info.SetHead(nullptr);
+
+ s32 size = header.length;
+ // If the splitter bug is present, calculate fixed size
+ if (!bug_fixed) {
+ if (info_count > 0) {
+ const auto factor = data_count / info_count;
+ size = std::min(header.length, static_cast<s32>(factor));
+ } else {
+ size = 0;
+ }
+ }
+
+ if (size < 1) {
+ LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
+ return true;
+ }
+
+ auto* start_head = &GetData(header.resource_id_base);
+ current_head = start_head;
+ std::vector<s32_le> resource_ids(size - 1);
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ resource_ids.size() * sizeof(s32_le))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ std::memcpy(resource_ids.data(), input.data() + input_offset,
+ resource_ids.size() * sizeof(s32_le));
+
+ for (auto resource_id : resource_ids) {
+ auto* head = &GetData(resource_id);
+ current_head->SetNextDestination(head);
+ current_head = head;
+ }
+
+ info.SetHead(start_head);
+ info.SetHeadDepth(size);
+
+ return true;
+}
+
+NodeStates::NodeStates() = default;
+NodeStates::~NodeStates() = default;
+
+void NodeStates::Initialize(std::size_t node_count_) {
+ // Setup our work parameters
+ node_count = node_count_;
+ was_node_found.resize(node_count);
+ was_node_completed.resize(node_count);
+ index_list.resize(node_count);
+ index_stack.Reset(node_count * node_count);
+}
+
+bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
+ return DepthFirstSearch(edge_matrix);
+}
+
+std::size_t NodeStates::GetIndexPos() const {
+ return index_pos;
+}
+
+const std::vector<s32>& NodeStates::GetIndexList() const {
+ return index_list;
+}
+
+void NodeStates::PushTsortResult(s32 index) {
+ ASSERT(index < static_cast<s32>(node_count));
+ index_list[index_pos++] = index;
+}
+
+bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
+ ResetState();
+ for (std::size_t i = 0; i < node_count; i++) {
+ const auto node_id = static_cast<s32>(i);
+
+ // If we don't have a state, send to our index stack for work
+ if (GetState(i) == NodeStates::State::NoState) {
+ index_stack.push(node_id);
+ }
+
+ // While we have work to do in our stack
+ while (index_stack.Count() > 0) {
+ // Get the current node
+ const auto current_stack_index = index_stack.top();
+ // Check if we've seen the node yet
+ const auto index_state = GetState(current_stack_index);
+ if (index_state == NodeStates::State::NoState) {
+ // Mark the node as seen
+ UpdateState(NodeStates::State::InFound, current_stack_index);
+ } else if (index_state == NodeStates::State::InFound) {
+ // We've seen this node before, mark it as completed
+ UpdateState(NodeStates::State::InCompleted, current_stack_index);
+ // Update our index list
+ PushTsortResult(current_stack_index);
+ // Pop the stack
+ index_stack.pop();
+ continue;
+ } else if (index_state == NodeStates::State::InCompleted) {
+ // If our node is already sorted, clear it
+ index_stack.pop();
+ continue;
+ }
+
+ const auto node_count = edge_matrix.GetNodeCount();
+ for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
+ // Check if our node is connected to our edge matrix
+ if (!edge_matrix.Connected(current_stack_index, j)) {
+ continue;
+ }
+
+ // Check if our node exists
+ const auto node_state = GetState(j);
+ if (node_state == NodeStates::State::NoState) {
+ // Add more work
+ index_stack.push(j);
+ } else if (node_state == NodeStates::State::InFound) {
+ UNREACHABLE_MSG("Node start marked as found");
+ ResetState();
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void NodeStates::ResetState() {
+ // Reset to the start of our index stack
+ index_pos = 0;
+ for (std::size_t i = 0; i < node_count; i++) {
+ // Mark all nodes as not found
+ was_node_found[i] = false;
+ // Mark all nodes as uncompleted
+ was_node_completed[i] = false;
+ // Mark all indexes as invalid
+ index_list[i] = -1;
+ }
+}
+
+void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
+ switch (state) {
+ case NodeStates::State::NoState:
+ was_node_found[i] = false;
+ was_node_completed[i] = false;
+ break;
+ case NodeStates::State::InFound:
+ was_node_found[i] = true;
+ was_node_completed[i] = false;
+ break;
+ case NodeStates::State::InCompleted:
+ was_node_found[i] = false;
+ was_node_completed[i] = true;
+ break;
+ }
+}
+
+NodeStates::State NodeStates::GetState(std::size_t i) {
+ ASSERT(i < node_count);
+ if (was_node_found[i]) {
+ // If our node exists in our found list
+ return NodeStates::State::InFound;
+ } else if (was_node_completed[i]) {
+ // If node is in the completed list
+ return NodeStates::State::InCompleted;
+ } else {
+ // If in neither
+ return NodeStates::State::NoState;
+ }
+}
+
+NodeStates::Stack::Stack() = default;
+NodeStates::Stack::~Stack() = default;
+
+void NodeStates::Stack::Reset(std::size_t size) {
+ // Mark our stack as empty
+ stack.resize(size);
+ stack_size = size;
+ stack_pos = 0;
+ std::fill(stack.begin(), stack.end(), 0);
+}
+
+void NodeStates::Stack::push(s32 val) {
+ ASSERT(stack_pos < stack_size);
+ stack[stack_pos++] = val;
+}
+
+std::size_t NodeStates::Stack::Count() const {
+ return stack_pos;
+}
+
+s32 NodeStates::Stack::top() const {
+ ASSERT(stack_pos > 0);
+ return stack[stack_pos - 1];
+}
+
+s32 NodeStates::Stack::pop() {
+ ASSERT(stack_pos > 0);
+ stack_pos--;
+ return stack[stack_pos];
+}
+
+EdgeMatrix::EdgeMatrix() = default;
+EdgeMatrix::~EdgeMatrix() = default;
+
+void EdgeMatrix::Initialize(std::size_t _node_count) {
+ node_count = _node_count;
+ edge_matrix.resize(node_count * node_count);
+}
+
+bool EdgeMatrix::Connected(s32 a, s32 b) {
+ return GetState(a, b);
+}
+
+void EdgeMatrix::Connect(s32 a, s32 b) {
+ SetState(a, b, true);
+}
+
+void EdgeMatrix::Disconnect(s32 a, s32 b) {
+ SetState(a, b, false);
+}
+
+void EdgeMatrix::RemoveEdges(s32 edge) {
+ for (std::size_t i = 0; i < node_count; i++) {
+ SetState(edge, static_cast<s32>(i), false);
+ }
+}
+
+std::size_t EdgeMatrix::GetNodeCount() const {
+ return node_count;
+}
+
+void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
+ ASSERT(InRange(a, b));
+ edge_matrix.at(a * node_count + b) = state;
+}
+
+bool EdgeMatrix::GetState(s32 a, s32 b) {
+ ASSERT(InRange(a, b));
+ return edge_matrix.at(a * node_count + b);
+}
+
+bool EdgeMatrix::InRange(s32 a, s32 b) const {
+ const std::size_t pos = a * node_count + b;
+ return pos < (node_count * node_count);
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <stack>
+#include <vector>
+#include "audio_core/common.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+class BehaviorInfo;
+
+class EdgeMatrix {
+public:
+ EdgeMatrix();
+ ~EdgeMatrix();
+
+ void Initialize(std::size_t _node_count);
+ bool Connected(s32 a, s32 b);
+ void Connect(s32 a, s32 b);
+ void Disconnect(s32 a, s32 b);
+ void RemoveEdges(s32 edge);
+ std::size_t GetNodeCount() const;
+
+private:
+ void SetState(s32 a, s32 b, bool state);
+ bool GetState(s32 a, s32 b);
+
+ bool InRange(s32 a, s32 b) const;
+ std::vector<bool> edge_matrix{};
+ std::size_t node_count{};
+};
+
+class NodeStates {
+public:
+ enum class State {
+ NoState = 0,
+ InFound = 1,
+ InCompleted = 2,
+ };
+
+ // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
+ class Stack {
+ public:
+ Stack();
+ ~Stack();
+
+ void Reset(std::size_t size);
+ void push(s32 val);
+ std::size_t Count() const;
+ s32 top() const;
+ s32 pop();
+
+ private:
+ std::vector<s32> stack{};
+ std::size_t stack_size{};
+ std::size_t stack_pos{};
+ };
+ NodeStates();
+ ~NodeStates();
+
+ void Initialize(std::size_t _node_count);
+ bool Tsort(EdgeMatrix& edge_matrix);
+ std::size_t GetIndexPos() const;
+ const std::vector<s32>& GetIndexList() const;
+
+private:
+ void PushTsortResult(s32 index);
+ bool DepthFirstSearch(EdgeMatrix& edge_matrix);
+ void ResetState();
+ void UpdateState(NodeStates::State state, std::size_t i);
+ NodeStates::State GetState(std::size_t i);
+
+ std::size_t node_count{};
+ std::vector<bool> was_node_found{};
+ std::vector<bool> was_node_completed{};
+ std::size_t index_pos{};
+ std::vector<s32> index_list{};
+ NodeStates::Stack index_stack{};
+};
+
+enum class SplitterMagic : u32_le {
+ SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
+ DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
+ InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
+};
+
+class SplitterInfo {
+public:
+ struct InHeader {
+ SplitterMagic magic{};
+ s32_le info_count{};
+ s32_le data_count{};
+ INSERT_PADDING_WORDS(5);
+ };
+ static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
+ "SplitterInfo::InHeader is an invalid size");
+
+ struct InInfoPrams {
+ SplitterMagic magic{};
+ s32_le send_id{};
+ s32_le sample_rate{};
+ s32_le length{};
+ s32_le resource_id_base{};
+ };
+ static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
+ "SplitterInfo::InInfoPrams is an invalid size");
+
+ struct InDestinationParams {
+ SplitterMagic magic{};
+ s32_le splitter_id{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
+ s32_le mix_id{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
+ "SplitterInfo::InDestinationParams is an invalid size");
+};
+
+class ServerSplitterDestinationData {
+public:
+ explicit ServerSplitterDestinationData(s32 id);
+ ~ServerSplitterDestinationData();
+
+ void Update(SplitterInfo::InDestinationParams& header);
+
+ ServerSplitterDestinationData* GetNextDestination();
+ const ServerSplitterDestinationData* GetNextDestination() const;
+ void SetNextDestination(ServerSplitterDestinationData* dest);
+ bool ValidMixId() const;
+ s32 GetMixId() const;
+ bool IsConfigured() const;
+ float GetMixVolume(std::size_t i) const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
+ void MarkDirty();
+ void UpdateInternalState();
+
+private:
+ bool needs_update{};
+ bool in_use{};
+ s32 id{};
+ s32 mix_id{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
+ ServerSplitterDestinationData* next = nullptr;
+};
+
+class ServerSplitterInfo {
+public:
+ explicit ServerSplitterInfo(s32 id);
+ ~ServerSplitterInfo();
+
+ void InitializeInfos();
+ void ClearNewConnectionFlag();
+ std::size_t Update(SplitterInfo::InInfoPrams& header);
+
+ ServerSplitterDestinationData* GetHead();
+ const ServerSplitterDestinationData* GetHead() const;
+ ServerSplitterDestinationData* GetData(std::size_t depth);
+ const ServerSplitterDestinationData* GetData(std::size_t depth) const;
+
+ bool HasNewConnection() const;
+ s32 GetLength() const;
+
+ void SetHead(ServerSplitterDestinationData* new_head);
+ void SetHeadDepth(s32 length);
+
+private:
+ s32 sample_rate{};
+ s32 id{};
+ s32 send_length{};
+ ServerSplitterDestinationData* head = nullptr;
+ bool new_connection{};
+};
+
+class SplitterContext {
+public:
+ SplitterContext();
+ ~SplitterContext();
+
+ void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
+ std::size_t data_count);
+
+ bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
+ bool UsingSplitter() const;
+
+ ServerSplitterInfo& GetInfo(std::size_t i);
+ const ServerSplitterInfo& GetInfo(std::size_t i) const;
+ ServerSplitterDestinationData& GetData(std::size_t i);
+ const ServerSplitterDestinationData& GetData(std::size_t i) const;
+ ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
+ const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
+ std::size_t data) const;
+ void UpdateInternalState();
+
+ std::size_t GetInfoCount() const;
+ std::size_t GetDataCount() const;
+
+private:
+ void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
+ bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_splitter_count);
+ bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_data_count);
+ bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
+ const std::vector<u8>& input, const std::size_t& input_offset);
+
+ std::vector<ServerSplitterInfo> infos{};
+ std::vector<ServerSplitterDestinationData> datas{};
+
+ std::size_t info_count{};
+ std::size_t data_count{};
+ bool bug_fixed{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 4ca98f8ea..4bbb1e0c4 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -12,7 +12,6 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core_timing.h"
-#include "core/core_timing_util.h"
#include "core/settings.h"
namespace AudioCore {
@@ -36,9 +35,10 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_)
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
-
- release_event = Core::Timing::CreateEvent(
- name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(); });
+ release_event =
+ Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
+ ReleaseActiveBuffer(ns_late);
+ });
}
void Stream::Play() {
@@ -59,15 +59,13 @@ Stream::State Stream::GetState() const {
return state;
}
-s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
+std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const {
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
- const auto us =
- std::chrono::microseconds((static_cast<u64>(num_samples) * 1000000) / sample_rate);
- return Core::Timing::usToCycles(us);
+ return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
}
static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
- const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)};
+ const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)};
if (volume == 1.0f) {
return;
@@ -80,7 +78,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
}
}
-void Stream::PlayNextBuffer() {
+void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
if (!IsPlaying()) {
// Ensure we are in playing state before playing the next buffer
sink_stream.Flush();
@@ -105,14 +103,14 @@ void Stream::PlayNextBuffer() {
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
- core_timing.ScheduleEvent(GetBufferReleaseCycles(*active_buffer), release_event, {});
+ core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {});
}
-void Stream::ReleaseActiveBuffer() {
+void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
ASSERT(active_buffer);
released_buffers.push(std::move(active_buffer));
release_callback();
- PlayNextBuffer();
+ PlayNextBuffer(ns_late);
}
bool Stream::QueueBuffer(BufferPtr&& buffer) {
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 1708a4d98..6437b8591 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -4,6 +4,7 @@
#pragma once
+#include <chrono>
#include <functional>
#include <memory>
#include <string>
@@ -90,13 +91,13 @@ public:
private:
/// Plays the next queued buffer in the audio stream, starting playback if necessary
- void PlayNextBuffer();
+ void PlayNextBuffer(std::chrono::nanoseconds ns_late = {});
/// Releases the actively playing buffer, signalling that it has been completed
- void ReleaseActiveBuffer();
+ void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {});
/// Gets the number of core cycles when the specified buffer will be released
- s64 GetBufferReleaseCycles(const Buffer& buffer) const;
+ std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
u32 sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
new file mode 100644
index 000000000..c46ee55f1
--- /dev/null
+++ b/src/audio_core/voice_context.cpp
@@ -0,0 +1,529 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/voice_context.h"
+#include "core/memory.h"
+
+namespace AudioCore {
+
+ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
+ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
+
+bool ServerVoiceChannelResource::InUse() const {
+ return in_use;
+}
+
+float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return mix_volume.at(i);
+}
+
+float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return last_mix_volume.at(i);
+}
+
+void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
+ in_use = in_params.in_use;
+ // Update our mix volumes only if it's in use
+ if (in_params.in_use) {
+ mix_volume = in_params.mix_volume;
+ }
+}
+
+void ServerVoiceChannelResource::UpdateLastMixVolumes() {
+ last_mix_volume = mix_volume;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerVoiceChannelResource::GetCurrentMixVolume() const {
+ return mix_volume;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerVoiceChannelResource::GetLastMixVolume() const {
+ return last_mix_volume;
+}
+
+ServerVoiceInfo::ServerVoiceInfo() {
+ Initialize();
+}
+ServerVoiceInfo::~ServerVoiceInfo() = default;
+
+void ServerVoiceInfo::Initialize() {
+ in_params.in_use = false;
+ in_params.node_id = 0;
+ in_params.id = 0;
+ in_params.current_playstate = ServerPlayState::Stop;
+ in_params.priority = 255;
+ in_params.sample_rate = 0;
+ in_params.sample_format = SampleFormat::Invalid;
+ in_params.channel_count = 0;
+ in_params.pitch = 0.0f;
+ in_params.volume = 0.0f;
+ in_params.last_volume = 0.0f;
+ in_params.biquad_filter.fill({});
+ in_params.wave_buffer_count = 0;
+ in_params.wave_bufffer_head = 0;
+ in_params.mix_id = AudioCommon::NO_MIX;
+ in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
+ in_params.additional_params_address = 0;
+ in_params.additional_params_size = 0;
+ in_params.is_new = false;
+ out_params.played_sample_count = 0;
+ out_params.wave_buffer_consumed = 0;
+ in_params.voice_drop_flag = false;
+ in_params.buffer_mapped = false;
+ in_params.wave_buffer_flush_request_count = 0;
+ in_params.was_biquad_filter_enabled.fill(false);
+
+ for (auto& wave_buffer : in_params.wave_buffer) {
+ wave_buffer.start_sample_offset = 0;
+ wave_buffer.end_sample_offset = 0;
+ wave_buffer.is_looping = false;
+ wave_buffer.end_of_stream = false;
+ wave_buffer.buffer_address = 0;
+ wave_buffer.buffer_size = 0;
+ wave_buffer.context_address = 0;
+ wave_buffer.context_size = 0;
+ wave_buffer.sent_to_dsp = true;
+ }
+
+ stored_samples.clear();
+}
+
+void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
+ BehaviorInfo& behavior_info) {
+ in_params.in_use = voice_in.is_in_use;
+ in_params.id = voice_in.id;
+ in_params.node_id = voice_in.node_id;
+ in_params.last_playstate = in_params.current_playstate;
+ switch (voice_in.play_state) {
+ case PlayState::Paused:
+ in_params.current_playstate = ServerPlayState::Paused;
+ break;
+ case PlayState::Stopped:
+ if (in_params.current_playstate != ServerPlayState::Stop) {
+ in_params.current_playstate = ServerPlayState::RequestStop;
+ }
+ break;
+ case PlayState::Started:
+ in_params.current_playstate = ServerPlayState::Play;
+ break;
+ default:
+ UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
+ break;
+ }
+
+ in_params.priority = voice_in.priority;
+ in_params.sorting_order = voice_in.sorting_order;
+ in_params.sample_rate = voice_in.sample_rate;
+ in_params.sample_format = voice_in.sample_format;
+ in_params.channel_count = voice_in.channel_count;
+ in_params.pitch = voice_in.pitch;
+ in_params.volume = voice_in.volume;
+ in_params.biquad_filter = voice_in.biquad_filter;
+ in_params.wave_buffer_count = voice_in.wave_buffer_count;
+ in_params.wave_bufffer_head = voice_in.wave_buffer_head;
+ if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
+ const auto in_request_count = in_params.wave_buffer_flush_request_count;
+ const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
+ in_params.wave_buffer_flush_request_count =
+ static_cast<u8>(in_request_count + voice_request_count);
+ }
+ in_params.mix_id = voice_in.mix_id;
+ if (behavior_info.IsSplitterSupported()) {
+ in_params.splitter_info_id = voice_in.splitter_info_id;
+ } else {
+ in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
+ }
+
+ std::memcpy(in_params.voice_channel_resource_id.data(),
+ voice_in.voice_channel_resource_ids.data(),
+ sizeof(s32) * in_params.voice_channel_resource_id.size());
+
+ if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
+ in_params.behavior_flags.is_played_samples_reset_at_loop_point =
+ voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
+ } else {
+ in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
+ }
+ if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
+ in_params.behavior_flags.is_pitch_and_src_skipped =
+ voice_in.behavior_flags.is_pitch_and_src_skipped;
+ } else {
+ in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
+ }
+
+ if (voice_in.is_voice_drop_flag_clear_requested) {
+ in_params.voice_drop_flag = false;
+ }
+
+ if (in_params.additional_params_address != voice_in.additional_params_address ||
+ in_params.additional_params_size != voice_in.additional_params_size) {
+ in_params.additional_params_address = voice_in.additional_params_address;
+ in_params.additional_params_size = voice_in.additional_params_size;
+ // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
+ // our context is new
+ }
+}
+
+void ServerVoiceInfo::UpdateWaveBuffers(
+ const VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
+ BehaviorInfo& behavior_info) {
+ if (voice_in.is_new) {
+ // Initialize our wave buffers
+ for (auto& wave_buffer : in_params.wave_buffer) {
+ wave_buffer.start_sample_offset = 0;
+ wave_buffer.end_sample_offset = 0;
+ wave_buffer.is_looping = false;
+ wave_buffer.end_of_stream = false;
+ wave_buffer.buffer_address = 0;
+ wave_buffer.buffer_size = 0;
+ wave_buffer.context_address = 0;
+ wave_buffer.context_size = 0;
+ wave_buffer.sent_to_dsp = true;
+ }
+
+ // Mark all our wave buffers as invalid
+ for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
+ channel++) {
+ for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
+ is_valid = false;
+ }
+ }
+ }
+
+ // Update our wave buffers
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ // Assume that we have at least 1 channel voice state
+ const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
+
+ UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
+ have_valid_wave_buffer, behavior_info);
+ }
+}
+
+void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
+ const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
+ bool is_buffer_valid, BehaviorInfo& behavior_info) {
+ if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
+ out_wavebuffer.buffer_address = 0;
+ out_wavebuffer.buffer_size = 0;
+ }
+
+ if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
+ // Validate sample offset sizings
+ if (sample_format == SampleFormat::Pcm16) {
+ const auto buffer_size = in_wave_buffer.buffer_size;
+ if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
+ (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
+ (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
+ // TODO(ogniK): Write error info
+ return;
+ }
+ }
+ // TODO(ogniK): ADPCM Size error
+
+ out_wavebuffer.sent_to_dsp = false;
+ out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
+ out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
+ out_wavebuffer.is_looping = in_wave_buffer.is_looping;
+ out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
+
+ out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
+ out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
+ out_wavebuffer.context_address = in_wave_buffer.context_address;
+ out_wavebuffer.context_size = in_wave_buffer.context_size;
+ in_params.buffer_mapped =
+ in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
+ // TODO(ogniK): Pool mapper attachment
+ // TODO(ogniK): IsAdpcmLoopContextBugFixed
+ }
+}
+
+void ServerVoiceInfo::WriteOutStatus(
+ VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
+ if (voice_in.is_new) {
+ in_params.is_new = true;
+ voice_out.wave_buffer_consumed = 0;
+ voice_out.played_sample_count = 0;
+ voice_out.voice_dropped = false;
+ } else if (!in_params.is_new) {
+ voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
+ voice_out.played_sample_count = voice_states[0]->played_sample_count;
+ voice_out.voice_dropped = in_params.voice_drop_flag;
+ } else {
+ voice_out.wave_buffer_consumed = 0;
+ voice_out.played_sample_count = 0;
+ voice_out.voice_dropped = false;
+ }
+}
+
+const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
+ return in_params;
+}
+
+ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
+ return in_params;
+}
+
+const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
+ return out_params;
+}
+
+ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
+ return out_params;
+}
+
+bool ServerVoiceInfo::ShouldSkip() const {
+ // TODO(ogniK): Handle unmapped wave buffers or parameters
+ return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
+}
+
+bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
+ if (in_params.is_new) {
+ ResetResources(voice_context);
+ in_params.last_volume = in_params.volume;
+ in_params.is_new = false;
+ }
+
+ const s32 channel_count = in_params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ const auto channel_resource = in_params.voice_channel_resource_id[i];
+ dsp_voice_states[i] =
+ &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
+ }
+ return UpdateParametersForCommandGeneration(dsp_voice_states);
+}
+
+void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
+ const s32 channel_count = in_params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ const auto channel_resource = in_params.voice_channel_resource_id[i];
+ auto& dsp_state =
+ voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
+ dsp_state = {};
+ voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
+ .UpdateLastMixVolumes();
+ }
+}
+
+bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
+ const s32 channel_count = in_params.channel_count;
+ if (in_params.wave_buffer_flush_request_count > 0) {
+ FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
+ channel_count);
+ in_params.wave_buffer_flush_request_count = 0;
+ }
+
+ switch (in_params.current_playstate) {
+ case ServerPlayState::Play: {
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ if (!in_params.wave_buffer[i].sent_to_dsp) {
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
+ }
+ in_params.wave_buffer[i].sent_to_dsp = true;
+ }
+ }
+ in_params.should_depop = false;
+ return HasValidWaveBuffer(dsp_voice_states[0]);
+ }
+ case ServerPlayState::Paused:
+ case ServerPlayState::Stop: {
+ in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
+ return in_params.should_depop;
+ }
+ case ServerPlayState::RequestStop: {
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ in_params.wave_buffer[i].sent_to_dsp = true;
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+
+ if (dsp_state->is_wave_buffer_valid[i]) {
+ dsp_state->wave_buffer_index =
+ (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ dsp_state->wave_buffer_consumed++;
+ }
+
+ dsp_state->is_wave_buffer_valid[i] = false;
+ }
+ }
+
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+ dsp_state->offset = 0;
+ dsp_state->played_sample_count = 0;
+ dsp_state->fraction = 0;
+ dsp_state->sample_history.fill(0);
+ dsp_state->context = {};
+ }
+
+ in_params.current_playstate = ServerPlayState::Stop;
+ in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
+ return in_params.should_depop;
+ }
+ default:
+ UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
+ }
+
+ return false;
+}
+
+void ServerVoiceInfo::FlushWaveBuffers(
+ u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
+ s32 channel_count) {
+ auto wave_head = in_params.wave_bufffer_head;
+
+ for (u8 i = 0; i < flush_count; i++) {
+ in_params.wave_buffer[wave_head].sent_to_dsp = true;
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+ dsp_state->wave_buffer_consumed++;
+ dsp_state->is_wave_buffer_valid[wave_head] = false;
+ dsp_state->wave_buffer_index =
+ (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ }
+ wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ }
+}
+
+bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
+ const auto& valid_wb = state->is_wave_buffer_valid;
+ return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
+}
+
+VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
+ for (std::size_t i = 0; i < voice_count; i++) {
+ voice_channel_resources.emplace_back(static_cast<s32>(i));
+ sorted_voice_info.push_back(&voice_info.emplace_back());
+ voice_states.emplace_back();
+ dsp_voice_states.emplace_back();
+ }
+}
+
+VoiceContext::~VoiceContext() {
+ sorted_voice_info.clear();
+}
+
+std::size_t VoiceContext::GetVoiceCount() const {
+ return voice_count;
+}
+
+ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_channel_resources.at(i);
+}
+
+const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_channel_resources.at(i);
+}
+
+VoiceState& VoiceContext::GetState(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_states.at(i);
+}
+
+const VoiceState& VoiceContext::GetState(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_states.at(i);
+}
+
+VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
+ ASSERT(i < voice_count);
+ return dsp_voice_states.at(i);
+}
+
+const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return dsp_voice_states.at(i);
+}
+
+ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_info.at(i);
+}
+
+const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_info.at(i);
+}
+
+ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
+ ASSERT(i < voice_count);
+ return *sorted_voice_info.at(i);
+}
+
+const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return *sorted_voice_info.at(i);
+}
+
+s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
+ s32 channel_count, s32 buffer_offset, s32 sample_count,
+ Core::Memory::Memory& memory) {
+ if (wave_buffer->buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer->buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
+ return 0;
+ }
+
+ const auto samples_remaining =
+ (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
+ const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
+ const auto buffer_pos = wave_buffer->buffer_address + start_offset;
+
+ s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
+
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+
+ // Fast path
+ if (channel_count == 1) {
+ for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
+ output_buffer[i] = buffer_data[i];
+ }
+ } else {
+ for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
+ output_buffer[i] = buffer_data[i * channel_count + channel];
+ }
+ }
+
+ return samples_processed;
+}
+
+void VoiceContext::SortInfo() {
+ for (std::size_t i = 0; i < voice_count; i++) {
+ sorted_voice_info[i] = &voice_info[i];
+ }
+
+ std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
+ [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
+ const auto& lhs_in = lhs->GetInParams();
+ const auto& rhs_in = rhs->GetInParams();
+ // Sort by priority
+ if (lhs_in.priority != rhs_in.priority) {
+ return lhs_in.priority > rhs_in.priority;
+ } else {
+ // If the priorities match, sort by sorting order
+ return lhs_in.sorting_order > rhs_in.sorting_order;
+ }
+ });
+}
+
+void VoiceContext::UpdateStateByDspShared() {
+ voice_states = dsp_voice_states;
+}
+
+} // 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 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "audio_core/algorithm/interpolate.h"
+#include "audio_core/codec.h"
+#include "audio_core/common.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore {
+
+class BehaviorInfo;
+class VoiceContext;
+
+enum class SampleFormat : u8 {
+ Invalid = 0,
+ Pcm8 = 1,
+ Pcm16 = 2,
+ Pcm24 = 3,
+ Pcm32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6,
+};
+
+enum class PlayState : u8 {
+ Started = 0,
+ Stopped = 1,
+ Paused = 2,
+};
+
+enum class ServerPlayState {
+ Play = 0,
+ Stop = 1,
+ RequestStop = 2,
+ Paused = 3,
+};
+
+struct BiquadFilterParameter {
+ bool enabled{};
+ INSERT_PADDING_BYTES(1);
+ std::array<s16, 3> numerator{};
+ std::array<s16, 2> denominator{};
+};
+static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
+
+struct WaveBuffer {
+ u64_le buffer_address{};
+ u64_le buffer_size{};
+ s32_le start_sample_offset{};
+ s32_le end_sample_offset{};
+ u8 is_looping{};
+ u8 end_of_stream{};
+ u8 sent_to_server{};
+ INSERT_PADDING_BYTES(5);
+ u64 context_address{};
+ u64 context_size{};
+ INSERT_PADDING_BYTES(8);
+};
+static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
+
+struct ServerWaveBuffer {
+ VAddr buffer_address{};
+ std::size_t buffer_size{};
+ s32 start_sample_offset{};
+ s32 end_sample_offset{};
+ bool is_looping{};
+ bool end_of_stream{};
+ VAddr context_address{};
+ std::size_t context_size{};
+ bool sent_to_dsp{true};
+};
+
+struct BehaviorFlags {
+ BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
+ BitField<1, 1, u16> is_pitch_and_src_skipped;
+};
+static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
+
+struct ADPCMContext {
+ u16 header{};
+ s16 yn1{};
+ s16 yn2{};
+};
+static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
+
+struct VoiceState {
+ s64 played_sample_count{};
+ s32 offset{};
+ s32 wave_buffer_index{};
+ std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
+ s32 wave_buffer_consumed{};
+ std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
+ s32 fraction{};
+ VAddr context_address{};
+ Codec::ADPCM_Coeff coeff{};
+ ADPCMContext context{};
+ std::array<s64, 2> biquad_filter_state{};
+ std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
+ u32 external_context_size{};
+ bool is_external_context_used{};
+ bool voice_dropped{};
+};
+
+class VoiceChannelResource {
+public:
+ struct InParams {
+ s32_le id{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(11);
+ };
+ static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
+};
+
+class ServerVoiceChannelResource {
+public:
+ explicit ServerVoiceChannelResource(s32 id);
+ ~ServerVoiceChannelResource();
+
+ bool InUse() const;
+ float GetCurrentMixVolumeAt(std::size_t i) const;
+ float GetLastMixVolumeAt(std::size_t i) const;
+ void Update(VoiceChannelResource::InParams& in_params);
+ void UpdateLastMixVolumes();
+
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
+
+private:
+ s32 id{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
+ bool in_use{};
+};
+
+class VoiceInfo {
+public:
+ struct InParams {
+ s32_le id{};
+ u32_le node_id{};
+ u8 is_new{};
+ u8 is_in_use{};
+ PlayState play_state{};
+ SampleFormat sample_format{};
+ s32_le sample_rate{};
+ s32_le priority{};
+ s32_le sorting_order{};
+ s32_le channel_count{};
+ float_le pitch{};
+ float_le volume{};
+ std::array<BiquadFilterParameter, 2> biquad_filter{};
+ s32_le wave_buffer_count{};
+ s16_le wave_buffer_head{};
+ INSERT_PADDING_BYTES(6);
+ u64_le additional_params_address{};
+ u64_le additional_params_size{};
+ s32_le mix_id{};
+ s32_le splitter_info_id{};
+ std::array<WaveBuffer, 4> wave_buffer{};
+ std::array<u32_le, 6> voice_channel_resource_ids{};
+ // TODO(ogniK): Remaining flags
+ u8 is_voice_drop_flag_clear_requested{};
+ u8 wave_buffer_flush_request_count{};
+ INSERT_PADDING_BYTES(2);
+ BehaviorFlags behavior_flags{};
+ INSERT_PADDING_BYTES(16);
+ };
+ static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
+
+ struct OutParams {
+ u64_le played_sample_count{};
+ u32_le wave_buffer_consumed{};
+ u8 voice_dropped{};
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
+};
+
+class ServerVoiceInfo {
+public:
+ struct InParams {
+ bool in_use{};
+ bool is_new{};
+ bool should_depop{};
+ SampleFormat sample_format{};
+ s32 sample_rate{};
+ s32 channel_count{};
+ s32 id{};
+ s32 node_id{};
+ s32 mix_id{};
+ ServerPlayState current_playstate{};
+ ServerPlayState last_playstate{};
+ s32 priority{};
+ s32 sorting_order{};
+ float pitch{};
+ float volume{};
+ float last_volume{};
+ std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
+ s32 wave_buffer_count{};
+ s16 wave_bufffer_head{};
+ INSERT_PADDING_BYTES(2);
+ BehaviorFlags behavior_flags{};
+ VAddr additional_params_address{};
+ std::size_t additional_params_size{};
+ std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
+ std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
+ s32 splitter_info_id{};
+ u8 wave_buffer_flush_request_count{};
+ bool voice_drop_flag{};
+ bool buffer_mapped{};
+ std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
+ };
+
+ struct OutParams {
+ s64 played_sample_count{};
+ s32 wave_buffer_consumed{};
+ };
+
+ ServerVoiceInfo();
+ ~ServerVoiceInfo();
+ void Initialize();
+ void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
+ void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
+ BehaviorInfo& behavior_info);
+ void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
+ SampleFormat sample_format, bool is_buffer_valid,
+ BehaviorInfo& behavior_info);
+ void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
+
+ const InParams& GetInParams() const;
+ InParams& GetInParams();
+
+ const OutParams& GetOutParams() const;
+ OutParams& GetOutParams();
+
+ bool ShouldSkip() const;
+ bool UpdateForCommandGeneration(VoiceContext& voice_context);
+ void ResetResources(VoiceContext& voice_context);
+ bool UpdateParametersForCommandGeneration(
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
+ void FlushWaveBuffers(u8 flush_count,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
+ s32 channel_count);
+
+private:
+ std::vector<s16> stored_samples;
+ InParams in_params{};
+ OutParams out_params{};
+
+ bool HasValidWaveBuffer(const VoiceState* state) const;
+};
+
+class VoiceContext {
+public:
+ VoiceContext(std::size_t voice_count);
+ ~VoiceContext();
+
+ std::size_t GetVoiceCount() const;
+ ServerVoiceChannelResource& GetChannelResource(std::size_t i);
+ const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
+ VoiceState& GetState(std::size_t i);
+ const VoiceState& GetState(std::size_t i) const;
+ VoiceState& GetDspSharedState(std::size_t i);
+ const VoiceState& GetDspSharedState(std::size_t i) const;
+ ServerVoiceInfo& GetInfo(std::size_t i);
+ const ServerVoiceInfo& GetInfo(std::size_t i) const;
+ ServerVoiceInfo& GetSortedInfo(std::size_t i);
+ const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
+
+ s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
+ s32 channel_count, s32 buffer_offset, s32 sample_count,
+ Core::Memory::Memory& memory);
+ void SortInfo();
+ void UpdateStateByDspShared();
+
+private:
+ std::size_t voice_count{};
+ std::vector<ServerVoiceChannelResource> voice_channel_resources{};
+ std::vector<VoiceState> voice_states{};
+ std::vector<VoiceState> dsp_voice_states{};
+ std::vector<ServerVoiceInfo> voice_info{};
+ std::vector<ServerVoiceInfo*> sorted_voice_info{};
+};
+
+} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index eeceaa655..d20e6c3b5 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -32,6 +32,8 @@ add_custom_command(OUTPUT scm_rev.cpp
DEPENDS
# WARNING! It was too much work to try and make a common location for this list,
# so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well
+ "${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.cpp"
+ "${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.h"
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
@@ -96,8 +98,11 @@ add_library(common STATIC
algorithm.h
alignment.h
assert.h
+ atomic_ops.cpp
+ atomic_ops.h
detached_tasks.cpp
detached_tasks.h
+ bit_cast.h
bit_field.h
bit_util.h
cityhash.cpp
@@ -106,8 +111,11 @@ add_library(common STATIC
common_funcs.h
common_paths.h
common_types.h
+ concepts.h
dynamic_library.cpp
dynamic_library.h
+ fiber.cpp
+ fiber.h
file_util.cpp
file_util.h
hash.h
@@ -123,6 +131,8 @@ add_library(common STATIC
lz4_compression.cpp
lz4_compression.h
math_util.h
+ memory_detect.cpp
+ memory_detect.h
memory_hook.cpp
memory_hook.h
microprofile.cpp
@@ -139,6 +149,10 @@ add_library(common STATIC
scm_rev.cpp
scm_rev.h
scope_exit.h
+ spin_lock.cpp
+ spin_lock.h
+ stream.cpp
+ stream.h
string_util.cpp
string_util.h
swap.h
@@ -148,6 +162,8 @@ add_library(common STATIC
thread.h
thread_queue_list.h
threadsafe_queue.h
+ time_zone.cpp
+ time_zone.h
timer.cpp
timer.h
uint128.cpp
@@ -155,7 +171,10 @@ add_library(common STATIC
uuid.cpp
uuid.h
vector_math.h
- web_result.h
+ virtual_buffer.cpp
+ virtual_buffer.h
+ wall_clock.cpp
+ wall_clock.h
zstd_compression.cpp
zstd_compression.h
)
@@ -165,10 +184,36 @@ if(ARCHITECTURE_x86_64)
PRIVATE
x64/cpu_detect.cpp
x64/cpu_detect.h
+ x64/native_clock.cpp
+ x64/native_clock.h
+ x64/xbyak_abi.h
+ x64/xbyak_util.h
)
endif()
+if (MSVC)
+ target_compile_definitions(common PRIVATE
+ # The standard library doesn't provide any replacement for codecvt yet
+ # so we can disable this deprecation warning for the time being.
+ _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
+ )
+ target_compile_options(common PRIVATE
+ /W4
+ /WX
+ )
+else()
+ target_compile_options(common PRIVATE
+ -Werror
+ )
+endif()
+
create_target_directory_groups(common)
+find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
-target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
-target_link_libraries(common PRIVATE lz4_static libzstd_static)
+target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
+target_link_libraries(common PRIVATE lz4::lz4 xbyak)
+if (MSVC)
+ target_link_libraries(common PRIVATE zstd::zstd)
+else()
+ target_link_libraries(common PRIVATE zstd)
+endif()
diff --git a/src/common/algorithm.h b/src/common/algorithm.h
index e21b1373c..4804a3421 100644
--- a/src/common/algorithm.h
+++ b/src/common/algorithm.h
@@ -15,7 +15,8 @@
namespace Common {
template <class ForwardIt, class T, class Compare = std::less<>>
-ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, Compare comp = {}) {
+[[nodiscard]] ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value,
+ Compare comp = {}) {
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
diff --git a/src/common/alignment.h b/src/common/alignment.h
index cdd4833f8..5040043de 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -3,41 +3,50 @@
#pragma once
#include <cstddef>
-#include <memory>
+#include <new>
#include <type_traits>
namespace Common {
template <typename T>
-constexpr T AlignUp(T value, std::size_t size) {
+[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
- return static_cast<T>(value + (size - value % size) % size);
+ auto mod{static_cast<T>(value % size)};
+ value -= mod;
+ return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
-constexpr T AlignDown(T value, std::size_t size) {
+[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
template <typename T>
-constexpr T AlignBits(T value, std::size_t align) {
+[[nodiscard]] constexpr T AlignBits(T value, std::size_t align) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>((value + ((1ULL << align) - 1)) >> align << align);
}
template <typename T>
-constexpr bool Is4KBAligned(T value) {
+[[nodiscard]] constexpr bool Is4KBAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0xFFF) == 0;
}
template <typename T>
-constexpr bool IsWordAligned(T value) {
+[[nodiscard]] constexpr bool IsWordAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0b11) == 0;
}
+template <typename T>
+[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) {
+ using U = typename std::make_unsigned<T>::type;
+ const U mask = static_cast<U>(alignment - 1);
+ return (value & mask) == 0;
+}
+
template <typename T, std::size_t Align = 16>
class AlignmentAllocator {
public:
@@ -45,66 +54,28 @@ public:
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
- using pointer = T*;
- using const_pointer = const T*;
-
- using reference = T&;
- using const_reference = const T&;
-
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
using is_always_equal = std::true_type;
-public:
constexpr AlignmentAllocator() noexcept = default;
template <typename T2>
constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {}
- pointer address(reference r) noexcept {
- return std::addressof(r);
- }
-
- const_pointer address(const_reference r) const noexcept {
- return std::addressof(r);
- }
-
- pointer allocate(size_type n) {
- return static_cast<pointer>(::operator new (n, std::align_val_t{Align}));
+ [[nodiscard]] T* allocate(size_type n) {
+ return static_cast<T*>(::operator new (n * sizeof(T), std::align_val_t{Align}));
}
- void deallocate(pointer p, size_type) {
- ::operator delete (p, std::align_val_t{Align});
- }
-
- void construct(pointer p, const value_type& wert) {
- new (p) value_type(wert);
- }
-
- void destroy(pointer p) {
- p->~value_type();
- }
-
- size_type max_size() const noexcept {
- return size_type(-1) / sizeof(value_type);
+ void deallocate(T* p, size_type n) {
+ ::operator delete (p, n * sizeof(T), std::align_val_t{Align});
}
template <typename T2>
struct rebind {
using other = AlignmentAllocator<T2, Align>;
};
-
- bool operator!=(const AlignmentAllocator<T, Align>& other) const noexcept {
- return !(*this == other);
- }
-
- // Returns true if and only if storage allocated from *this
- // can be deallocated from other, and vice versa.
- // Always returns true for stateless allocators.
- bool operator==(const AlignmentAllocator<T, Align>& other) const noexcept {
- return true;
- }
};
} // namespace Common
diff --git a/src/common/assert.h b/src/common/assert.h
index 5b67c5c52..06d7b5612 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -17,11 +17,12 @@
// enough for our purposes.
template <typename Fn>
#if defined(_MSC_VER)
-__declspec(noinline, noreturn)
+[[msvc::noinline, noreturn]]
#elif defined(__GNUC__)
- __attribute__((noinline, noreturn, cold))
+[[gnu::cold, gnu::noinline, noreturn]]
#endif
- static void assert_noinline_call(const Fn& fn) {
+static void
+assert_noinline_call(const Fn& fn) {
fn();
Crash();
exit(1); // Keeps GCC's mouth shut about this actually returning
diff --git a/src/common/atomic_ops.cpp b/src/common/atomic_ops.cpp
new file mode 100644
index 000000000..1612d0e67
--- /dev/null
+++ b/src/common/atomic_ops.cpp
@@ -0,0 +1,75 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+
+#include "common/atomic_ops.h"
+
+#if _MSC_VER
+#include <intrin.h>
+#endif
+
+namespace Common {
+
+#if _MSC_VER
+
+bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
+ const u8 result =
+ _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
+ return result == expected;
+}
+
+bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
+ const u16 result =
+ _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
+ return result == expected;
+}
+
+bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
+ const u32 result =
+ _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
+ return result == expected;
+}
+
+bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
+ const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer),
+ value, expected);
+ return result == expected;
+}
+
+bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
+ return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
+ value[0],
+ reinterpret_cast<__int64*>(expected.data())) != 0;
+}
+
+#else
+
+bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
+ unsigned __int128 value_a;
+ unsigned __int128 expected_a;
+ std::memcpy(&value_a, value.data(), sizeof(u128));
+ std::memcpy(&expected_a, expected.data(), sizeof(u128));
+ return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
+}
+
+#endif
+
+} // namespace Common
diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h
new file mode 100644
index 000000000..b46888589
--- /dev/null
+++ b/src/common/atomic_ops.h
@@ -0,0 +1,17 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Common {
+
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected);
+
+} // namespace Common
diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h
new file mode 100644
index 000000000..a32a063d1
--- /dev/null
+++ b/src/common/bit_cast.h
@@ -0,0 +1,22 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstring>
+#include <type_traits>
+
+namespace Common {
+
+template <typename To, typename From>
+[[nodiscard]] std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> &&
+ std::is_trivially_copyable_v<To>,
+ To>
+BitCast(const From& src) noexcept {
+ To dst;
+ std::memcpy(&dst, &src, sizeof(To));
+ return dst;
+}
+
+} // namespace Common
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index fd2bbbd99..0f0661172 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -36,13 +36,6 @@
#include "common/common_funcs.h"
#include "common/swap.h"
-// Inlining
-#ifdef _WIN32
-#define FORCE_INLINE __forceinline
-#else
-#define FORCE_INLINE inline __attribute__((always_inline))
-#endif
-
/*
* Abstract bitfield class
*
@@ -142,8 +135,8 @@ public:
* containing several bitfields can be assembled by formatting each of their values and ORing
* the results together.
*/
- static constexpr FORCE_INLINE StorageType FormatValue(const T& value) {
- return ((StorageType)value << position) & mask;
+ [[nodiscard]] static constexpr StorageType FormatValue(const T& value) {
+ return (static_cast<StorageType>(value) << position) & mask;
}
/**
@@ -151,7 +144,7 @@ public:
* (such as Value() or operator T), but this can be used to extract a value from a bitfield
* union in a constexpr context.
*/
- static constexpr FORCE_INLINE T ExtractValue(const StorageType& storage) {
+ [[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) {
if constexpr (std::numeric_limits<UnderlyingType>::is_signed) {
std::size_t shift = 8 * sizeof(T) - bits;
return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >>
@@ -175,19 +168,19 @@ public:
constexpr BitField(BitField&&) noexcept = default;
constexpr BitField& operator=(BitField&&) noexcept = default;
- constexpr operator T() const {
+ [[nodiscard]] constexpr operator T() const {
return Value();
}
constexpr void Assign(const T& value) {
- storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
+ storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value));
}
- constexpr T Value() const {
+ [[nodiscard]] constexpr T Value() const {
return ExtractValue(storage);
}
- constexpr explicit operator bool() const {
+ [[nodiscard]] constexpr explicit operator bool() const {
return Value() != 0;
}
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index 6f7d5a947..29f59a9a3 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.h
@@ -17,12 +17,12 @@ namespace Common {
/// Gets the size of a specified type T in bits.
template <typename T>
-constexpr std::size_t BitSize() {
+[[nodiscard]] constexpr std::size_t BitSize() {
return sizeof(T) * CHAR_BIT;
}
#ifdef _MSC_VER
-inline u32 CountLeadingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse(&leading_zero, value) != 0) {
@@ -32,7 +32,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
-inline u32 CountLeadingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse64(&leading_zero, value) != 0) {
@@ -42,7 +42,7 @@ inline u32 CountLeadingZeroes64(u64 value) {
return 64;
}
#else
-inline u32 CountLeadingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
@@ -50,7 +50,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return static_cast<u32>(__builtin_clz(value));
}
-inline u32 CountLeadingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
@@ -60,7 +60,7 @@ inline u32 CountLeadingZeroes64(u64 value) {
#endif
#ifdef _MSC_VER
-inline u32 CountTrailingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward(&trailing_zero, value) != 0) {
@@ -70,7 +70,7 @@ inline u32 CountTrailingZeroes32(u32 value) {
return 32;
}
-inline u32 CountTrailingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward64(&trailing_zero, value) != 0) {
@@ -80,7 +80,7 @@ inline u32 CountTrailingZeroes64(u64 value) {
return 64;
}
#else
-inline u32 CountTrailingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
@@ -88,7 +88,7 @@ inline u32 CountTrailingZeroes32(u32 value) {
return static_cast<u32>(__builtin_ctz(value));
}
-inline u32 CountTrailingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
@@ -99,13 +99,13 @@ inline u32 CountTrailingZeroes64(u64 value) {
#ifdef _MSC_VER
-inline u32 MostSignificantBit32(const u32 value) {
+[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
unsigned long result;
_BitScanReverse(&result, value);
return static_cast<u32>(result);
}
-inline u32 MostSignificantBit64(const u64 value) {
+[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
unsigned long result;
_BitScanReverse64(&result, value);
return static_cast<u32>(result);
@@ -113,30 +113,30 @@ inline u32 MostSignificantBit64(const u64 value) {
#else
-inline u32 MostSignificantBit32(const u32 value) {
+[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
return 31U - static_cast<u32>(__builtin_clz(value));
}
-inline u32 MostSignificantBit64(const u64 value) {
+[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
return 63U - static_cast<u32>(__builtin_clzll(value));
}
#endif
-inline u32 Log2Floor32(const u32 value) {
+[[nodiscard]] inline u32 Log2Floor32(const u32 value) {
return MostSignificantBit32(value);
}
-inline u32 Log2Ceil32(const u32 value) {
+[[nodiscard]] inline u32 Log2Ceil32(const u32 value) {
const u32 log2_f = Log2Floor32(value);
return log2_f + ((value ^ (1U << log2_f)) != 0U);
}
-inline u32 Log2Floor64(const u64 value) {
+[[nodiscard]] inline u32 Log2Floor64(const u64 value) {
return MostSignificantBit64(value);
}
-inline u32 Log2Ceil64(const u64 value) {
+[[nodiscard]] inline u32 Log2Ceil64(const u64 value) {
const u64 log2_f = static_cast<u64>(Log2Floor64(value));
return static_cast<u32>(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL));
}
diff --git a/src/common/cityhash.h b/src/common/cityhash.h
index 4b94f8e18..a00804e01 100644
--- a/src/common/cityhash.h
+++ b/src/common/cityhash.h
@@ -61,42 +61,43 @@
#pragma once
+#include <cstddef>
+#include <cstdint>
#include <utility>
-#include <stdint.h>
-#include <stdlib.h> // for std::size_t.
namespace Common {
-typedef std::pair<uint64_t, uint64_t> uint128;
+using uint128 = std::pair<uint64_t, uint64_t>;
-inline uint64_t Uint128Low64(const uint128& x) {
+[[nodiscard]] inline uint64_t Uint128Low64(const uint128& x) {
return x.first;
}
-inline uint64_t Uint128High64(const uint128& x) {
+[[nodiscard]] inline uint64_t Uint128High64(const uint128& x) {
return x.second;
}
// Hash function for a byte array.
-uint64_t CityHash64(const char* buf, std::size_t len);
+[[nodiscard]] uint64_t CityHash64(const char* buf, std::size_t len);
// Hash function for a byte array. For convenience, a 64-bit seed is also
// hashed into the result.
-uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed);
+[[nodiscard]] uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed);
// Hash function for a byte array. For convenience, two seeds are also
// hashed into the result.
-uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, uint64_t seed1);
+[[nodiscard]] uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0,
+ uint64_t seed1);
// Hash function for a byte array.
-uint128 CityHash128(const char* s, std::size_t len);
+[[nodiscard]] uint128 CityHash128(const char* s, std::size_t len);
// Hash function for a byte array. For convenience, a 128-bit seed is also
// hashed into the result.
-uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed);
+[[nodiscard]] uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed);
// Hash 128 input bits down to 64 bits of output.
// This is intended to be a reasonably good hash function.
-inline uint64_t Hash128to64(const uint128& x) {
+[[nodiscard]] inline uint64_t Hash128to64(const uint128& x) {
// Murmur-inspired hashing.
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
diff --git a/src/common/color.h b/src/common/color.h
index 3a2222077..bbcac858e 100644
--- a/src/common/color.h
+++ b/src/common/color.h
@@ -10,45 +10,45 @@
#include "common/swap.h"
#include "common/vector_math.h"
-namespace Color {
+namespace Common::Color {
/// Convert a 1-bit color component to 8 bit
-constexpr u8 Convert1To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert1To8(u8 value) {
return value * 255;
}
/// Convert a 4-bit color component to 8 bit
-constexpr u8 Convert4To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert4To8(u8 value) {
return (value << 4) | value;
}
/// Convert a 5-bit color component to 8 bit
-constexpr u8 Convert5To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert5To8(u8 value) {
return (value << 3) | (value >> 2);
}
/// Convert a 6-bit color component to 8 bit
-constexpr u8 Convert6To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert6To8(u8 value) {
return (value << 2) | (value >> 4);
}
/// Convert a 8-bit color component to 1 bit
-constexpr u8 Convert8To1(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To1(u8 value) {
return value >> 7;
}
/// Convert a 8-bit color component to 4 bit
-constexpr u8 Convert8To4(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To4(u8 value) {
return value >> 4;
}
/// Convert a 8-bit color component to 5 bit
-constexpr u8 Convert8To5(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To5(u8 value) {
return value >> 3;
}
/// Convert a 8-bit color component to 6 bit
-constexpr u8 Convert8To6(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To6(u8 value) {
return value >> 2;
}
@@ -57,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
return {bytes[3], bytes[2], bytes[1], bytes[0]};
}
@@ -66,7 +66,7 @@ inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
return {bytes[2], bytes[1], bytes[0], 255};
}
@@ -75,7 +75,7 @@ inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
return {bytes[1], bytes[0], 0, 255};
}
@@ -84,7 +84,7 @@ inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
@@ -96,7 +96,7 @@ inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
@@ -108,7 +108,7 @@ inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
@@ -120,7 +120,7 @@ inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
* @param bytes Pointer to encoded source value
* @return Depth value as an u32
*/
-inline u32 DecodeD16(const u8* bytes) {
+[[nodiscard]] inline u32 DecodeD16(const u8* bytes) {
u16_le data;
std::memcpy(&data, bytes, sizeof(data));
return data;
@@ -131,7 +131,7 @@ inline u32 DecodeD16(const u8* bytes) {
* @param bytes Pointer to encoded source value
* @return Depth value as an u32
*/
-inline u32 DecodeD24(const u8* bytes) {
+[[nodiscard]] inline u32 DecodeD24(const u8* bytes) {
return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
}
@@ -140,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) {
* @param bytes Pointer to encoded source values
* @return Resulting values stored as a Common::Vec2
*/
-inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
}
@@ -268,4 +268,4 @@ inline void EncodeX24S8(u8 stencil, u8* bytes) {
bytes[3] = stencil;
}
-} // namespace Color
+} // namespace Common::Color
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 052254678..367b6bf6e 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -53,11 +53,49 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in Misc.cpp.
-std::string GetLastErrorMsg();
+[[nodiscard]] std::string GetLastErrorMsg();
+
+#define DECLARE_ENUM_FLAG_OPERATORS(type) \
+ [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
+ } \
+ [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
+ } \
+ [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
+ } \
+ constexpr type& operator|=(type& a, type b) noexcept { \
+ a = a | b; \
+ return a; \
+ } \
+ constexpr type& operator&=(type& a, type b) noexcept { \
+ a = a & b; \
+ return a; \
+ } \
+ constexpr type& operator^=(type& a, type b) noexcept { \
+ a = a ^ b; \
+ return a; \
+ } \
+ [[nodiscard]] constexpr type operator~(type key) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<type>(~static_cast<T>(key)); \
+ } \
+ [[nodiscard]] constexpr bool True(type key) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<T>(key) != 0; \
+ } \
+ [[nodiscard]] constexpr bool False(type key) noexcept { \
+ using T = std::underlying_type_t<type>; \
+ return static_cast<T>(key) == 0; \
+ }
namespace Common {
-constexpr u32 MakeMagic(char a, char b, char c, char d) {
+[[nodiscard]] constexpr u32 MakeMagic(char a, char b, char c, char d) {
return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24;
}
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 076752d3b..3c593d5f6 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -35,6 +35,7 @@
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define DUMP_DIR "dump"
+#define SCREENSHOTS_DIR "screenshots"
#define SHADER_DIR "shader"
#define LOG_DIR "log"
diff --git a/src/common/concepts.h b/src/common/concepts.h
new file mode 100644
index 000000000..5bef3ad67
--- /dev/null
+++ b/src/common/concepts.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+
+namespace Common {
+
+// Check if type is like an STL container
+template <typename T>
+concept IsSTLContainer = requires(T t) {
+ typename T::value_type;
+ typename T::iterator;
+ typename T::const_iterator;
+ // TODO(ogniK): Replace below is std::same_as<void> when MSVC supports it.
+ t.begin();
+ t.end();
+ t.cbegin();
+ t.cend();
+ t.data();
+ t.size();
+};
+
+// TODO: Replace with std::derived_from when the <concepts> header
+// is available on all supported platforms.
+template <typename Derived, typename Base>
+concept DerivedFrom = requires {
+ std::is_base_of_v<Base, Derived>;
+ std::is_convertible_v<const volatile Derived*, const volatile Base*>;
+};
+
+} // namespace Common
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index f268d6021..f2b4939df 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -34,8 +34,7 @@ void DetachedTasks::AddTask(std::function<void()> task) {
std::unique_lock lock{instance->mutex};
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
- })
- .detach();
+ }).detach();
}
} // namespace Common
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 7ab54e9e4..7f0a10521 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -21,7 +21,7 @@ namespace Common {
DynamicLibrary::DynamicLibrary() = default;
DynamicLibrary::DynamicLibrary(const char* filename) {
- Open(filename);
+ void(Open(filename));
}
DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept
diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h
index 2a06372fd..3512da940 100644
--- a/src/common/dynamic_library.h
+++ b/src/common/dynamic_library.h
@@ -33,7 +33,7 @@ public:
~DynamicLibrary();
/// Returns the specified library name with the platform-specific suffix added.
- static std::string GetUnprefixedFilename(const char* filename);
+ [[nodiscard]] static std::string GetUnprefixedFilename(const char* filename);
/// Returns the specified library name in platform-specific format.
/// Major/minor versions will not be included if set to -1.
@@ -41,28 +41,29 @@ public:
/// Windows: LIBNAME-MAJOR-MINOR.dll
/// Linux: libLIBNAME.so.MAJOR.MINOR
/// Mac: libLIBNAME.MAJOR.MINOR.dylib
- static std::string GetVersionedFilename(const char* libname, int major = -1, int minor = -1);
+ [[nodiscard]] static std::string GetVersionedFilename(const char* libname, int major = -1,
+ int minor = -1);
/// Returns true if a module is loaded, otherwise false.
- bool IsOpen() const {
+ [[nodiscard]] bool IsOpen() const {
return handle != nullptr;
}
/// Loads (or replaces) the handle with the specified library file name.
/// Returns true if the library was loaded and can be used.
- bool Open(const char* filename);
+ [[nodiscard]] bool Open(const char* filename);
/// Unloads the library, any function pointers from this library are no longer valid.
void Close();
/// Returns the address of the specified symbol (function or variable) as an untyped pointer.
/// If the specified symbol does not exist in this library, nullptr is returned.
- void* GetSymbolAddress(const char* name) const;
+ [[nodiscard]] void* GetSymbolAddress(const char* name) const;
/// Obtains the address of the specified symbol, automatically casting to the correct type.
/// Returns true if the symbol was found and assigned, otherwise false.
template <typename T>
- bool GetSymbol(const char* name, T* ptr) const {
+ [[nodiscard]] bool GetSymbol(const char* name, T* ptr) const {
*ptr = reinterpret_cast<T>(GetSymbolAddress(name));
return *ptr != nullptr;
}
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp
new file mode 100644
index 000000000..3e3029cd1
--- /dev/null
+++ b/src/common/fiber.cpp
@@ -0,0 +1,233 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/fiber.h"
+#include "common/spin_lock.h"
+
+#if defined(_WIN32) || defined(WIN32)
+#include <windows.h>
+#else
+#include <boost/context/detail/fcontext.hpp>
+#endif
+
+namespace Common {
+
+constexpr std::size_t default_stack_size = 256 * 1024; // 256kb
+
+struct Fiber::FiberImpl {
+ SpinLock guard{};
+ std::function<void(void*)> entry_point;
+ std::function<void(void*)> rewind_point;
+ void* rewind_parameter{};
+ void* start_parameter{};
+ std::shared_ptr<Fiber> previous_fiber;
+ bool is_thread_fiber{};
+ bool released{};
+
+#if defined(_WIN32) || defined(WIN32)
+ LPVOID handle = nullptr;
+ LPVOID rewind_handle = nullptr;
+#else
+ alignas(64) std::array<u8, default_stack_size> stack;
+ alignas(64) std::array<u8, default_stack_size> rewind_stack;
+ u8* stack_limit;
+ u8* rewind_stack_limit;
+ boost::context::detail::fcontext_t context;
+ boost::context::detail::fcontext_t rewind_context;
+#endif
+};
+
+void Fiber::SetStartParameter(void* new_parameter) {
+ impl->start_parameter = new_parameter;
+}
+
+void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param) {
+ impl->rewind_point = std::move(rewind_func);
+ impl->rewind_parameter = rewind_param;
+}
+
+#if defined(_WIN32) || defined(WIN32)
+
+void Fiber::Start() {
+ ASSERT(impl->previous_fiber != nullptr);
+ impl->previous_fiber->impl->guard.unlock();
+ impl->previous_fiber.reset();
+ impl->entry_point(impl->start_parameter);
+ UNREACHABLE();
+}
+
+void Fiber::OnRewind() {
+ ASSERT(impl->handle != nullptr);
+ DeleteFiber(impl->handle);
+ impl->handle = impl->rewind_handle;
+ impl->rewind_handle = nullptr;
+ impl->rewind_point(impl->rewind_parameter);
+ UNREACHABLE();
+}
+
+void Fiber::FiberStartFunc(void* fiber_parameter) {
+ auto* fiber = static_cast<Fiber*>(fiber_parameter);
+ fiber->Start();
+}
+
+void Fiber::RewindStartFunc(void* fiber_parameter) {
+ auto* fiber = static_cast<Fiber*>(fiber_parameter);
+ fiber->OnRewind();
+}
+
+Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
+ : impl{std::make_unique<FiberImpl>()} {
+ impl->entry_point = std::move(entry_point_func);
+ impl->start_parameter = start_parameter;
+ impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this);
+}
+
+Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
+
+Fiber::~Fiber() {
+ if (impl->released) {
+ return;
+ }
+ // Make sure the Fiber is not being used
+ const bool locked = impl->guard.try_lock();
+ ASSERT_MSG(locked, "Destroying a fiber that's still running");
+ if (locked) {
+ impl->guard.unlock();
+ }
+ DeleteFiber(impl->handle);
+}
+
+void Fiber::Exit() {
+ ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber");
+ if (!impl->is_thread_fiber) {
+ return;
+ }
+ ConvertFiberToThread();
+ impl->guard.unlock();
+ impl->released = true;
+}
+
+void Fiber::Rewind() {
+ ASSERT(impl->rewind_point);
+ ASSERT(impl->rewind_handle == nullptr);
+ impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
+ SwitchToFiber(impl->rewind_handle);
+}
+
+void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) {
+ ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
+ ASSERT_MSG(to != nullptr, "Next fiber is null!");
+ to->impl->guard.lock();
+ to->impl->previous_fiber = from;
+ SwitchToFiber(to->impl->handle);
+ ASSERT(from->impl->previous_fiber != nullptr);
+ from->impl->previous_fiber->impl->guard.unlock();
+ from->impl->previous_fiber.reset();
+}
+
+std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
+ std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
+ fiber->impl->guard.lock();
+ fiber->impl->handle = ConvertThreadToFiber(nullptr);
+ fiber->impl->is_thread_fiber = true;
+ return fiber;
+}
+
+#else
+
+void Fiber::Start(boost::context::detail::transfer_t& transfer) {
+ ASSERT(impl->previous_fiber != nullptr);
+ impl->previous_fiber->impl->context = transfer.fctx;
+ impl->previous_fiber->impl->guard.unlock();
+ impl->previous_fiber.reset();
+ impl->entry_point(impl->start_parameter);
+ UNREACHABLE();
+}
+
+void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) {
+ ASSERT(impl->context != nullptr);
+ impl->context = impl->rewind_context;
+ impl->rewind_context = nullptr;
+ u8* tmp = impl->stack_limit;
+ impl->stack_limit = impl->rewind_stack_limit;
+ impl->rewind_stack_limit = tmp;
+ impl->rewind_point(impl->rewind_parameter);
+ UNREACHABLE();
+}
+
+void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
+ auto* fiber = static_cast<Fiber*>(transfer.data);
+ fiber->Start(transfer);
+}
+
+void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
+ auto* fiber = static_cast<Fiber*>(transfer.data);
+ fiber->OnRewind(transfer);
+}
+
+Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
+ : impl{std::make_unique<FiberImpl>()} {
+ impl->entry_point = std::move(entry_point_func);
+ impl->start_parameter = start_parameter;
+ impl->stack_limit = impl->stack.data();
+ impl->rewind_stack_limit = impl->rewind_stack.data();
+ u8* stack_base = impl->stack_limit + default_stack_size;
+ impl->context =
+ boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
+}
+
+Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
+
+Fiber::~Fiber() {
+ if (impl->released) {
+ return;
+ }
+ // Make sure the Fiber is not being used
+ const bool locked = impl->guard.try_lock();
+ ASSERT_MSG(locked, "Destroying a fiber that's still running");
+ if (locked) {
+ impl->guard.unlock();
+ }
+}
+
+void Fiber::Exit() {
+ ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber");
+ if (!impl->is_thread_fiber) {
+ return;
+ }
+ impl->guard.unlock();
+ impl->released = true;
+}
+
+void Fiber::Rewind() {
+ ASSERT(impl->rewind_point);
+ ASSERT(impl->rewind_context == nullptr);
+ u8* stack_base = impl->rewind_stack_limit + default_stack_size;
+ impl->rewind_context =
+ boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc);
+ boost::context::detail::jump_fcontext(impl->rewind_context, this);
+}
+
+void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) {
+ ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
+ ASSERT_MSG(to != nullptr, "Next fiber is null!");
+ to->impl->guard.lock();
+ to->impl->previous_fiber = from;
+ auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
+ ASSERT(from->impl->previous_fiber != nullptr);
+ from->impl->previous_fiber->impl->context = transfer.fctx;
+ from->impl->previous_fiber->impl->guard.unlock();
+ from->impl->previous_fiber.reset();
+}
+
+std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
+ std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
+ fiber->impl->guard.lock();
+ fiber->impl->is_thread_fiber = true;
+ return fiber;
+}
+
+#endif
+} // namespace Common
diff --git a/src/common/fiber.h b/src/common/fiber.h
new file mode 100644
index 000000000..5323e8579
--- /dev/null
+++ b/src/common/fiber.h
@@ -0,0 +1,78 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+
+#if !defined(_WIN32) && !defined(WIN32)
+namespace boost::context::detail {
+struct transfer_t;
+}
+#endif
+
+namespace Common {
+
+/**
+ * Fiber class
+ * a fiber is a userspace thread with it's own context. They can be used to
+ * implement coroutines, emulated threading systems and certain asynchronous
+ * patterns.
+ *
+ * This class implements fibers at a low level, thus allowing greater freedom
+ * to implement such patterns. This fiber class is 'threadsafe' only one fiber
+ * can be running at a time and threads will be locked while trying to yield to
+ * a running fiber until it yields. WARNING exchanging two running fibers between
+ * threads will cause a deadlock. In order to prevent a deadlock, each thread should
+ * have an intermediary fiber, you switch to the intermediary fiber of the current
+ * thread and then from it switch to the expected fiber. This way you can exchange
+ * 2 fibers within 2 different threads.
+ */
+class Fiber {
+public:
+ Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter);
+ ~Fiber();
+
+ Fiber(const Fiber&) = delete;
+ Fiber& operator=(const Fiber&) = delete;
+
+ Fiber(Fiber&&) = default;
+ Fiber& operator=(Fiber&&) = default;
+
+ /// Yields control from Fiber 'from' to Fiber 'to'
+ /// Fiber 'from' must be the currently running fiber.
+ static void YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to);
+ [[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
+
+ void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param);
+
+ void Rewind();
+
+ /// Only call from main thread's fiber
+ void Exit();
+
+ /// Changes the start parameter of the fiber. Has no effect if the fiber already started
+ void SetStartParameter(void* new_parameter);
+
+private:
+ Fiber();
+
+#if defined(_WIN32) || defined(WIN32)
+ void OnRewind();
+ void Start();
+ static void FiberStartFunc(void* fiber_parameter);
+ static void RewindStartFunc(void* fiber_parameter);
+#else
+ void OnRewind(boost::context::detail::transfer_t& transfer);
+ void Start(boost::context::detail::transfer_t& transfer);
+ static void FiberStartFunc(boost::context::detail::transfer_t transfer);
+ static void RewindStartFunc(boost::context::detail::transfer_t transfer);
+#endif
+
+ struct FiberImpl;
+ std::unique_ptr<FiberImpl> impl;
+};
+
+} // namespace Common
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 35eee0096..18fbfa25b 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -74,7 +74,7 @@
// This namespace has various generic functions related to files and paths.
// The code still needs a ton of cleanup.
// REMEMBER: strdup considered harmful!
-namespace FileUtil {
+namespace Common::FS {
// Remove any ending forward slashes from directory paths
// Modifies argument.
@@ -196,7 +196,7 @@ bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
- if (FileUtil::Exists(fullPath)) {
+ if (Exists(fullPath)) {
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
return true;
}
@@ -212,7 +212,7 @@ bool CreateFullPath(const std::string& fullPath) {
// Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
std::string const subPath(fullPath.substr(0, position + 1));
- if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) {
+ if (!IsDirectory(subPath) && !CreateDir(subPath)) {
LOG_ERROR(Common, "CreateFullPath: directory creation failed");
return false;
}
@@ -231,7 +231,7 @@ bool DeleteDir(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "directory {}", filename);
// check if a directory
- if (!FileUtil::IsDirectory(filename)) {
+ if (!IsDirectory(filename)) {
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
return false;
}
@@ -371,7 +371,7 @@ u64 GetSize(FILE* f) {
bool CreateEmptyFile(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "{}", filename);
- if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
+ if (!IOFile(filename, "wb").IsOpen()) {
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
return false;
}
@@ -472,13 +472,14 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
}
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
- const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- std::string new_path = directory + DIR_SEP_CHR + virtual_name;
+ const auto callback = [recursion](u64*, const std::string& directory,
+ const std::string& virtual_name) {
+ const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path)) {
- if (recursion == 0)
+ if (recursion == 0) {
return false;
+ }
return DeleteDirRecursively(new_path, recursion - 1);
}
return Delete(new_path);
@@ -488,29 +489,35 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
return false;
// Delete the outermost directory
- FileUtil::DeleteDir(directory);
+ DeleteDir(directory);
return true;
}
-void CopyDir(const std::string& source_path, const std::string& dest_path) {
+void CopyDir([[maybe_unused]] const std::string& source_path,
+ [[maybe_unused]] const std::string& dest_path) {
#ifndef _WIN32
- if (source_path == dest_path)
+ if (source_path == dest_path) {
return;
- if (!FileUtil::Exists(source_path))
+ }
+ if (!Exists(source_path)) {
return;
- if (!FileUtil::Exists(dest_path))
- FileUtil::CreateFullPath(dest_path);
+ }
+ if (!Exists(dest_path)) {
+ CreateFullPath(dest_path);
+ }
DIR* dirp = opendir(source_path.c_str());
- if (!dirp)
+ if (!dirp) {
return;
+ }
while (struct dirent* result = readdir(dirp)) {
const std::string virtualName(result->d_name);
// check for "." and ".."
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
- ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
+ ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
continue;
+ }
std::string source, dest;
source = source_path + virtualName;
@@ -518,11 +525,13 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
if (IsDirectory(source)) {
source += '/';
dest += '/';
- if (!FileUtil::Exists(dest))
- FileUtil::CreateFullPath(dest);
+ if (!Exists(dest)) {
+ CreateFullPath(dest);
+ }
CopyDir(source, dest);
- } else if (!FileUtil::Exists(dest))
- FileUtil::Copy(source, dest);
+ } else if (!Exists(dest)) {
+ Copy(source, dest);
+ }
}
closedir(dirp);
#endif
@@ -538,7 +547,7 @@ std::optional<std::string> GetCurrentDir() {
if (!dir) {
#endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
- return {};
+ return std::nullopt;
}
#ifdef _WIN32
std::string strDir = Common::UTF16ToUTF8(dir);
@@ -668,7 +677,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
if (user_path.empty()) {
#ifdef _WIN32
user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
- if (!FileUtil::IsDirectory(user_path)) {
+ if (!IsDirectory(user_path)) {
user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
} else {
LOG_INFO(Common_Filesystem, "Using the local user directory");
@@ -677,7 +686,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
#else
- if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
+ if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
@@ -695,6 +704,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
+ paths.emplace(UserPath::ScreenshotsDir, user_path + SCREENSHOTS_DIR DIR_SEP);
paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
@@ -703,7 +713,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
}
if (!new_path.empty()) {
- if (!FileUtil::IsDirectory(new_path)) {
+ if (!IsDirectory(new_path)) {
LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path);
return paths[path];
} else {
@@ -764,21 +774,23 @@ std::size_t ReadFileToString(bool text_file, const std::string& filename, std::s
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension) {
- const std::string forbidden_characters = ".\"/\\[]:;=, ";
+ static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, ";
// On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces.
short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}};
extension = {{' ', ' ', ' ', '\0'}};
- std::string::size_type point = filename.rfind('.');
- if (point == filename.size() - 1)
+ auto point = filename.rfind('.');
+ if (point == filename.size() - 1) {
point = filename.rfind('.', point);
+ }
// Get short name.
int j = 0;
for (char letter : filename.substr(0, point)) {
- if (forbidden_characters.find(letter, 0) != std::string::npos)
+ if (forbidden_characters.find(letter, 0) != std::string::npos) {
continue;
+ }
if (j == 8) {
// TODO(Link Mauve): also do that for filenames containing a space.
// TODO(Link Mauve): handle multiple files having the same short name.
@@ -786,14 +798,15 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
short_name[7] = '1';
break;
}
- short_name[j++] = toupper(letter);
+ short_name[j++] = static_cast<char>(std::toupper(letter));
}
// Get extension.
if (point != std::string::npos) {
j = 0;
- for (char letter : filename.substr(point + 1, 3))
- extension[j++] = toupper(letter);
+ for (char letter : filename.substr(point + 1, 3)) {
+ extension[j++] = static_cast<char>(std::toupper(letter));
+ }
}
}
@@ -888,16 +901,23 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
}
std::replace(path.begin(), path.end(), type1, type2);
- path.erase(std::unique(path.begin(), path.end(),
+
+ auto start = path.begin();
+#ifdef _WIN32
+ // allow network paths which start with a double backslash (e.g. \\server\share)
+ if (start != path.end())
+ ++start;
+#endif
+ path.erase(std::unique(start, path.end(),
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
path.end());
return std::string(RemoveTrailingSlash(path));
}
-IOFile::IOFile() {}
+IOFile::IOFile() = default;
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
- Open(filename, openmode, flags);
+ void(Open(filename, openmode, flags));
}
IOFile::~IOFile() {
@@ -938,17 +958,18 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
}
bool IOFile::Close() {
- if (!IsOpen() || 0 != std::fclose(m_file))
+ if (!IsOpen() || 0 != std::fclose(m_file)) {
return false;
+ }
m_file = nullptr;
return true;
}
u64 IOFile::GetSize() const {
- if (IsOpen())
- return FileUtil::GetSize(m_file);
-
+ if (IsOpen()) {
+ return FS::GetSize(m_file);
+ }
return 0;
}
@@ -957,9 +978,9 @@ bool IOFile::Seek(s64 off, int origin) const {
}
u64 IOFile::Tell() const {
- if (IsOpen())
+ if (IsOpen()) {
return ftello(m_file);
-
+ }
return std::numeric_limits<u64>::max();
}
@@ -967,6 +988,34 @@ bool IOFile::Flush() {
return IsOpen() && 0 == std::fflush(m_file);
}
+std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) const {
+ if (!IsOpen()) {
+ return std::numeric_limits<std::size_t>::max();
+ }
+
+ if (length == 0) {
+ return 0;
+ }
+
+ DEBUG_ASSERT(data != nullptr);
+
+ return std::fread(data, data_size, length, m_file);
+}
+
+std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
+ if (!IsOpen()) {
+ return std::numeric_limits<std::size_t>::max();
+ }
+
+ if (length == 0) {
+ return 0;
+ }
+
+ DEBUG_ASSERT(data != nullptr);
+
+ return std::fwrite(data, data_size, length, m_file);
+}
+
bool IOFile::Resize(u64 size) {
return IsOpen() && 0 ==
#ifdef _WIN32
@@ -980,4 +1029,4 @@ bool IOFile::Resize(u64 size) {
;
}
-} // namespace FileUtil
+} // namespace Common::FS
diff --git a/src/common/file_util.h b/src/common/file_util.h
index cde7ddf2d..840cde2a6 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -19,7 +19,7 @@
#include "common/string_util.h"
#endif
-namespace FileUtil {
+namespace Common::FS {
// User paths for GetUserPath
enum class UserPath {
@@ -32,6 +32,7 @@ enum class UserPath {
SDMCDir,
LoadDir,
DumpDir,
+ ScreenshotsDir,
ShaderDir,
SysDataDir,
UserDir,
@@ -47,19 +48,19 @@ struct FSTEntry {
};
// Returns true if file filename exists
-bool Exists(const std::string& filename);
+[[nodiscard]] bool Exists(const std::string& filename);
// Returns true if filename is a directory
-bool IsDirectory(const std::string& filename);
+[[nodiscard]] bool IsDirectory(const std::string& filename);
// Returns the size of filename (64bit)
-u64 GetSize(const std::string& filename);
+[[nodiscard]] u64 GetSize(const std::string& filename);
// Overloaded GetSize, accepts file descriptor
-u64 GetSize(const int fd);
+[[nodiscard]] u64 GetSize(int fd);
// Overloaded GetSize, accepts FILE*
-u64 GetSize(FILE* f);
+[[nodiscard]] u64 GetSize(FILE* f);
// Returns true if successful, or path already exists.
bool CreateDir(const std::string& filename);
@@ -119,7 +120,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
// Returns the current directory
-std::optional<std::string> GetCurrentDir();
+[[nodiscard]] std::optional<std::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path);
@@ -131,20 +132,20 @@ bool SetCurrentDir(const std::string& directory);
// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
-std::string GetHactoolConfigurationPath();
+[[nodiscard]] std::string GetHactoolConfigurationPath();
-std::string GetNANDRegistrationDir(bool system = false);
+[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false);
// Returns the path to where the sys file are
-std::string GetSysDirectory();
+[[nodiscard]] std::string GetSysDirectory();
#ifdef __APPLE__
-std::string GetBundleDirectory();
+[[nodiscard]] std::string GetBundleDirectory();
#endif
#ifdef _WIN32
-const std::string& GetExeDirectory();
-std::string AppDataRoamingDirectory();
+[[nodiscard]] const std::string& GetExeDirectory();
+[[nodiscard]] std::string AppDataRoamingDirectory();
#endif
std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
@@ -163,38 +164,55 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
-std::vector<std::string> SplitPathComponents(std::string_view filename);
+[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
// Gets all of the text up to the last '/' or '\' in the path.
-std::string_view GetParentPath(std::string_view path);
+[[nodiscard]] std::string_view GetParentPath(std::string_view path);
// Gets all of the text after the first '/' or '\' in the path.
-std::string_view GetPathWithoutTop(std::string_view path);
+[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
// Gets the filename of the path
-std::string_view GetFilename(std::string_view path);
+[[nodiscard]] std::string_view GetFilename(std::string_view path);
// Gets the extension of the filename
-std::string_view GetExtensionFromFilename(std::string_view name);
+[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
// Removes the final '/' or '\' if one exists
-std::string_view RemoveTrailingSlash(std::string_view path);
+[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
// Creates a new vector containing indices [first, last) from the original.
template <typename T>
-std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, std::size_t last) {
- if (first >= last)
+[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first,
+ std::size_t last) {
+ if (first >= last) {
return {};
+ }
last = std::min<std::size_t>(last, vector.size());
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
}
-enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault };
+enum class DirectorySeparator {
+ ForwardSlash,
+ BackwardSlash,
+ PlatformDefault,
+};
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
-std::string SanitizePath(std::string_view path,
- DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
+[[nodiscard]] std::string SanitizePath(
+ std::string_view path,
+ DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
+
+// To deal with Windows being dumb at Unicode
+template <typename T>
+void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
+#ifdef _MSC_VER
+ fstream.open(Common::UTF8ToUTF16W(filename), openmode);
+#else
+ fstream.open(filename, openmode);
+#endif
+}
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
@@ -222,22 +240,15 @@ public:
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
- if (!IsOpen()) {
- return std::numeric_limits<std::size_t>::max();
- }
-
- return std::fread(data, sizeof(T), length, m_file);
+ return ReadImpl(data, length, sizeof(T));
}
template <typename T>
std::size_t WriteArray(const T* data, std::size_t length) {
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
- if (!IsOpen()) {
- return std::numeric_limits<std::size_t>::max();
- }
- return std::fwrite(data, sizeof(T), length, m_file);
+ return WriteImpl(data, length, sizeof(T));
}
template <typename T>
@@ -262,13 +273,13 @@ public:
return WriteArray(str.data(), str.length());
}
- bool IsOpen() const {
+ [[nodiscard]] bool IsOpen() const {
return nullptr != m_file;
}
bool Seek(s64 off, int origin) const;
- u64 Tell() const;
- u64 GetSize() const;
+ [[nodiscard]] u64 Tell() const;
+ [[nodiscard]] u64 GetSize() const;
bool Resize(u64 size);
bool Flush();
@@ -278,17 +289,10 @@ public:
}
private:
+ std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const;
+ std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
+
std::FILE* m_file = nullptr;
};
-} // namespace FileUtil
-
-// To deal with Windows being dumb at unicode:
-template <typename T>
-void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
-#ifdef _MSC_VER
- fstream.open(Common::UTF8ToUTF16W(filename), openmode);
-#else
- fstream.open(filename, openmode);
-#endif
-}
+} // namespace Common::FS
diff --git a/src/common/hash.h b/src/common/hash.h
index b2538f3ea..298930702 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -5,36 +5,11 @@
#pragma once
#include <cstddef>
-#include <cstring>
#include <utility>
#include <boost/functional/hash.hpp>
-#include "common/cityhash.h"
-#include "common/common_types.h"
namespace Common {
-/**
- * Computes a 64-bit hash over the specified block of data
- * @param data Block of data to compute hash over
- * @param len Length of data (in bytes) to compute hash over
- * @returns 64-bit hash value that was computed over the data block
- */
-static inline u64 ComputeHash64(const void* data, std::size_t len) {
- return CityHash64(static_cast<const char*>(data), len);
-}
-
-/**
- * Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical
- * that either the struct includes no padding, or that any padding is initialized to a known value
- * by memsetting the struct to 0 before filling it in.
- */
-template <typename T>
-static inline u64 ComputeStructHash64(const T& data) {
- static_assert(std::is_trivially_copyable_v<T>,
- "Type passed to ComputeStructHash64 must be trivially copyable");
- return ComputeHash64(&data, sizeof(data));
-}
-
struct PairHash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2>& pair) const noexcept {
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index c2f6cf0f6..74f52dd11 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -3,21 +3,9 @@
// Refer to the license.txt file included.
#include "common/hex_util.h"
-#include "common/logging/log.h"
namespace Common {
-u8 ToHexNibble(char c1) {
- if (c1 >= 65 && c1 <= 70)
- return c1 - 55;
- if (c1 >= 97 && c1 <= 102)
- return c1 - 87;
- if (c1 >= 48 && c1 <= 57)
- return c1 - 48;
- LOG_ERROR(Common, "Invalid hex digit: 0x{:02X}", c1);
- return 0;
-}
-
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
std::vector<u8> out(str.size() / 2);
if (little_endian) {
@@ -30,26 +18,4 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
return out;
}
-std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
- if (len != 32) {
- LOG_ERROR(Common,
- "Attempting to parse string to array that is not of correct size (expected=32, "
- "actual={}).",
- len);
- return {};
- }
- return HexStringToArray<16>(str);
-}
-
-std::array<u8, 32> operator""_array32(const char* str, std::size_t len) {
- if (len != 64) {
- LOG_ERROR(Common,
- "Attempting to parse string to array that is not of correct size (expected=64, "
- "actual={}).",
- len);
- return {};
- }
- return HexStringToArray<32>(str);
-}
-
} // namespace Common
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index bb4736f96..a8d414fb8 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -14,25 +14,37 @@
namespace Common {
-u8 ToHexNibble(char c1);
+[[nodiscard]] constexpr u8 ToHexNibble(char c) {
+ if (c >= 65 && c <= 70) {
+ return static_cast<u8>(c - 55);
+ }
+
+ if (c >= 97 && c <= 102) {
+ return static_cast<u8>(c - 87);
+ }
+
+ return static_cast<u8>(c - 48);
+}
-std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
+[[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
template <std::size_t Size, bool le = false>
-std::array<u8, Size> HexStringToArray(std::string_view str) {
+[[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
if constexpr (le) {
- for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
- out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) {
+ out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
+ }
} else {
- for (std::size_t i = 0; i < 2 * Size; i += 2)
- out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ for (std::size_t i = 0; i < 2 * Size; i += 2) {
+ out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
+ }
}
return out;
}
template <typename ContiguousContainer>
-std::string HexToString(const ContiguousContainer& data, bool upper = true) {
+[[nodiscard]] std::string HexToString(const ContiguousContainer& data, bool upper = true) {
static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>,
"Underlying type within the contiguous container must be u8.");
@@ -48,7 +60,12 @@ std::string HexToString(const ContiguousContainer& data, bool upper = true) {
return out;
}
-std::array<u8, 0x10> operator"" _array16(const char* str, std::size_t len);
-std::array<u8, 0x20> operator"" _array32(const char* str, std::size_t len);
+[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) {
+ return HexStringToArray<16>(data);
+}
+
+[[nodiscard]] constexpr std::array<u8, 32> AsArray(const char (&data)[65]) {
+ return HexStringToArray<32>(data);
+}
} // namespace Common
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 04bc3128f..631f64d05 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -23,6 +23,7 @@
#include "common/logging/text_formatter.h"
#include "common/string_util.h"
#include "common/threadsafe_queue.h"
+#include "core/settings.h"
namespace Log {
@@ -113,19 +114,19 @@ private:
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, std::string message) const {
using std::chrono::duration_cast;
+ using std::chrono::microseconds;
using std::chrono::steady_clock;
- Entry entry;
- entry.timestamp =
- duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
- entry.log_class = log_class;
- entry.log_level = log_level;
- entry.filename = filename;
- entry.line_num = line_nr;
- entry.function = function;
- entry.message = std::move(message);
-
- return entry;
+ return {
+ .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
+ .log_class = log_class,
+ .log_level = log_level,
+ .filename = filename,
+ .line_num = line_nr,
+ .function = function,
+ .message = std::move(message),
+ .final_entry = false,
+ };
}
std::mutex writing_mutex;
@@ -152,10 +153,19 @@ FileBackend::FileBackend(const std::string& filename)
void FileBackend::Write(const Entry& entry) {
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
// know)
- constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
- if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
+ constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024;
+ constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024;
+
+ if (!file.IsOpen()) {
return;
}
+
+ if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) {
+ return;
+ } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) {
+ return;
+ }
+
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
if (entry.log_level >= Level::Error) {
file.Flush();
@@ -222,6 +232,7 @@ void DebuggerBackend::Write(const Entry& entry) {
SUB(Service, NPNS) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
+ SUB(Service, OLSC) \
SUB(Service, PCIE) \
SUB(Service, PCTL) \
SUB(Service, PCV) \
@@ -274,7 +285,6 @@ const char* GetLogClassName(Class log_class) {
case Class::Count:
break;
}
- UNREACHABLE();
return "Invalid";
}
@@ -293,7 +303,6 @@ const char* GetLevelName(Level log_level) {
break;
}
#undef LVL
- UNREACHABLE();
return "Invalid";
}
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index fc338c70d..da1c2f185 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -21,19 +21,13 @@ class Filter;
*/
struct Entry {
std::chrono::microseconds timestamp;
- Class log_class;
- Level log_level;
- const char* filename;
- unsigned int line_num;
+ Class log_class{};
+ Level log_level{};
+ const char* filename = nullptr;
+ unsigned int line_num = 0;
std::string function;
std::string message;
bool final_entry = false;
-
- Entry() = default;
- Entry(Entry&& o) = default;
-
- Entry& operator=(Entry&& o) = default;
- Entry& operator=(const Entry& o) = default;
};
/**
@@ -100,7 +94,7 @@ public:
void Write(const Entry& entry) override;
private:
- FileUtil::IOFile file;
+ Common::FS::IOFile file;
std::size_t bytes_written;
};
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 13a4f1e30..835894918 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -95,6 +95,7 @@ enum class Class : ClassType {
Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
+ Service_OLSC, ///< The OLSC service
Service_PCIE, ///< The PCIe service
Service_PCTL, ///< The PCTL (Parental control) service
Service_PCV, ///< The PCV service
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
index ade6759bb..25700015a 100644
--- a/src/common/lz4_compression.cpp
+++ b/src/common/lz4_compression.cpp
@@ -14,19 +14,19 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) {
ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
const auto source_size_int = static_cast<int>(source_size);
- const int max_compressed_size = LZ4_compressBound(source_size_int);
+ const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int));
std::vector<u8> compressed(max_compressed_size);
- const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
- reinterpret_cast<char*>(compressed.data()),
- source_size_int, max_compressed_size);
+ const int compressed_size = LZ4_compress_default(
+ reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
+ source_size_int, static_cast<int>(max_compressed_size));
if (compressed_size <= 0) {
// Compression failed
return {};
}
- compressed.resize(compressed_size);
+ compressed.resize(static_cast<std::size_t>(compressed_size));
return compressed;
}
@@ -38,19 +38,19 @@ std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
const auto source_size_int = static_cast<int>(source_size);
- const int max_compressed_size = LZ4_compressBound(source_size_int);
+ const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int));
std::vector<u8> compressed(max_compressed_size);
const int compressed_size = LZ4_compress_HC(
reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
- source_size_int, max_compressed_size, compression_level);
+ source_size_int, static_cast<int>(max_compressed_size), compression_level);
if (compressed_size <= 0) {
// Compression failed
return {};
}
- compressed.resize(compressed_size);
+ compressed.resize(static_cast<std::size_t>(compressed_size));
return compressed;
}
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
index 4c16f6e03..87a4be1b0 100644
--- a/src/common/lz4_compression.h
+++ b/src/common/lz4_compression.h
@@ -13,12 +13,12 @@ namespace Common::Compression {
/**
* Compresses a source memory region with LZ4 and returns the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
/**
* Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression
@@ -26,23 +26,24 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
* compression level has almost no impact on decompression speed. Data compressed with LZ4HC can
* also be decompressed with the default LZ4 decompression.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
- * @param compression_level the used compression level. Should be between 3 and 12.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
+ * @param compression_level The used compression level. Should be between 3 and 12.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level);
+[[nodiscard]] std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
+ s32 compression_level);
/**
* Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
/**
* Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector.
@@ -52,6 +53,7 @@ std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
*
* @return the decompressed data.
*/
-std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size);
+[[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
+ std::size_t uncompressed_size);
} // namespace Common::Compression \ No newline at end of file
diff --git a/src/common/math_util.h b/src/common/math_util.h
index 83ef0201f..4c38d8040 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -9,7 +9,7 @@
namespace Common {
-constexpr float PI = 3.14159265f;
+constexpr float PI = 3.1415926535f;
template <class T>
struct Rectangle {
@@ -20,40 +20,40 @@ struct Rectangle {
constexpr Rectangle() = default;
- constexpr Rectangle(T left, T top, T right, T bottom)
- : left(left), top(top), right(right), bottom(bottom) {}
+ constexpr Rectangle(T left_, T top_, T right_, T bottom_)
+ : left(left_), top(top_), right(right_), bottom(bottom_) {}
- T GetWidth() const {
+ [[nodiscard]] T GetWidth() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(right - left);
} else {
- return std::abs(static_cast<std::make_signed_t<T>>(right - left));
+ return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(right - left)));
}
}
- T GetHeight() const {
+ [[nodiscard]] T GetHeight() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(bottom - top);
} else {
- return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
+ return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(bottom - top)));
}
}
- Rectangle<T> TranslateX(const T x) const {
+ [[nodiscard]] Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};
}
- Rectangle<T> TranslateY(const T y) const {
+ [[nodiscard]] Rectangle<T> TranslateY(const T y) const {
return Rectangle{left, top + y, right, bottom + y};
}
- Rectangle<T> Scale(const float s) const {
+ [[nodiscard]] Rectangle<T> Scale(const float s) const {
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
static_cast<T>(top + GetHeight() * s)};
}
};
template <typename T>
-Rectangle(T, T, T, T)->Rectangle<T>;
+Rectangle(T, T, T, T) -> Rectangle<T>;
} // namespace Common
diff --git a/src/common/memory_detect.cpp b/src/common/memory_detect.cpp
new file mode 100644
index 000000000..8cff6ec37
--- /dev/null
+++ b/src/common/memory_detect.cpp
@@ -0,0 +1,73 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _WIN32
+// clang-format off
+#include <windows.h>
+#include <sysinfoapi.h>
+// clang-format on
+#else
+#include <sys/types.h>
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#elif defined(__linux__)
+#include <sys/sysinfo.h>
+#else
+#include <unistd.h>
+#endif
+#endif
+
+#include "common/memory_detect.h"
+
+namespace Common {
+
+// Detects the RAM and Swapfile sizes
+static MemoryInfo Detect() {
+ MemoryInfo mem_info{};
+
+#ifdef _WIN32
+ MEMORYSTATUSEX memorystatus;
+ memorystatus.dwLength = sizeof(memorystatus);
+ GlobalMemoryStatusEx(&memorystatus);
+ mem_info.TotalPhysicalMemory = memorystatus.ullTotalPhys;
+ mem_info.TotalSwapMemory = memorystatus.ullTotalPageFile - mem_info.TotalPhysicalMemory;
+#elif defined(__APPLE__)
+ u64 ramsize;
+ struct xsw_usage vmusage;
+ std::size_t sizeof_ramsize = sizeof(ramsize);
+ std::size_t sizeof_vmusage = sizeof(vmusage);
+ // hw and vm are defined in sysctl.h
+ // https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471
+ // sysctlbyname(const char *, void *, size_t *, void *, size_t);
+ sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, nullptr, 0);
+ sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0);
+ mem_info.TotalPhysicalMemory = ramsize;
+ mem_info.TotalSwapMemory = vmusage.xsu_total;
+#elif defined(__FreeBSD__)
+ u_long physmem, swap_total;
+ std::size_t sizeof_u_long = sizeof(u_long);
+ // sysctlbyname(const char *, void *, size_t *, const void *, size_t);
+ sysctlbyname("hw.physmem", &physmem, &sizeof_u_long, nullptr, 0);
+ sysctlbyname("vm.swap_total", &swap_total, &sizeof_u_long, nullptr, 0);
+ mem_info.TotalPhysicalMemory = physmem;
+ mem_info.TotalSwapMemory = swap_total;
+#elif defined(__linux__)
+ struct sysinfo meminfo;
+ sysinfo(&meminfo);
+ mem_info.TotalPhysicalMemory = meminfo.totalram;
+ mem_info.TotalSwapMemory = meminfo.totalswap;
+#else
+ mem_info.TotalPhysicalMemory = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
+ mem_info.TotalSwapMemory = 0;
+#endif
+
+ return mem_info;
+}
+
+const MemoryInfo& GetMemInfo() {
+ static MemoryInfo mem_info = Detect();
+ return mem_info;
+}
+
+} // namespace Common \ No newline at end of file
diff --git a/src/common/memory_detect.h b/src/common/memory_detect.h
new file mode 100644
index 000000000..0f73751c8
--- /dev/null
+++ b/src/common/memory_detect.h
@@ -0,0 +1,22 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Common {
+
+struct MemoryInfo {
+ u64 TotalPhysicalMemory{};
+ u64 TotalSwapMemory{};
+};
+
+/**
+ * Gets the memory info of the host system
+ * @return Reference to a MemoryInfo struct with the physical and swap memory sizes in bytes
+ */
+[[nodiscard]] const MemoryInfo& GetMemInfo();
+
+} // namespace Common \ No newline at end of file
diff --git a/src/common/misc.cpp b/src/common/misc.cpp
index 68cb86cd1..1d5393597 100644
--- a/src/common/misc.cpp
+++ b/src/common/misc.cpp
@@ -16,16 +16,23 @@
// Call directly after the command or use the error num.
// This function might change the error code.
std::string GetLastErrorMsg() {
- static const std::size_t buff_size = 255;
+ static constexpr std::size_t buff_size = 255;
char err_str[buff_size];
#ifdef _WIN32
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr);
+ return std::string(err_str, buff_size);
+#elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))
+ // Thread safe (GNU-specific)
+ const char* str = strerror_r(errno, err_str, buff_size);
+ return std::string(str);
#else
// Thread safe (XSI-compliant)
- strerror_r(errno, err_str, buff_size);
+ const int success = strerror_r(errno, err_str, buff_size);
+ if (success != 0) {
+ return {};
+ }
+ return std::string(err_str);
#endif
-
- return std::string(err_str, buff_size);
}
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
index 50acfdbf2..4b305bf40 100644
--- a/src/common/multi_level_queue.h
+++ b/src/common/multi_level_queue.h
@@ -223,15 +223,15 @@ public:
ListShiftForward(levels[priority], n);
}
- std::size_t depth() const {
+ [[nodiscard]] std::size_t depth() const {
return Depth;
}
- std::size_t size(u32 priority) const {
+ [[nodiscard]] std::size_t size(u32 priority) const {
return levels[priority].size();
}
- std::size_t size() const {
+ [[nodiscard]] std::size_t size() const {
u64 priorities = used_priorities;
std::size_t size = 0;
while (priorities != 0) {
@@ -242,64 +242,64 @@ public:
return size;
}
- bool empty() const {
+ [[nodiscard]] bool empty() const {
return used_priorities == 0;
}
- bool empty(u32 priority) const {
+ [[nodiscard]] bool empty(u32 priority) const {
return (used_priorities & (1ULL << priority)) == 0;
}
- u32 highest_priority_set(u32 max_priority = 0) const {
+ [[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const {
const u64 priorities =
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
}
- u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
+ [[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
const u64 priorities = min_priority >= Depth - 1
? used_priorities
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
}
- const_iterator cbegin(u32 max_prio = 0) const {
+ [[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? cend()
: const_iterator{*this, levels[priority].cbegin(), priority};
}
- const_iterator begin(u32 max_prio = 0) const {
+ [[nodiscard]] const_iterator begin(u32 max_prio = 0) const {
return cbegin(max_prio);
}
- iterator begin(u32 max_prio = 0) {
+ [[nodiscard]] iterator begin(u32 max_prio = 0) {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
}
- const_iterator cend(u32 min_prio = Depth - 1) const {
+ [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const {
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
}
- const_iterator end(u32 min_prio = Depth - 1) const {
+ [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const {
return cend(min_prio);
}
- iterator end(u32 min_prio = Depth - 1) {
+ [[nodiscard]] iterator end(u32 min_prio = Depth - 1) {
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
}
- T& front(u32 max_priority = 0) {
+ [[nodiscard]] T& front(u32 max_priority = 0) {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
- const T& front(u32 max_priority = 0) const {
+ [[nodiscard]] const T& front(u32 max_priority = 0) const {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
- T back(u32 min_priority = Depth - 1) {
+ [[nodiscard]] T& back(u32 min_priority = Depth - 1) {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
- const T& back(u32 min_priority = Depth - 1) const {
+ [[nodiscard]] const T& back(u32 min_priority = Depth - 1) const {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
@@ -329,7 +329,8 @@ private:
in_list.splice(position, out_list, element);
}
- static const_list_iterator ListIterateTo(const std::list<T>& list, const T& element) {
+ [[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list,
+ const T& element) {
auto it = list.cbegin();
while (it != list.cend() && *it != element) {
++it;
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 566b57b62..bccea0894 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -6,36 +6,20 @@
namespace Common {
-PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
+PageTable::PageTable() = default;
-PageTable::~PageTable() = default;
-
-void PageTable::Resize(std::size_t address_space_width_in_bits) {
- const std::size_t num_page_table_entries = 1ULL
- << (address_space_width_in_bits - page_size_in_bits);
+PageTable::~PageTable() noexcept = default;
+void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits,
+ bool has_attribute) {
+ const std::size_t num_page_table_entries{1ULL
+ << (address_space_width_in_bits - page_size_in_bits)};
pointers.resize(num_page_table_entries);
- attributes.resize(num_page_table_entries);
-
- // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
- // vector size is subsequently decreased (via resize), the vector might not automatically
- // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
- // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
-
- pointers.shrink_to_fit();
- attributes.shrink_to_fit();
-}
-
-BackingPageTable::BackingPageTable(std::size_t page_size_in_bits) : PageTable{page_size_in_bits} {}
-
-BackingPageTable::~BackingPageTable() = default;
-
-void BackingPageTable::Resize(std::size_t address_space_width_in_bits) {
- PageTable::Resize(address_space_width_in_bits);
- const std::size_t num_page_table_entries = 1ULL
- << (address_space_width_in_bits - page_size_in_bits);
backing_addr.resize(num_page_table_entries);
- backing_addr.shrink_to_fit();
+
+ if (has_attribute) {
+ attributes.resize(num_page_table_entries);
+ }
}
} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
index dbc272ab7..9754fabf9 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -4,10 +4,11 @@
#pragma once
-#include <vector>
-#include <boost/icl/interval_map.hpp>
+#include <tuple>
+
#include "common/common_types.h"
#include "common/memory_hook.h"
+#include "common/virtual_buffer.h"
namespace Common {
@@ -33,11 +34,11 @@ struct SpecialRegion {
MemoryHookPointer handler;
- bool operator<(const SpecialRegion& other) const {
+ [[nodiscard]] bool operator<(const SpecialRegion& other) const {
return std::tie(type, handler) < std::tie(other.type, other.handler);
}
- bool operator==(const SpecialRegion& other) const {
+ [[nodiscard]] bool operator==(const SpecialRegion& other) const {
return std::tie(type, handler) == std::tie(other.type, other.handler);
}
};
@@ -47,49 +48,35 @@ struct SpecialRegion {
* mimics the way a real CPU page table works.
*/
struct PageTable {
- explicit PageTable(std::size_t page_size_in_bits);
- ~PageTable();
+ PageTable();
+ ~PageTable() noexcept;
+
+ PageTable(const PageTable&) = delete;
+ PageTable& operator=(const PageTable&) = delete;
+
+ PageTable(PageTable&&) noexcept = default;
+ PageTable& operator=(PageTable&&) noexcept = default;
/**
* Resizes the page table to be able to accomodate enough pages within
* a given address space.
*
* @param address_space_width_in_bits The address size width in bits.
+ * @param page_size_in_bits The page size in bits.
+ * @param has_attribute Whether or not this page has any backing attributes.
*/
- void Resize(std::size_t address_space_width_in_bits);
+ void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits,
+ bool has_attribute);
/**
* Vector of memory pointers backing each page. An entry can only be non-null if the
* corresponding entry in the `attributes` vector is of type `Memory`.
*/
- std::vector<u8*> pointers;
-
- /**
- * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
- * of type `Special`.
- */
- boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
-
- /**
- * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
- * the corresponding entry in `pointers` MUST be set to null.
- */
- std::vector<PageType> attributes;
-
- const std::size_t page_size_in_bits{};
-};
-
-/**
- * A more advanced Page Table with the ability to save a backing address when using it
- * depends on another MMU.
- */
-struct BackingPageTable : PageTable {
- explicit BackingPageTable(std::size_t page_size_in_bits);
- ~BackingPageTable();
+ VirtualBuffer<u8*> pointers;
- void Resize(std::size_t address_space_width_in_bits);
+ VirtualBuffer<u64> backing_addr;
- std::vector<u64> backing_addr;
+ VirtualBuffer<PageType> attributes;
};
} // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 6a0a9b656..c13e45479 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -19,19 +19,19 @@ public:
explicit ParamPackage(const std::string& serialized);
ParamPackage(std::initializer_list<DataType::value_type> list);
ParamPackage(const ParamPackage& other) = default;
- ParamPackage(ParamPackage&& other) = default;
+ ParamPackage(ParamPackage&& other) noexcept = default;
ParamPackage& operator=(const ParamPackage& other) = default;
ParamPackage& operator=(ParamPackage&& other) = default;
- std::string Serialize() const;
- std::string Get(const std::string& key, const std::string& default_value) const;
- int Get(const std::string& key, int default_value) const;
- float Get(const std::string& key, float default_value) const;
+ [[nodiscard]] std::string Serialize() const;
+ [[nodiscard]] std::string Get(const std::string& key, const std::string& default_value) const;
+ [[nodiscard]] int Get(const std::string& key, int default_value) const;
+ [[nodiscard]] float Get(const std::string& key, float default_value) const;
void Set(const std::string& key, std::string value);
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
- bool Has(const std::string& key) const;
+ [[nodiscard]] bool Has(const std::string& key) const;
void Erase(const std::string& key);
void Clear();
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index 370198ae0..4d0871eb4 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -14,35 +14,66 @@ public:
Vec3<T> xyz;
T w{};
- Quaternion<decltype(-T{})> Inverse() const {
+ [[nodiscard]] Quaternion<decltype(-T{})> Inverse() const {
return {-xyz, w};
}
- Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
return {xyz + other.xyz, w + other.w};
}
- Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
return {xyz - other.xyz, w - other.w};
}
- Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(
+ const Quaternion& other) const {
return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
w * other.w - Dot(xyz, other.xyz)};
}
- Quaternion<T> Normalized() const {
+ [[nodiscard]] Quaternion<T> Normalized() const {
T length = std::sqrt(xyz.Length2() + w * w);
return {xyz / length, w / length};
}
+
+ [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
+ const T x2 = xyz[0] * xyz[0];
+ const T y2 = xyz[1] * xyz[1];
+ const T z2 = xyz[2] * xyz[2];
+
+ const T xy = xyz[0] * xyz[1];
+ const T wz = w * xyz[2];
+ const T xz = xyz[0] * xyz[2];
+ const T wy = w * xyz[1];
+ const T yz = xyz[1] * xyz[2];
+ const T wx = w * xyz[0];
+
+ return {1.0f - 2.0f * (y2 + z2),
+ 2.0f * (xy + wz),
+ 2.0f * (xz - wy),
+ 0.0f,
+ 2.0f * (xy - wz),
+ 1.0f - 2.0f * (x2 + z2),
+ 2.0f * (yz + wx),
+ 0.0f,
+ 2.0f * (xz + wy),
+ 2.0f * (yz - wx),
+ 1.0f - 2.0f * (x2 + y2),
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f};
+ }
};
template <typename T>
-auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
+[[nodiscard]] auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
}
-inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
+[[nodiscard]] inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
}
diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h
index abe3b4dc2..138fa0131 100644
--- a/src/common/ring_buffer.h
+++ b/src/common/ring_buffer.h
@@ -91,12 +91,12 @@ public:
}
/// @returns Number of slots used
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return m_write_index.load() - m_read_index.load();
}
/// @returns Maximum size of ring buffer
- constexpr std::size_t Capacity() const {
+ [[nodiscard]] constexpr std::size_t Capacity() const {
return capacity;
}
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index 1176a72b1..68ef5f197 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -12,10 +12,17 @@ template <typename Func>
struct ScopeExitHelper {
explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
~ScopeExitHelper() {
- func();
+ if (active) {
+ func();
+ }
+ }
+
+ void Cancel() {
+ active = false;
}
Func func;
+ bool active{true};
};
template <typename Func>
diff --git a/src/common/spin_lock.cpp b/src/common/spin_lock.cpp
new file mode 100644
index 000000000..c1524220f
--- /dev/null
+++ b/src/common/spin_lock.cpp
@@ -0,0 +1,54 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/spin_lock.h"
+
+#if _MSC_VER
+#include <intrin.h>
+#if _M_AMD64
+#define __x86_64__ 1
+#endif
+#if _M_ARM64
+#define __aarch64__ 1
+#endif
+#else
+#if __x86_64__
+#include <xmmintrin.h>
+#endif
+#endif
+
+namespace {
+
+void ThreadPause() {
+#if __x86_64__
+ _mm_pause();
+#elif __aarch64__ && _MSC_VER
+ __yield();
+#elif __aarch64__
+ asm("yield");
+#endif
+}
+
+} // Anonymous namespace
+
+namespace Common {
+
+void SpinLock::lock() {
+ while (lck.test_and_set(std::memory_order_acquire)) {
+ ThreadPause();
+ }
+}
+
+void SpinLock::unlock() {
+ lck.clear(std::memory_order_release);
+}
+
+bool SpinLock::try_lock() {
+ if (lck.test_and_set(std::memory_order_acquire)) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace Common
diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h
new file mode 100644
index 000000000..06ac2f5bb
--- /dev/null
+++ b/src/common/spin_lock.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+
+namespace Common {
+
+/**
+ * SpinLock class
+ * a lock similar to mutex that forces a thread to spin wait instead calling the
+ * supervisor. Should be used on short sequences of code.
+ */
+class SpinLock {
+public:
+ SpinLock() = default;
+
+ SpinLock(const SpinLock&) = delete;
+ SpinLock& operator=(const SpinLock&) = delete;
+
+ SpinLock(SpinLock&&) = delete;
+ SpinLock& operator=(SpinLock&&) = delete;
+
+ void lock();
+ void unlock();
+ [[nodiscard]] bool try_lock();
+
+private:
+ std::atomic_flag lck = ATOMIC_FLAG_INIT;
+};
+
+} // namespace Common
diff --git a/src/common/stream.cpp b/src/common/stream.cpp
new file mode 100644
index 000000000..bf0496c26
--- /dev/null
+++ b/src/common/stream.cpp
@@ -0,0 +1,47 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <stdexcept>
+#include "common/common_types.h"
+#include "common/stream.h"
+
+namespace Common {
+
+Stream::Stream() = default;
+Stream::~Stream() = default;
+
+void Stream::Seek(s32 offset, SeekOrigin origin) {
+ if (origin == SeekOrigin::SetOrigin) {
+ if (offset < 0) {
+ position = 0;
+ } else if (position >= buffer.size()) {
+ position = buffer.size();
+ } else {
+ position = offset;
+ }
+ } else if (origin == SeekOrigin::FromCurrentPos) {
+ Seek(static_cast<s32>(position) + offset, SeekOrigin::SetOrigin);
+ } else if (origin == SeekOrigin::FromEnd) {
+ Seek(static_cast<s32>(buffer.size()) - offset, SeekOrigin::SetOrigin);
+ }
+}
+
+u8 Stream::ReadByte() {
+ if (position < buffer.size()) {
+ return buffer[position++];
+ } else {
+ throw std::out_of_range("Attempting to read a byte not within the buffer range");
+ }
+}
+
+void Stream::WriteByte(u8 byte) {
+ if (position == buffer.size()) {
+ buffer.push_back(byte);
+ position++;
+ } else {
+ buffer.insert(buffer.begin() + position, byte);
+ }
+}
+
+} // namespace Common
diff --git a/src/common/stream.h b/src/common/stream.h
new file mode 100644
index 000000000..0e40692de
--- /dev/null
+++ b/src/common/stream.h
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+
+namespace Common {
+
+enum class SeekOrigin {
+ SetOrigin,
+ FromCurrentPos,
+ FromEnd,
+};
+
+class Stream {
+public:
+ /// Stream creates a bitstream and provides common functionality on the stream.
+ explicit Stream();
+ ~Stream();
+
+ Stream(const Stream&) = delete;
+ Stream& operator=(const Stream&) = delete;
+
+ Stream(Stream&&) = default;
+ Stream& operator=(Stream&&) = default;
+
+ /// Reposition bitstream "cursor" to the specified offset from origin
+ void Seek(s32 offset, SeekOrigin origin);
+
+ /// Reads next byte in the stream buffer and increments position
+ u8 ReadByte();
+
+ /// Writes byte at current position
+ void WriteByte(u8 byte);
+
+ [[nodiscard]] std::size_t GetPosition() const {
+ return position;
+ }
+
+ [[nodiscard]] std::vector<u8>& GetBuffer() {
+ return buffer;
+ }
+
+ [[nodiscard]] const std::vector<u8>& GetBuffer() const {
+ return buffer;
+ }
+
+private:
+ std::vector<u8> buffer;
+ std::size_t position{0};
+};
+
+} // namespace Common
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 84883a1d3..4cba2aaa4 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -8,6 +8,7 @@
#include <cstdlib>
#include <locale>
#include <sstream>
+
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -21,14 +22,14 @@ namespace Common {
/// Make a string lowercase
std::string ToLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
- [](unsigned char c) { return std::tolower(c); });
+ [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return str;
}
/// Make a string uppercase
std::string ToUpper(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
- [](unsigned char c) { return std::toupper(c); });
+ [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return str;
}
diff --git a/src/common/string_util.h b/src/common/string_util.h
index 583fd05e6..a32c07c06 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -12,19 +12,19 @@
namespace Common {
/// Make a string lowercase
-std::string ToLower(std::string str);
+[[nodiscard]] std::string ToLower(std::string str);
/// Make a string uppercase
-std::string ToUpper(std::string str);
+[[nodiscard]] std::string ToUpper(std::string str);
-std::string StringFromBuffer(const std::vector<u8>& data);
+[[nodiscard]] std::string StringFromBuffer(const std::vector<u8>& data);
-std::string StripSpaces(const std::string& s);
-std::string StripQuotes(const std::string& s);
+[[nodiscard]] std::string StripSpaces(const std::string& s);
+[[nodiscard]] std::string StripQuotes(const std::string& s);
-std::string StringFromBool(bool value);
+[[nodiscard]] std::string StringFromBool(bool value);
-std::string TabsToSpaces(int tab_size, std::string in);
+[[nodiscard]] std::string TabsToSpaces(int tab_size, std::string in);
void SplitString(const std::string& str, char delim, std::vector<std::string>& output);
@@ -34,14 +34,15 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
const std::string& _Filename);
-std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest);
+[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
+ const std::string& dest);
-std::string UTF16ToUTF8(const std::u16string& input);
-std::u16string UTF8ToUTF16(const std::string& input);
+[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input);
+[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input);
#ifdef _WIN32
-std::string UTF16ToUTF8(const std::wstring& input);
-std::wstring UTF8ToUTF16W(const std::string& str);
+[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input);
+[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str);
#endif
@@ -50,7 +51,7 @@ std::wstring UTF8ToUTF16W(const std::string& str);
* `other` for equality.
*/
template <typename InIt>
-bool ComparePartialString(InIt begin, InIt end, const char* other) {
+[[nodiscard]] bool ComparePartialString(InIt begin, InIt end, const char* other) {
for (; begin != end && *other != '\0'; ++begin, ++other) {
if (*begin != *other) {
return false;
@@ -64,26 +65,15 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) {
* Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't
* NUL-terminated then the string ends at max_len characters.
*/
-std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len);
+[[nodiscard]] std::string StringFromFixedZeroTerminatedBuffer(const char* buffer,
+ std::size_t max_len);
/**
* Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't
* null-terminated, then the string ends at the greatest multiple of two less then or equal to
* max_len_bytes.
*/
-std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
- std::size_t max_len);
-
-/**
- * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
- * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
- * leaving only the path relative to the sources root.
- *
- * @param path The input file path as a null-terminated string
- * @param root The name of the root source directory as a null-terminated string. Path up to and
- * including the last occurrence of this name will be stripped
- * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
- */
-const char* TrimSourcePath(const char* path, const char* root = "src");
+[[nodiscard]] std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
+ std::size_t max_len);
} // namespace Common
diff --git a/src/common/swap.h b/src/common/swap.h
index 71932c2bb..7665942a2 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -17,43 +17,14 @@
#pragma once
-#include <type_traits>
-
#if defined(_MSC_VER)
#include <cstdlib>
#endif
+#include <bit>
#include <cstring>
+#include <type_traits>
#include "common/common_types.h"
-// GCC
-#ifdef __GNUC__
-
-#if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN)
-#define COMMON_LITTLE_ENDIAN 1
-#elif __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) && !defined(COMMON_BIG_ENDIAN)
-#define COMMON_BIG_ENDIAN 1
-#endif
-
-// LLVM/clang
-#elif defined(__clang__)
-
-#if __LITTLE_ENDIAN__ && !defined(COMMON_LITTLE_ENDIAN)
-#define COMMON_LITTLE_ENDIAN 1
-#elif __BIG_ENDIAN__ && !defined(COMMON_BIG_ENDIAN)
-#define COMMON_BIG_ENDIAN 1
-#endif
-
-// MSVC
-#elif defined(_MSC_VER) && !defined(COMMON_BIG_ENDIAN) && !defined(COMMON_LITTLE_ENDIAN)
-
-#define COMMON_LITTLE_ENDIAN 1
-#endif
-
-// Worst case, default to little endian.
-#if !COMMON_BIG_ENDIAN && !COMMON_LITTLE_ENDIAN
-#define COMMON_LITTLE_ENDIAN 1
-#endif
-
namespace Common {
#ifdef _MSC_VER
@@ -675,17 +646,8 @@ struct AddEndian<T, SwapTag> {
};
// Alias LETag/BETag as KeepTag/SwapTag depending on the system
-#if COMMON_LITTLE_ENDIAN
-
-using LETag = KeepTag;
-using BETag = SwapTag;
-
-#else
-
-using BETag = KeepTag;
-using LETag = SwapTag;
-
-#endif
+using LETag = std::conditional_t<std::endian::native == std::endian::little, KeepTag, SwapTag>;
+using BETag = std::conditional_t<std::endian::native == std::endian::big, KeepTag, SwapTag>;
// Aliases for LE types
using u16_le = AddEndian<u16, LETag>::type;
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index 200c6489a..6241d08b3 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -12,7 +12,7 @@
#include "common/x64/cpu_detect.h"
#endif
-namespace Telemetry {
+namespace Common::Telemetry {
void FieldCollection::Accept(VisitorInterface& visitor) const {
for (const auto& field : fields) {
@@ -60,6 +60,7 @@ void AppendCPUInfo(FieldCollection& fc) {
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
+ fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX512", Common::GetCPUCaps().avx512);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
@@ -87,4 +88,4 @@ void AppendOSInfo(FieldCollection& fc) {
#endif
}
-} // namespace Telemetry
+} // namespace Common::Telemetry
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index 854a73fae..a50c5d1de 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -10,7 +10,7 @@
#include <string>
#include "common/common_types.h"
-namespace Telemetry {
+namespace Common::Telemetry {
/// Field type, used for grouping fields together in the final submitted telemetry log
enum class FieldType : u8 {
@@ -63,30 +63,30 @@ public:
void Accept(VisitorInterface& visitor) const override;
- const std::string& GetName() const override {
+ [[nodiscard]] const std::string& GetName() const override {
return name;
}
/**
* Returns the type of the field.
*/
- FieldType GetType() const {
+ [[nodiscard]] FieldType GetType() const {
return type;
}
/**
* Returns the value of the field.
*/
- const T& GetValue() const {
+ [[nodiscard]] const T& GetValue() const {
return value;
}
- bool operator==(const Field& other) const {
+ [[nodiscard]] bool operator==(const Field& other) const {
return (type == other.type) && (name == other.name) && (value == other.value);
}
- bool operator!=(const Field& other) const {
- return !(*this == other);
+ [[nodiscard]] bool operator!=(const Field& other) const {
+ return !operator==(other);
}
private:
@@ -196,4 +196,4 @@ void AppendCPUInfo(FieldCollection& fc);
/// such as platform name, etc.
void AppendOSInfo(FieldCollection& fc);
-} // namespace Telemetry
+} // namespace Common::Telemetry
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 0cd2d10bf..d2c1ac60d 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/common_funcs.h"
+#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -19,12 +21,60 @@
#include <unistd.h>
#endif
+#include <string>
+
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
namespace Common {
+#ifdef _WIN32
+
+void SetCurrentThreadPriority(ThreadPriority new_priority) {
+ auto handle = GetCurrentThread();
+ int windows_priority = 0;
+ switch (new_priority) {
+ case ThreadPriority::Low:
+ windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
+ break;
+ case ThreadPriority::Normal:
+ windows_priority = THREAD_PRIORITY_NORMAL;
+ break;
+ case ThreadPriority::High:
+ windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
+ break;
+ case ThreadPriority::VeryHigh:
+ windows_priority = THREAD_PRIORITY_HIGHEST;
+ break;
+ default:
+ windows_priority = THREAD_PRIORITY_NORMAL;
+ break;
+ }
+ SetThreadPriority(handle, windows_priority);
+}
+
+#else
+
+void SetCurrentThreadPriority(ThreadPriority new_priority) {
+ pthread_t this_thread = pthread_self();
+
+ s32 max_prio = sched_get_priority_max(SCHED_OTHER);
+ s32 min_prio = sched_get_priority_min(SCHED_OTHER);
+ u32 level = static_cast<u32>(new_priority) + 1;
+
+ struct sched_param params;
+ if (max_prio > min_prio) {
+ params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
+ } else {
+ params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
+ }
+
+ pthread_setschedparam(this_thread, SCHED_OTHER, &params);
+}
+
+#endif
+
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
@@ -64,12 +114,26 @@ void SetCurrentThreadName(const char* name) {
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
+#elif defined(__linux__)
+ // Linux limits thread names to 15 characters and will outright reject any
+ // attempt to set a longer name with ERANGE.
+ std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15)));
+ if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
+ errno = e;
+ LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
+ }
#else
pthread_setname_np(pthread_self(), name);
#endif
}
#endif
+#if defined(_WIN32)
+void SetCurrentThreadName(const char* name) {
+ // Do Nothing on MingW
+}
+#endif
+
#endif
} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index 2fc071685..a8c17c71a 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,11 +4,13 @@
#pragma once
+#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <mutex>
#include <thread>
+#include "common/common_types.h"
namespace Common {
@@ -24,14 +26,13 @@ public:
void Wait() {
std::unique_lock lk{mutex};
- condvar.wait(lk, [&] { return is_set; });
+ condvar.wait(lk, [&] { return is_set.load(); });
is_set = false;
}
- template <class Duration>
- bool WaitFor(const std::chrono::duration<Duration>& time) {
+ bool WaitFor(const std::chrono::nanoseconds& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_for(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -40,7 +41,7 @@ public:
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_until(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -54,9 +55,9 @@ public:
}
private:
- bool is_set = false;
std::condition_variable condvar;
std::mutex mutex;
+ std::atomic_bool is_set{false};
};
class Barrier {
@@ -86,6 +87,15 @@ private:
std::size_t generation = 0; // Incremented once each time the barrier is used
};
+enum class ThreadPriority : u32 {
+ Low = 0,
+ Normal = 1,
+ High = 2,
+ VeryHigh = 3,
+};
+
+void SetCurrentThreadPriority(ThreadPriority new_priority);
+
void SetCurrentThreadName(const char* name);
} // namespace Common
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 791f99a8c..def9e5d8d 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -18,14 +18,14 @@ struct ThreadQueueList {
using Priority = unsigned int;
// Number of priority levels. (Valid levels are [0..NUM_QUEUES).)
- static const Priority NUM_QUEUES = N;
+ static constexpr Priority NUM_QUEUES = N;
ThreadQueueList() {
first = nullptr;
}
// Only for debugging, returns priority level.
- Priority contains(const T& uid) const {
+ [[nodiscard]] Priority contains(const T& uid) const {
for (Priority i = 0; i < NUM_QUEUES; ++i) {
const Queue& cur = queues[i];
if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) {
@@ -36,7 +36,7 @@ struct ThreadQueueList {
return -1;
}
- T get_first() const {
+ [[nodiscard]] T get_first() const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
@@ -49,7 +49,7 @@ struct ThreadQueueList {
}
template <typename UnaryPredicate>
- T get_first_filter(UnaryPredicate filter) const {
+ [[nodiscard]] T get_first_filter(UnaryPredicate filter) const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
@@ -129,7 +129,7 @@ struct ThreadQueueList {
first = nullptr;
}
- bool empty(Priority priority) const {
+ [[nodiscard]] bool empty(Priority priority) const {
const Queue* cur = &queues[priority];
return cur->data.empty();
}
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 8268bbd5c..a4647314a 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -25,15 +25,15 @@ public:
delete read_ptr;
}
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return size.load();
}
- bool Empty() const {
+ [[nodiscard]] bool Empty() const {
return Size() == 0;
}
- T& Front() const {
+ [[nodiscard]] T& Front() const {
return read_ptr->current;
}
@@ -130,15 +130,15 @@ private:
template <typename T>
class MPSCQueue {
public:
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return spsc_queue.Size();
}
- bool Empty() const {
+ [[nodiscard]] bool Empty() const {
return spsc_queue.Empty();
}
- T& Front() const {
+ [[nodiscard]] T& Front() const {
return spsc_queue.Front();
}
diff --git a/src/common/time_zone.cpp b/src/common/time_zone.cpp
new file mode 100644
index 000000000..ce239eb63
--- /dev/null
+++ b/src/common/time_zone.cpp
@@ -0,0 +1,49 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <iomanip>
+#include <sstream>
+
+#include "common/logging/log.h"
+#include "common/time_zone.h"
+
+namespace Common::TimeZone {
+
+std::string GetDefaultTimeZone() {
+ return "GMT";
+}
+
+static std::string GetOsTimeZoneOffset() {
+ const std::time_t t{std::time(nullptr)};
+ const std::tm tm{*std::localtime(&t)};
+
+ std::stringstream ss;
+ ss << std::put_time(&tm, "%z"); // Get the current timezone offset, e.g. "-400", as a string
+
+ return ss.str();
+}
+
+static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) {
+ try {
+ return std::stoi(timezone);
+ } catch (const std::invalid_argument&) {
+ LOG_CRITICAL(Common, "invalid_argument with {}!", timezone);
+ return 0;
+ } catch (const std::out_of_range&) {
+ LOG_CRITICAL(Common, "out_of_range with {}!", timezone);
+ return 0;
+ }
+}
+
+std::chrono::seconds GetCurrentOffsetSeconds() {
+ const int offset{ConvertOsTimeZoneOffsetToInt(GetOsTimeZoneOffset())};
+
+ int seconds{(offset / 100) * 60 * 60}; // Convert hour component to seconds
+ seconds += (offset % 100) * 60; // Convert minute component to seconds
+
+ return std::chrono::seconds{seconds};
+}
+
+} // namespace Common::TimeZone
diff --git a/src/common/time_zone.h b/src/common/time_zone.h
new file mode 100644
index 000000000..9f5939ca5
--- /dev/null
+++ b/src/common/time_zone.h
@@ -0,0 +1,18 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <string>
+
+namespace Common::TimeZone {
+
+/// Gets the default timezone, i.e. "GMT"
+[[nodiscard]] std::string GetDefaultTimeZone();
+
+/// Gets the offset of the current timezone (from the default), in seconds
+[[nodiscard]] std::chrono::seconds GetCurrentOffsetSeconds();
+
+} // namespace Common::TimeZone
diff --git a/src/common/timer.cpp b/src/common/timer.cpp
index 2dc15e434..d17dc2a50 100644
--- a/src/common/timer.cpp
+++ b/src/common/timer.cpp
@@ -142,20 +142,18 @@ std::string Timer::GetTimeFormatted() {
// ----------------
double Timer::GetDoubleTime() {
// Get continuous timestamp
- u64 TmpSeconds = static_cast<u64>(Common::Timer::GetTimeSinceJan1970().count());
- double ms = static_cast<u64>(GetTimeMs().count()) % 1000;
+ auto tmp_seconds = static_cast<u64>(GetTimeSinceJan1970().count());
+ const auto ms = static_cast<double>(static_cast<u64>(GetTimeMs().count()) % 1000);
// Remove a few years. We only really want enough seconds to make
// sure that we are detecting actual actions, perhaps 60 seconds is
// enough really, but I leave a year of seconds anyway, in case the
// user's clock is incorrect or something like that.
- TmpSeconds = TmpSeconds - (38 * 365 * 24 * 60 * 60);
+ tmp_seconds = tmp_seconds - (38 * 365 * 24 * 60 * 60);
// Make a smaller integer that fits in the double
- u32 Seconds = static_cast<u32>(TmpSeconds);
- double TmpTime = Seconds + ms;
-
- return TmpTime;
+ const auto seconds = static_cast<u32>(tmp_seconds);
+ return seconds + ms;
}
} // Namespace Common
diff --git a/src/common/timer.h b/src/common/timer.h
index 27b521baa..8894a143d 100644
--- a/src/common/timer.h
+++ b/src/common/timer.h
@@ -19,18 +19,18 @@ public:
// The time difference is always returned in milliseconds, regardless of alternative internal
// representation
- std::chrono::milliseconds GetTimeDifference();
+ [[nodiscard]] std::chrono::milliseconds GetTimeDifference();
void AddTimeDifference();
- static std::chrono::seconds GetTimeSinceJan1970();
- static std::chrono::seconds GetLocalTimeSinceJan1970();
- static double GetDoubleTime();
+ [[nodiscard]] static std::chrono::seconds GetTimeSinceJan1970();
+ [[nodiscard]] static std::chrono::seconds GetLocalTimeSinceJan1970();
+ [[nodiscard]] static double GetDoubleTime();
- static std::string GetTimeFormatted();
- std::string GetTimeElapsedFormatted() const;
- std::chrono::milliseconds GetTimeElapsed();
+ [[nodiscard]] static std::string GetTimeFormatted();
+ [[nodiscard]] std::string GetTimeElapsedFormatted() const;
+ [[nodiscard]] std::chrono::milliseconds GetTimeElapsed();
- static std::chrono::milliseconds GetTimeMs();
+ [[nodiscard]] static std::chrono::milliseconds GetTimeMs();
private:
std::chrono::milliseconds m_LastTime;
diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp
index 32bf56730..16bf7c828 100644
--- a/src/common/uint128.cpp
+++ b/src/common/uint128.cpp
@@ -6,12 +6,38 @@
#include <intrin.h>
#pragma intrinsic(_umul128)
+#pragma intrinsic(_udiv128)
#endif
#include <cstring>
#include "common/uint128.h"
namespace Common {
+#ifdef _MSC_VER
+
+u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
+ u128 r{};
+ r[0] = _umul128(a, b, &r[1]);
+ u64 remainder;
+#if _MSC_VER < 1923
+ return udiv128(r[1], r[0], d, &remainder);
+#else
+ return _udiv128(r[1], r[0], d, &remainder);
+#endif
+}
+
+#else
+
+u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
+ const u64 diva = a / d;
+ const u64 moda = a % d;
+ const u64 divb = b / d;
+ const u64 modb = b % d;
+ return diva * b + moda * divb + moda * modb / d;
+}
+
+#endif
+
u128 Multiply64Into128(u64 a, u64 b) {
u128 result;
#ifdef _MSC_VER
diff --git a/src/common/uint128.h b/src/common/uint128.h
index a3be2a2cb..969259ab6 100644
--- a/src/common/uint128.h
+++ b/src/common/uint128.h
@@ -9,11 +9,14 @@
namespace Common {
+// This function multiplies 2 u64 values and divides it by a u64 value.
+[[nodiscard]] u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
+
// This function multiplies 2 u64 values and produces a u128 value;
-u128 Multiply64Into128(u64 a, u64 b);
+[[nodiscard]] u128 Multiply64Into128(u64 a, u64 b);
// This function divides a u128 by a u32 value and produces two u64 values:
// the result of division and the remainder
-std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
+[[nodiscard]] std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
} // namespace Common
diff --git a/src/common/uuid.h b/src/common/uuid.h
index f6ad064fb..4ab9a25f0 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -19,29 +19,34 @@ struct UUID {
constexpr explicit UUID(const u128& id) : uuid{id} {}
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
- constexpr explicit operator bool() const {
+ [[nodiscard]] constexpr explicit operator bool() const {
return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1];
}
- constexpr bool operator==(const UUID& rhs) const {
+ [[nodiscard]] constexpr bool operator==(const UUID& rhs) const {
// TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20
return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1];
}
- constexpr bool operator!=(const UUID& rhs) const {
+ [[nodiscard]] constexpr bool operator!=(const UUID& rhs) const {
return !operator==(rhs);
}
// TODO(ogniK): Properly generate uuids based on RFC-4122
- static UUID Generate();
+ [[nodiscard]] static UUID Generate();
// Set the UUID to {0,0} to be considered an invalid user
constexpr void Invalidate() {
uuid = INVALID_UUID;
}
- std::string Format() const;
- std::string FormatSwitch() const;
+ // TODO(ogniK): Properly generate a Nintendo ID
+ [[nodiscard]] constexpr u64 GetNintendoID() const {
+ return uuid[0];
+ }
+
+ [[nodiscard]] std::string Format() const;
+ [[nodiscard]] std::string FormatSwitch() const;
};
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index 429485329..22dba3c2d 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -52,15 +52,15 @@ public:
constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
template <typename T2>
- constexpr Vec2<T2> Cast() const {
+ [[nodiscard]] constexpr Vec2<T2> Cast() const {
return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y));
}
- static constexpr Vec2 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) {
return Vec2{f, f};
}
- constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
return {x + other.x, y + other.y};
}
constexpr Vec2& operator+=(const Vec2& other) {
@@ -68,7 +68,7 @@ public:
y += other.y;
return *this;
}
- constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
return {x - other.x, y - other.y};
}
constexpr Vec2& operator-=(const Vec2& other) {
@@ -78,16 +78,22 @@ public:
}
template <typename U = T>
- constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y};
}
- constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
return {x * other.x, y * other.y};
}
template <typename V>
- constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f};
+ [[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -97,8 +103,14 @@ public:
}
template <typename V>
- constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f};
+ [[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -107,18 +119,18 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y;
}
// Only implemented for T=float
- float Length() const;
- float Normalize(); // returns the previous length, which is often useful
+ [[nodiscard]] float Length() const;
+ [[nodiscard]] float Normalize(); // returns the previous length, which is often useful
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -128,47 +140,50 @@ public:
}
// Common aliases: UV (texel coordinates), ST (texture coordinates)
- constexpr T& u() {
+ [[nodiscard]] constexpr T& u() {
return x;
}
- constexpr T& v() {
+ [[nodiscard]] constexpr T& v() {
return y;
}
- constexpr T& s() {
+ [[nodiscard]] constexpr T& s() {
return x;
}
- constexpr T& t() {
+ [[nodiscard]] constexpr T& t() {
return y;
}
- constexpr const T& u() const {
+ [[nodiscard]] constexpr const T& u() const {
return x;
}
- constexpr const T& v() const {
+ [[nodiscard]] constexpr const T& v() const {
return y;
}
- constexpr const T& s() const {
+ [[nodiscard]] constexpr const T& s() const {
return x;
}
- constexpr const T& t() const {
+ [[nodiscard]] constexpr const T& t() const {
return y;
}
// swizzlers - create a subvector of specific components
- constexpr Vec2 yx() const {
+ [[nodiscard]] constexpr Vec2 yx() const {
return Vec2(y, x);
}
- constexpr Vec2 vu() const {
+ [[nodiscard]] constexpr Vec2 vu() const {
return Vec2(y, x);
}
- constexpr Vec2 ts() const {
+ [[nodiscard]] constexpr Vec2 ts() const {
return Vec2(y, x);
}
};
template <typename T, typename V>
-constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
- return Vec2<T>(f * vec.x, f * vec.y);
+[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
+ using C = std::common_type_t<T, V>;
+
+ return Vec2<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)));
}
using Vec2f = Vec2<float>;
@@ -196,15 +211,15 @@ public:
constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
template <typename T2>
- constexpr Vec3<T2> Cast() const {
+ [[nodiscard]] constexpr Vec3<T2> Cast() const {
return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
}
- static constexpr Vec3 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) {
return Vec3(f, f, f);
}
- constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
return {x + other.x, y + other.y, z + other.z};
}
@@ -215,7 +230,7 @@ public:
return *this;
}
- constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
return {x - other.x, y - other.y, z - other.z};
}
@@ -227,17 +242,24 @@ public:
}
template <typename U = T>
- constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z};
}
- constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
return {x * other.x, y * other.y, z * other.z};
}
template <typename V>
- constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f, z * f};
+ [[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -246,8 +268,15 @@ public:
return *this;
}
template <typename V>
- constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f, z / f};
+ [[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -256,20 +285,20 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z;
}
// Only implemented for T=float
- float Length() const;
- Vec3 Normalized() const;
- float Normalize(); // returns the previous length, which is often useful
+ [[nodiscard]] float Length() const;
+ [[nodiscard]] Vec3 Normalized() const;
+ [[nodiscard]] float Normalize(); // returns the previous length, which is often useful
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -280,63 +309,63 @@ public:
}
// Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
- constexpr T& u() {
+ [[nodiscard]] constexpr T& u() {
return x;
}
- constexpr T& v() {
+ [[nodiscard]] constexpr T& v() {
return y;
}
- constexpr T& w() {
+ [[nodiscard]] constexpr T& w() {
return z;
}
- constexpr T& r() {
+ [[nodiscard]] constexpr T& r() {
return x;
}
- constexpr T& g() {
+ [[nodiscard]] constexpr T& g() {
return y;
}
- constexpr T& b() {
+ [[nodiscard]] constexpr T& b() {
return z;
}
- constexpr T& s() {
+ [[nodiscard]] constexpr T& s() {
return x;
}
- constexpr T& t() {
+ [[nodiscard]] constexpr T& t() {
return y;
}
- constexpr T& q() {
+ [[nodiscard]] constexpr T& q() {
return z;
}
- constexpr const T& u() const {
+ [[nodiscard]] constexpr const T& u() const {
return x;
}
- constexpr const T& v() const {
+ [[nodiscard]] constexpr const T& v() const {
return y;
}
- constexpr const T& w() const {
+ [[nodiscard]] constexpr const T& w() const {
return z;
}
- constexpr const T& r() const {
+ [[nodiscard]] constexpr const T& r() const {
return x;
}
- constexpr const T& g() const {
+ [[nodiscard]] constexpr const T& g() const {
return y;
}
- constexpr const T& b() const {
+ [[nodiscard]] constexpr const T& b() const {
return z;
}
- constexpr const T& s() const {
+ [[nodiscard]] constexpr const T& s() const {
return x;
}
- constexpr const T& t() const {
+ [[nodiscard]] constexpr const T& t() const {
return y;
}
- constexpr const T& q() const {
+ [[nodiscard]] constexpr const T& q() const {
return z;
}
@@ -345,7 +374,7 @@ public:
// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
// component names (x<->r) and permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
- constexpr Vec2<T> name() const { \
+ [[nodiscard]] constexpr Vec2<T> name() const { \
return Vec2<T>(a, b); \
}
#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \
@@ -366,8 +395,12 @@ public:
};
template <typename T, typename V>
-constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
- return Vec3<T>(f * vec.x, f * vec.y, f * vec.z);
+[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
+ using C = std::common_type_t<T, V>;
+
+ return Vec3<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.z)));
}
template <>
@@ -402,16 +435,16 @@ public:
: x(x_), y(y_), z(z_), w(w_) {}
template <typename T2>
- constexpr Vec4<T2> Cast() const {
+ [[nodiscard]] constexpr Vec4<T2> Cast() const {
return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
static_cast<T2>(w));
}
- static constexpr Vec4 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) {
return Vec4(f, f, f, f);
}
- constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
return {x + other.x, y + other.y, z + other.z, w + other.w};
}
@@ -423,7 +456,7 @@ public:
return *this;
}
- constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
return {x - other.x, y - other.y, z - other.z, w - other.w};
}
@@ -436,17 +469,25 @@ public:
}
template <typename U = T>
- constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z, -w};
}
- constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
return {x * other.x, y * other.y, z * other.z, w * other.w};
}
template <typename V>
- constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f, z * f, w * f};
+ [[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(w) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -456,8 +497,16 @@ public:
}
template <typename V>
- constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f, z / f, w / f};
+ [[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(w) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -466,15 +515,15 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z + w * w;
}
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -486,29 +535,29 @@ public:
}
// Common alias: RGBA (colors)
- constexpr T& r() {
+ [[nodiscard]] constexpr T& r() {
return x;
}
- constexpr T& g() {
+ [[nodiscard]] constexpr T& g() {
return y;
}
- constexpr T& b() {
+ [[nodiscard]] constexpr T& b() {
return z;
}
- constexpr T& a() {
+ [[nodiscard]] constexpr T& a() {
return w;
}
- constexpr const T& r() const {
+ [[nodiscard]] constexpr const T& r() const {
return x;
}
- constexpr const T& g() const {
+ [[nodiscard]] constexpr const T& g() const {
return y;
}
- constexpr const T& b() const {
+ [[nodiscard]] constexpr const T& b() const {
return z;
}
- constexpr const T& a() const {
+ [[nodiscard]] constexpr const T& a() const {
return w;
}
@@ -520,7 +569,7 @@ public:
// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
// permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
- constexpr Vec2<T> name() const { \
+ [[nodiscard]] constexpr Vec2<T> name() const { \
return Vec2<T>(a, b); \
}
#define DEFINE_SWIZZLER2_COMP1(a, a2) \
@@ -547,7 +596,7 @@ public:
#undef _DEFINE_SWIZZLER2
#define _DEFINE_SWIZZLER3(a, b, c, name) \
- constexpr Vec3<T> name() const { \
+ [[nodiscard]] constexpr Vec3<T> name() const { \
return Vec3<T>(a, b, c); \
}
#define DEFINE_SWIZZLER3_COMP1(a, a2) \
@@ -581,8 +630,16 @@ public:
};
template <typename T, typename V>
-constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
- return {f * vec.x, f * vec.y, f * vec.z, f * vec.w};
+[[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
+ using TV = decltype(V{} * T{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.y)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.z)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.w)),
+ };
}
using Vec4f = Vec4<float>;
@@ -593,39 +650,41 @@ constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b
}
template <typename T>
-constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
+[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
template <typename T>
-constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
+[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
template <typename T>
-constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
+[[nodiscard]] constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a,
+ const Vec3<T>& b) {
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
// linear interpolation via float: 0.0=begin, 1.0=end
template <typename X>
-constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
- const float t) {
+[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
+ const float t) {
return begin * (1.f - t) + end * t;
}
// linear interpolation via int: 0=begin, base=end
template <typename X, int base>
-constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
- const int t) {
+[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin,
+ const X& end,
+ const int t) {
return (begin * (base - t) + end * t) / base;
}
// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
// interpolation.
template <typename X>
-constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
- const float t) {
+[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11,
+ const float s, const float t) {
auto y0 = Lerp(x00, x01, s);
auto y1 = Lerp(x10, x11, s);
return Lerp(y0, y1, t);
@@ -633,42 +692,42 @@ constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X&
// Utility vector factories
template <typename T>
-constexpr Vec2<T> MakeVec(const T& x, const T& y) {
+[[nodiscard]] constexpr Vec2<T> MakeVec(const T& x, const T& y) {
return Vec2<T>{x, y};
}
template <typename T>
-constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
return Vec3<T>{x, y, z};
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
return MakeVec(x, y, zw[0], zw[1]);
}
template <typename T>
-constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
return MakeVec(xy[0], xy[1], z);
}
template <typename T>
-constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
return MakeVec(x, yz[0], yz[1]);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
return Vec4<T>{x, y, z, w};
}
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
return MakeVec(xy[0], xy[1], z, w);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
return MakeVec(x, yz[0], yz[1], w);
}
@@ -676,17 +735,17 @@ constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
// Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
// out soon enough due to misuse of the returned structure.
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
return MakeVec(xy[0], xy[1], zw[0], zw[1]);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
return MakeVec(xyz[0], xyz[1], xyz[2], w);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
}
diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp
new file mode 100644
index 000000000..e3ca29258
--- /dev/null
+++ b/src/common/virtual_buffer.cpp
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/mman.h>
+#endif
+
+#include "common/assert.h"
+#include "common/virtual_buffer.h"
+
+namespace Common {
+
+void* AllocateMemoryPages(std::size_t size) noexcept {
+#ifdef _WIN32
+ void* base{VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE)};
+#else
+ void* base{mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0)};
+
+ if (base == MAP_FAILED) {
+ base = nullptr;
+ }
+#endif
+
+ ASSERT(base);
+
+ return base;
+}
+
+void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
+ if (!base) {
+ return;
+ }
+#ifdef _WIN32
+ ASSERT(VirtualFree(base, 0, MEM_RELEASE));
+#else
+ ASSERT(munmap(base, size) == 0);
+#endif
+}
+
+} // namespace Common
diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h
new file mode 100644
index 000000000..91d430036
--- /dev/null
+++ b/src/common/virtual_buffer.h
@@ -0,0 +1,82 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+namespace Common {
+
+void* AllocateMemoryPages(std::size_t size) noexcept;
+void FreeMemoryPages(void* base, std::size_t size) noexcept;
+
+template <typename T>
+class VirtualBuffer final {
+public:
+ static_assert(
+ std::is_trivially_constructible_v<T>,
+ "T must be trivially constructible, as non-trivial constructors will not be executed "
+ "with the current allocator");
+
+ constexpr VirtualBuffer() = default;
+ explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
+ base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
+ }
+
+ ~VirtualBuffer() noexcept {
+ FreeMemoryPages(base_ptr, alloc_size);
+ }
+
+ VirtualBuffer(const VirtualBuffer&) = delete;
+ VirtualBuffer& operator=(const VirtualBuffer&) = delete;
+
+ VirtualBuffer(VirtualBuffer&& other) noexcept
+ : alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr),
+ nullptr} {}
+
+ VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
+ alloc_size = std::exchange(other.alloc_size, 0);
+ base_ptr = std::exchange(other.base_ptr, nullptr);
+ return *this;
+ }
+
+ void resize(std::size_t count) {
+ const auto new_size = count * sizeof(T);
+ if (new_size == alloc_size) {
+ return;
+ }
+
+ FreeMemoryPages(base_ptr, alloc_size);
+
+ alloc_size = new_size;
+ base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
+ }
+
+ [[nodiscard]] constexpr const T& operator[](std::size_t index) const {
+ return base_ptr[index];
+ }
+
+ [[nodiscard]] constexpr T& operator[](std::size_t index) {
+ return base_ptr[index];
+ }
+
+ [[nodiscard]] constexpr T* data() {
+ return base_ptr;
+ }
+
+ [[nodiscard]] constexpr const T* data() const {
+ return base_ptr;
+ }
+
+ [[nodiscard]] constexpr std::size_t size() const {
+ return alloc_size / sizeof(T);
+ }
+
+private:
+ std::size_t alloc_size{};
+ T* base_ptr{};
+};
+
+} // namespace Common
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
new file mode 100644
index 000000000..452a2837e
--- /dev/null
+++ b/src/common/wall_clock.cpp
@@ -0,0 +1,91 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/uint128.h"
+#include "common/wall_clock.h"
+
+#ifdef ARCHITECTURE_x86_64
+#include "common/x64/cpu_detect.h"
+#include "common/x64/native_clock.h"
+#endif
+
+namespace Common {
+
+using base_timer = std::chrono::steady_clock;
+using base_time_point = std::chrono::time_point<base_timer>;
+
+class StandardWallClock final : public WallClock {
+public:
+ StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency)
+ : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) {
+ start_time = base_timer::now();
+ }
+
+ std::chrono::nanoseconds GetTimeNS() override {
+ base_time_point current = base_timer::now();
+ auto elapsed = current - start_time;
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
+ }
+
+ std::chrono::microseconds GetTimeUS() override {
+ base_time_point current = base_timer::now();
+ auto elapsed = current - start_time;
+ return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
+ }
+
+ std::chrono::milliseconds GetTimeMS() override {
+ base_time_point current = base_timer::now();
+ auto elapsed = current - start_time;
+ return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
+ }
+
+ u64 GetClockCycles() override {
+ std::chrono::nanoseconds time_now = GetTimeNS();
+ const u128 temporary =
+ Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
+ return Common::Divide128On32(temporary, 1000000000).first;
+ }
+
+ u64 GetCPUCycles() override {
+ std::chrono::nanoseconds time_now = GetTimeNS();
+ const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
+ return Common::Divide128On32(temporary, 1000000000).first;
+ }
+
+ void Pause([[maybe_unused]] bool is_paused) override {
+ // Do nothing in this clock type.
+ }
+
+private:
+ base_time_point start_time;
+};
+
+#ifdef ARCHITECTURE_x86_64
+
+std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
+ u32 emulated_clock_frequency) {
+ const auto& caps = GetCPUCaps();
+ u64 rtsc_frequency = 0;
+ if (caps.invariant_tsc) {
+ rtsc_frequency = EstimateRDTSCFrequency();
+ }
+ if (rtsc_frequency == 0) {
+ return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
+ emulated_clock_frequency);
+ } else {
+ return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
+ rtsc_frequency);
+ }
+}
+
+#else
+
+std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
+ u32 emulated_clock_frequency) {
+ return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
+}
+
+#endif
+
+} // namespace Common
diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h
new file mode 100644
index 000000000..bc7adfbf8
--- /dev/null
+++ b/src/common/wall_clock.h
@@ -0,0 +1,55 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+class WallClock {
+public:
+ virtual ~WallClock() = default;
+
+ /// Returns current wall time in nanoseconds
+ [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
+
+ /// Returns current wall time in microseconds
+ [[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
+
+ /// Returns current wall time in milliseconds
+ [[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
+
+ /// Returns current wall time in emulated clock cycles
+ [[nodiscard]] virtual u64 GetClockCycles() = 0;
+
+ /// Returns current wall time in emulated cpu cycles
+ [[nodiscard]] virtual u64 GetCPUCycles() = 0;
+
+ virtual void Pause(bool is_paused) = 0;
+
+ /// Tells if the wall clock, uses the host CPU's hardware clock
+ [[nodiscard]] bool IsNative() const {
+ return is_native;
+ }
+
+protected:
+ WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
+ : emulated_cpu_frequency{emulated_cpu_frequency},
+ emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
+
+ u64 emulated_cpu_frequency;
+ u64 emulated_clock_frequency;
+
+private:
+ bool is_native;
+};
+
+[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
+ u32 emulated_clock_frequency);
+
+} // namespace Common
diff --git a/src/common/web_result.h b/src/common/web_result.h
deleted file mode 100644
index 8bfa2141d..000000000
--- a/src/common/web_result.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include "common/common_types.h"
-
-namespace Common {
-struct WebResult {
- enum class Code : u32 {
- Success,
- InvalidURL,
- CredentialsMissing,
- LibError,
- HttpError,
- WrongContent,
- NoWebservice,
- };
- Code result_code;
- std::string result_string;
- std::string returned_data;
-};
-} // namespace Common
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp
index c9349a6b4..fccd2eee5 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/x64/cpu_detect.cpp
@@ -62,6 +62,17 @@ static CPUCaps Detect() {
std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int));
std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int));
std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int));
+ if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69)
+ caps.manufacturer = Manufacturer::Intel;
+ else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65)
+ caps.manufacturer = Manufacturer::AMD;
+ else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e)
+ caps.manufacturer = Manufacturer::Hygon;
+ else
+ caps.manufacturer = Manufacturer::Unknown;
+
+ u32 family = {};
+ u32 model = {};
__cpuid(cpu_id, 0x80000000);
@@ -73,6 +84,14 @@ static CPUCaps Detect() {
// Detect family and other miscellaneous features
if (max_std_fn >= 1) {
__cpuid(cpu_id, 0x00000001);
+ family = (cpu_id[0] >> 8) & 0xf;
+ model = (cpu_id[0] >> 4) & 0xf;
+ if (family == 0xf) {
+ family += (cpu_id[0] >> 20) & 0xff;
+ }
+ if (family >= 6) {
+ model += ((cpu_id[0] >> 16) & 0xf) << 4;
+ }
if ((cpu_id[3] >> 25) & 1)
caps.sse = true;
@@ -110,6 +129,11 @@ static CPUCaps Detect() {
caps.bmi1 = true;
if ((cpu_id[1] >> 8) & 1)
caps.bmi2 = true;
+ // Checks for AVX512F, AVX512CD, AVX512VL, AVX512DQ, AVX512BW (Intel Skylake-X/SP)
+ if ((cpu_id[1] >> 16) & 1 && (cpu_id[1] >> 28) & 1 && (cpu_id[1] >> 31) & 1 &&
+ (cpu_id[1] >> 17) & 1 && (cpu_id[1] >> 30) & 1) {
+ caps.avx512 = caps.avx2;
+ }
}
}
@@ -130,6 +154,20 @@ static CPUCaps Detect() {
caps.fma4 = true;
}
+ if (max_ex_fn >= 0x80000007) {
+ __cpuid(cpu_id, 0x80000007);
+ if (cpu_id[3] & (1 << 8)) {
+ caps.invariant_tsc = true;
+ }
+ }
+
+ if (max_std_fn >= 0x16) {
+ __cpuid(cpu_id, 0x16);
+ caps.base_frequency = cpu_id[0];
+ caps.max_frequency = cpu_id[1];
+ caps.bus_frequency = cpu_id[2];
+ }
+
return caps;
}
diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h
index 20f2ba234..e3b63302e 100644
--- a/src/common/x64/cpu_detect.h
+++ b/src/common/x64/cpu_detect.h
@@ -6,8 +6,16 @@
namespace Common {
+enum class Manufacturer : u32 {
+ Intel = 0,
+ AMD = 1,
+ Hygon = 2,
+ Unknown = 3,
+};
+
/// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps {
+ Manufacturer manufacturer;
char cpu_string[0x21];
char brand_string[0x41];
bool sse;
@@ -19,11 +27,16 @@ struct CPUCaps {
bool lzcnt;
bool avx;
bool avx2;
+ bool avx512;
bool bmi1;
bool bmi2;
bool fma;
bool fma4;
bool aes;
+ bool invariant_tsc;
+ u32 base_frequency;
+ u32 max_frequency;
+ u32 bus_frequency;
};
/**
diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp
new file mode 100644
index 000000000..424b39b1f
--- /dev/null
+++ b/src/common/x64/native_clock.cpp
@@ -0,0 +1,103 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <mutex>
+#include <thread>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#else
+#include <x86intrin.h>
+#endif
+
+#include "common/uint128.h"
+#include "common/x64/native_clock.h"
+
+namespace Common {
+
+u64 EstimateRDTSCFrequency() {
+ const auto milli_10 = std::chrono::milliseconds{10};
+ // get current time
+ _mm_mfence();
+ const u64 tscStart = __rdtsc();
+ const auto startTime = std::chrono::high_resolution_clock::now();
+ // wait roughly 3 seconds
+ while (true) {
+ auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::high_resolution_clock::now() - startTime);
+ if (milli.count() >= 3000)
+ break;
+ std::this_thread::sleep_for(milli_10);
+ }
+ const auto endTime = std::chrono::high_resolution_clock::now();
+ _mm_mfence();
+ const u64 tscEnd = __rdtsc();
+ // calculate difference
+ const u64 timer_diff =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
+ const u64 tsc_diff = tscEnd - tscStart;
+ const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
+ return tsc_freq;
+}
+
+namespace X64 {
+NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
+ u64 rtsc_frequency)
+ : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
+ rtsc_frequency} {
+ _mm_mfence();
+ last_measure = __rdtsc();
+ accumulated_ticks = 0U;
+}
+
+u64 NativeClock::GetRTSC() {
+ std::scoped_lock scope{rtsc_serialize};
+ _mm_mfence();
+ const u64 current_measure = __rdtsc();
+ u64 diff = current_measure - last_measure;
+ diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
+ if (current_measure > last_measure) {
+ last_measure = current_measure;
+ }
+ accumulated_ticks += diff;
+ /// The clock cannot be more precise than the guest timer, remove the lower bits
+ return accumulated_ticks & inaccuracy_mask;
+}
+
+void NativeClock::Pause(bool is_paused) {
+ if (!is_paused) {
+ _mm_mfence();
+ last_measure = __rdtsc();
+ }
+}
+
+std::chrono::nanoseconds NativeClock::GetTimeNS() {
+ const u64 rtsc_value = GetRTSC();
+ return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
+}
+
+std::chrono::microseconds NativeClock::GetTimeUS() {
+ const u64 rtsc_value = GetRTSC();
+ return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
+}
+
+std::chrono::milliseconds NativeClock::GetTimeMS() {
+ const u64 rtsc_value = GetRTSC();
+ return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
+}
+
+u64 NativeClock::GetClockCycles() {
+ const u64 rtsc_value = GetRTSC();
+ return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
+}
+
+u64 NativeClock::GetCPUCycles() {
+ const u64 rtsc_value = GetRTSC();
+ return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
+}
+
+} // namespace X64
+
+} // namespace Common
diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h
new file mode 100644
index 000000000..97aab6ac9
--- /dev/null
+++ b/src/common/x64/native_clock.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+
+#include "common/spin_lock.h"
+#include "common/wall_clock.h"
+
+namespace Common {
+
+namespace X64 {
+class NativeClock final : public WallClock {
+public:
+ NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency);
+
+ std::chrono::nanoseconds GetTimeNS() override;
+
+ std::chrono::microseconds GetTimeUS() override;
+
+ std::chrono::milliseconds GetTimeMS() override;
+
+ u64 GetClockCycles() override;
+
+ u64 GetCPUCycles() override;
+
+ void Pause(bool is_paused) override;
+
+private:
+ u64 GetRTSC();
+
+ /// value used to reduce the native clocks accuracy as some apss rely on
+ /// undefined behavior where the level of accuracy in the clock shouldn't
+ /// be higher.
+ static constexpr u64 inaccuracy_mask = ~(UINT64_C(0x400) - 1);
+
+ SpinLock rtsc_serialize{};
+ u64 last_measure{};
+ u64 accumulated_ticks{};
+ u64 rtsc_frequency;
+};
+} // namespace X64
+
+u64 EstimateRDTSCFrequency();
+
+} // namespace Common
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
new file mode 100644
index 000000000..26e4bfda5
--- /dev/null
+++ b/src/common/x64/xbyak_abi.h
@@ -0,0 +1,229 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <bitset>
+#include <initializer_list>
+#include <xbyak.h>
+#include "common/assert.h"
+
+namespace Common::X64 {
+
+constexpr std::size_t RegToIndex(const Xbyak::Reg& reg) {
+ using Kind = Xbyak::Reg::Kind;
+ ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
+ "RegSet only support GPRs and XMM registers.");
+ ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15.");
+ return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16);
+}
+
+constexpr Xbyak::Reg64 IndexToReg64(std::size_t reg_index) {
+ ASSERT(reg_index < 16);
+ return Xbyak::Reg64(static_cast<int>(reg_index));
+}
+
+constexpr Xbyak::Xmm IndexToXmm(std::size_t reg_index) {
+ ASSERT(reg_index >= 16 && reg_index < 32);
+ return Xbyak::Xmm(static_cast<int>(reg_index - 16));
+}
+
+constexpr Xbyak::Reg IndexToReg(std::size_t reg_index) {
+ if (reg_index < 16) {
+ return IndexToReg64(reg_index);
+ } else {
+ return IndexToXmm(reg_index);
+ }
+}
+
+inline std::bitset<32> BuildRegSet(std::initializer_list<Xbyak::Reg> regs) {
+ std::bitset<32> bits;
+ for (const Xbyak::Reg& reg : regs) {
+ bits[RegToIndex(reg)] = true;
+ }
+ return bits;
+}
+
+constexpr inline std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
+constexpr inline std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
+
+#ifdef _WIN32
+
+// Microsoft x64 ABI
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
+
+const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
+ // GPRs
+ Xbyak::util::rcx,
+ Xbyak::util::rdx,
+ Xbyak::util::r8,
+ Xbyak::util::r9,
+ Xbyak::util::r10,
+ Xbyak::util::r11,
+ // XMMs
+ Xbyak::util::xmm0,
+ Xbyak::util::xmm1,
+ Xbyak::util::xmm2,
+ Xbyak::util::xmm3,
+ Xbyak::util::xmm4,
+ Xbyak::util::xmm5,
+});
+
+const std::bitset<32> ABI_ALL_CALLEE_SAVED = BuildRegSet({
+ // GPRs
+ Xbyak::util::rbx,
+ Xbyak::util::rsi,
+ Xbyak::util::rdi,
+ Xbyak::util::rbp,
+ Xbyak::util::r12,
+ Xbyak::util::r13,
+ Xbyak::util::r14,
+ Xbyak::util::r15,
+ // XMMs
+ Xbyak::util::xmm6,
+ Xbyak::util::xmm7,
+ Xbyak::util::xmm8,
+ Xbyak::util::xmm9,
+ Xbyak::util::xmm10,
+ Xbyak::util::xmm11,
+ Xbyak::util::xmm12,
+ Xbyak::util::xmm13,
+ Xbyak::util::xmm14,
+ Xbyak::util::xmm15,
+});
+
+constexpr size_t ABI_SHADOW_SPACE = 0x20;
+
+#else
+
+// System V x86-64 ABI
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
+
+const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
+ // GPRs
+ Xbyak::util::rcx,
+ Xbyak::util::rdx,
+ Xbyak::util::rdi,
+ Xbyak::util::rsi,
+ Xbyak::util::r8,
+ Xbyak::util::r9,
+ Xbyak::util::r10,
+ Xbyak::util::r11,
+ // XMMs
+ Xbyak::util::xmm0,
+ Xbyak::util::xmm1,
+ Xbyak::util::xmm2,
+ Xbyak::util::xmm3,
+ Xbyak::util::xmm4,
+ Xbyak::util::xmm5,
+ Xbyak::util::xmm6,
+ Xbyak::util::xmm7,
+ Xbyak::util::xmm8,
+ Xbyak::util::xmm9,
+ Xbyak::util::xmm10,
+ Xbyak::util::xmm11,
+ Xbyak::util::xmm12,
+ Xbyak::util::xmm13,
+ Xbyak::util::xmm14,
+ Xbyak::util::xmm15,
+});
+
+const std::bitset<32> ABI_ALL_CALLEE_SAVED = BuildRegSet({
+ // GPRs
+ Xbyak::util::rbx,
+ Xbyak::util::rbp,
+ Xbyak::util::r12,
+ Xbyak::util::r13,
+ Xbyak::util::r14,
+ Xbyak::util::r15,
+});
+
+constexpr size_t ABI_SHADOW_SPACE = 0;
+
+#endif
+
+struct ABIFrameInfo {
+ s32 subtraction;
+ s32 xmm_offset;
+};
+
+inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<32> regs, size_t rsp_alignment,
+ size_t needed_frame_size) {
+ const auto count = (regs & ABI_ALL_GPRS).count();
+ rsp_alignment -= count * 8;
+ size_t subtraction = 0;
+ const auto xmm_count = (regs & ABI_ALL_XMMS).count();
+ if (xmm_count) {
+ // If we have any XMMs to save, we must align the stack here.
+ subtraction = rsp_alignment & 0xF;
+ }
+ subtraction += 0x10 * xmm_count;
+ size_t xmm_base_subtraction = subtraction;
+ subtraction += needed_frame_size;
+ subtraction += ABI_SHADOW_SPACE;
+ // Final alignment.
+ rsp_alignment -= subtraction;
+ subtraction += rsp_alignment & 0xF;
+
+ return ABIFrameInfo{static_cast<s32>(subtraction),
+ static_cast<s32>(subtraction - xmm_base_subtraction)};
+}
+
+inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
+ size_t rsp_alignment, size_t needed_frame_size = 0) {
+ auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
+
+ for (std::size_t i = 0; i < regs.size(); ++i) {
+ if (regs[i] && ABI_ALL_GPRS[i]) {
+ code.push(IndexToReg64(i));
+ }
+ }
+
+ if (frame_info.subtraction != 0) {
+ code.sub(code.rsp, frame_info.subtraction);
+ }
+
+ for (std::size_t i = 0; i < regs.size(); ++i) {
+ if (regs[i] && ABI_ALL_XMMS[i]) {
+ code.movaps(code.xword[code.rsp + frame_info.xmm_offset], IndexToXmm(i));
+ frame_info.xmm_offset += 0x10;
+ }
+ }
+
+ return ABI_SHADOW_SPACE;
+}
+
+inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
+ size_t rsp_alignment, size_t needed_frame_size = 0) {
+ auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
+
+ for (std::size_t i = 0; i < regs.size(); ++i) {
+ if (regs[i] && ABI_ALL_XMMS[i]) {
+ code.movaps(IndexToXmm(i), code.xword[code.rsp + frame_info.xmm_offset]);
+ frame_info.xmm_offset += 0x10;
+ }
+ }
+
+ if (frame_info.subtraction != 0) {
+ code.add(code.rsp, frame_info.subtraction);
+ }
+
+ // GPRs need to be popped in reverse order
+ for (std::size_t j = 0; j < regs.size(); ++j) {
+ const std::size_t i = regs.size() - j - 1;
+ if (regs[i] && ABI_ALL_GPRS[i]) {
+ code.pop(IndexToReg64(i));
+ }
+ }
+}
+
+} // namespace Common::X64
diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h
new file mode 100644
index 000000000..df17f8cbe
--- /dev/null
+++ b/src/common/x64/xbyak_util.h
@@ -0,0 +1,47 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+#include <xbyak.h>
+#include "common/x64/xbyak_abi.h"
+
+namespace Common::X64 {
+
+// Constants for use with cmpps/cmpss
+enum {
+ CMP_EQ = 0,
+ CMP_LT = 1,
+ CMP_LE = 2,
+ CMP_UNORD = 3,
+ CMP_NEQ = 4,
+ CMP_NLT = 5,
+ CMP_NLE = 6,
+ CMP_ORD = 7,
+};
+
+constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) {
+ const u64 distance = target - (ref + 5);
+ return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
+}
+
+inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
+ return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target);
+}
+
+template <typename T>
+inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
+ static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
+ size_t addr = reinterpret_cast<size_t>(f);
+ if (IsWithin2G(code, addr)) {
+ code.call(f);
+ } else {
+ // ABI_RETURN is a safe temp register to use before a call
+ code.mov(ABI_RETURN, addr);
+ code.call(ABI_RETURN);
+ }
+}
+
+} // namespace Common::X64
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
index 978526492..5f45459da 100644
--- a/src/common/zstd_compression.cpp
+++ b/src/common/zstd_compression.cpp
@@ -5,7 +5,6 @@
#include <algorithm>
#include <zstd.h>
-#include "common/assert.h"
#include "common/zstd_compression.h"
namespace Common::Compression {
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
index e9de941c8..c26a30ab9 100644
--- a/src/common/zstd_compression.h
+++ b/src/common/zstd_compression.h
@@ -13,24 +13,25 @@ namespace Common::Compression {
/**
* Compresses a source memory region with Zstandard and returns the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
- * @param compression_level the used compression level. Should be between 1 and 22.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
+ * @param compression_level The used compression level. Should be between 1 and 22.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
+[[nodiscard]] std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size,
+ s32 compression_level);
/**
* Compresses a source memory region with Zstandard with the default compression level and returns
* the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
/**
* Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
@@ -39,6 +40,6 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz
*
* @return the decompressed data.
*/
-std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
+[[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
} // namespace Common::Compression \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 66497a386..e370fd225 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,22 +1,22 @@
-if (YUZU_ENABLE_BOXCAT)
- set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
-else()
- set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
-endif()
-
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
+ arm/cpu_interrupt_handler.cpp
+ arm/cpu_interrupt_handler.h
+ arm/dynarmic/arm_dynarmic_32.cpp
+ arm/dynarmic/arm_dynarmic_32.h
+ arm/dynarmic/arm_dynarmic_64.cpp
+ arm/dynarmic/arm_dynarmic_64.h
+ arm/dynarmic/arm_dynarmic_cp15.cpp
+ arm/dynarmic/arm_dynarmic_cp15.h
+ arm/dynarmic/arm_exclusive_monitor.cpp
+ arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp
arm/exclusive_monitor.h
- arm/unicorn/arm_unicorn.cpp
- arm/unicorn/arm_unicorn.h
constants.cpp
constants.h
core.cpp
core.h
- core_manager.cpp
- core_manager.h
core_timing.cpp
core_timing.h
core_timing_util.cpp
@@ -35,6 +35,8 @@ add_library(core STATIC
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
crypto/xts_encryption_layer.h
+ device_memory.cpp
+ device_memory.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
@@ -116,6 +118,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
+ frontend/applets/controller.cpp
+ frontend/applets/controller.h
frontend/applets/error.cpp
frontend/applets/error.h
frontend/applets/general_frontend.cpp
@@ -152,12 +156,30 @@ add_library(core STATIC
hle/kernel/hle_ipc.h
hle/kernel/kernel.cpp
hle/kernel/kernel.h
+ hle/kernel/memory/address_space_info.cpp
+ hle/kernel/memory/address_space_info.h
+ hle/kernel/memory/memory_block.h
+ hle/kernel/memory/memory_block_manager.cpp
+ hle/kernel/memory/memory_block_manager.h
+ hle/kernel/memory/memory_layout.h
+ hle/kernel/memory/memory_manager.cpp
+ hle/kernel/memory/memory_manager.h
+ hle/kernel/memory/memory_types.h
+ hle/kernel/memory/page_linked_list.h
+ hle/kernel/memory/page_heap.cpp
+ hle/kernel/memory/page_heap.h
+ hle/kernel/memory/page_table.cpp
+ hle/kernel/memory/page_table.h
+ hle/kernel/memory/slab_heap.h
+ hle/kernel/memory/system_control.cpp
+ hle/kernel/memory/system_control.h
hle/kernel/mutex.cpp
hle/kernel/mutex.h
hle/kernel/object.cpp
hle/kernel/object.h
hle/kernel/physical_core.cpp
hle/kernel/physical_core.h
+ hle/kernel/physical_memory.h
hle/kernel/process.cpp
hle/kernel/process.h
hle/kernel/process_capability.cpp
@@ -178,6 +200,7 @@ add_library(core STATIC
hle/kernel/shared_memory.h
hle/kernel/svc.cpp
hle/kernel/svc.h
+ hle/kernel/svc_types.h
hle/kernel/svc_wrap.h
hle/kernel/synchronization_object.cpp
hle/kernel/synchronization_object.h
@@ -189,8 +212,6 @@ add_library(core STATIC
hle/kernel/time_manager.h
hle/kernel/transfer_memory.cpp
hle/kernel/transfer_memory.h
- hle/kernel/vm_manager.cpp
- hle/kernel/vm_manager.h
hle/kernel/writable_event.cpp
hle/kernel/writable_event.h
hle/lock.cpp
@@ -217,6 +238,8 @@ add_library(core STATIC
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
+ hle/service/am/applets/controller.cpp
+ hle/service/am/applets/controller.h
hle/service/am/applets/error.cpp
hle/service/am/applets/error.h
hle/service/am/applets/general_backend.cpp
@@ -272,7 +295,6 @@ add_library(core STATIC
hle/service/audio/hwopus.h
hle/service/bcat/backend/backend.cpp
hle/service/bcat/backend/backend.h
- ${BCAT_BOXCAT_ADDITIONAL_SOURCES}
hle/service/bcat/bcat.cpp
hle/service/bcat/bcat.h
hle/service/bcat/module.cpp
@@ -372,10 +394,13 @@ add_library(core STATIC
hle/service/lm/manager.h
hle/service/mig/mig.cpp
hle/service/mig/mig.h
+ hle/service/mii/manager.cpp
+ hle/service/mii/manager.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
- hle/service/mii/mii_manager.cpp
- hle/service/mii/mii_manager.h
+ hle/service/mii/raw_data.cpp
+ hle/service/mii/raw_data.h
+ hle/service/mii/types.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
hle/service/ncm/ncm.cpp
@@ -412,6 +437,8 @@ add_library(core STATIC
hle/service/nvdrv/devices/nvhost_gpu.h
hle/service/nvdrv/devices/nvhost_nvdec.cpp
hle/service/nvdrv/devices/nvhost_nvdec.h
+ hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+ hle/service/nvdrv/devices/nvhost_nvdec_common.h
hle/service/nvdrv/devices/nvhost_nvjpg.cpp
hle/service/nvdrv/devices/nvhost_nvjpg.h
hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -425,10 +452,14 @@ add_library(core STATIC
hle/service/nvdrv/nvdrv.h
hle/service/nvdrv/nvmemp.cpp
hle/service/nvdrv/nvmemp.h
+ hle/service/nvdrv/syncpoint_manager.cpp
+ hle/service/nvdrv/syncpoint_manager.h
hle/service/nvflinger/buffer_queue.cpp
hle/service/nvflinger/buffer_queue.h
hle/service/nvflinger/nvflinger.cpp
hle/service/nvflinger/nvflinger.h
+ hle/service/olsc/olsc.cpp
+ hle/service/olsc/olsc.h
hle/service/pcie/pcie.cpp
hle/service/pcie/pcie.h
hle/service/pctl/module.cpp
@@ -461,6 +492,7 @@ add_library(core STATIC
hle/service/sm/controller.h
hle/service/sm/sm.cpp
hle/service/sm/sm.h
+ hle/service/sockets/blocking_worker.h
hle/service/sockets/bsd.cpp
hle/service/sockets/bsd.h
hle/service/sockets/ethc.cpp
@@ -471,6 +503,8 @@ add_library(core STATIC
hle/service/sockets/sfdnsres.h
hle/service/sockets/sockets.cpp
hle/service/sockets/sockets.h
+ hle/service/sockets/sockets_translate.cpp
+ hle/service/sockets/sockets_translate.h
hle/service/spl/csrng.cpp
hle/service/spl/csrng.h
hle/service/spl/module.cpp
@@ -556,6 +590,9 @@ add_library(core STATIC
memory/dmnt_cheat_vm.h
memory.cpp
memory.h
+ network/network.cpp
+ network/network.h
+ network/sockets.h
perf_stats.cpp
perf_stats.h
reporter.cpp
@@ -568,6 +605,13 @@ add_library(core STATIC
tools/freezer.h
)
+if (YUZU_ENABLE_BOXCAT)
+ target_sources(core PRIVATE
+ hle/service/bcat/backend/boxcat.cpp
+ hle/service/bcat/backend/boxcat.h
+ )
+endif()
+
if (MSVC)
target_compile_options(core PRIVATE
# 'expression' : signed/unsigned mismatch
@@ -583,19 +627,30 @@ if (MSVC)
# 'context' : truncation from 'type1' to 'type2'
/we4305
)
+else()
+ target_compile_options(core PRIVATE
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=reorder
+ -Werror=sign-compare
+ -Werror=unused-variable
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+
+ -Wno-sign-conversion
+ )
endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus zip)
if (YUZU_ENABLE_BOXCAT)
- get_directory_property(OPENSSL_LIBS
- DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
- DEFINITION OPENSSL_LIBS)
- target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
- target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
+ target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT)
+ target_link_libraries(core PRIVATE httplib nlohmann_json::nlohmann_json)
endif()
if (ENABLE_WEB_SERVICE)
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 7e846ddd5..0951e1976 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -60,7 +60,7 @@ static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size.");
using Symbols = std::vector<std::pair<ELFSymbol, std::string>>;
-Symbols GetSymbols(VAddr text_offset, Memory::Memory& memory) {
+Symbols GetSymbols(VAddr text_offset, Core::Memory::Memory& memory) {
const auto mod_offset = text_offset + memory.Read32(text_offset + 4);
if (mod_offset < text_offset || (mod_offset & 0b11) != 0 ||
@@ -123,7 +123,7 @@ Symbols GetSymbols(VAddr text_offset, Memory::Memory& memory) {
std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) {
const auto iter =
std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) {
- const auto& [symbol, name] = pair;
+ const auto& symbol = pair.first;
const auto end_address = symbol.value + symbol.size;
return func_address >= symbol.value && func_address < end_address;
});
@@ -139,6 +139,71 @@ std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_addr
constexpr u64 SEGMENT_BASE = 0x7100000000ull;
+std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
+ System& system, const ThreadContext64& ctx) {
+ std::vector<BacktraceEntry> out;
+ auto& memory = system.Memory();
+
+ auto fp = ctx.cpu_registers[29];
+ auto lr = ctx.cpu_registers[30];
+ while (true) {
+ out.push_back({
+ .module = "",
+ .address = 0,
+ .original_address = lr,
+ .offset = 0,
+ .name = {},
+ });
+
+ if (fp == 0) {
+ break;
+ }
+
+ lr = memory.Read64(fp + 8) - 4;
+ fp = memory.Read64(fp);
+ }
+
+ std::map<VAddr, std::string> modules;
+ auto& loader{system.GetAppLoader()};
+ if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
+ return {};
+ }
+
+ std::map<std::string, Symbols> symbols;
+ for (const auto& module : modules) {
+ symbols.insert_or_assign(module.second, GetSymbols(module.first, memory));
+ }
+
+ for (auto& entry : out) {
+ VAddr base = 0;
+ for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
+ const auto& module{*iter};
+ if (entry.original_address >= module.first) {
+ entry.module = module.second;
+ base = module.first;
+ break;
+ }
+ }
+
+ entry.offset = entry.original_address - base;
+ entry.address = SEGMENT_BASE + entry.offset;
+
+ if (entry.module.empty())
+ entry.module = "unknown";
+
+ const auto symbol_set = symbols.find(entry.module);
+ if (symbol_set != symbols.end()) {
+ const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
+ if (symbol.has_value()) {
+ // TODO(DarkLordZach): Add demangling of symbol names.
+ entry.name = *symbol;
+ }
+ }
+ }
+
+ return out;
+}
+
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
std::vector<BacktraceEntry> out;
auto& memory = system.Memory();
@@ -146,7 +211,7 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
auto fp = GetReg(29);
auto lr = GetReg(30);
while (true) {
- out.push_back({"", 0, lr, 0});
+ out.push_back({"", 0, lr, 0, ""});
if (!fp) {
break;
}
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 57eae839e..1f24051e4 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -7,6 +7,7 @@
#include <array>
#include <vector>
#include "common/common_types.h"
+#include "core/hardware_properties.h"
namespace Common {
struct PageTable;
@@ -18,36 +19,40 @@ enum class VMAPermission : u8;
namespace Core {
class System;
+class CPUInterruptHandler;
+
+using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
/// Generic ARMv8 CPU interface
class ARM_Interface : NonCopyable {
public:
- explicit ARM_Interface(System& system_) : system{system_} {}
+ explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers, bool uses_wall_clock)
+ : system{system_}, interrupt_handlers{interrupt_handlers}, uses_wall_clock{
+ uses_wall_clock} {}
virtual ~ARM_Interface() = default;
struct ThreadContext32 {
- std::array<u32, 16> cpu_registers;
- u32 cpsr;
- std::array<u8, 4> padding;
- std::array<u64, 32> fprs;
- u32 fpscr;
- u32 fpexc;
- u32 tpidr;
+ std::array<u32, 16> cpu_registers{};
+ std::array<u32, 64> extension_registers{};
+ u32 cpsr{};
+ u32 fpscr{};
+ u32 fpexc{};
+ u32 tpidr{};
};
// Internally within the kernel, it expects the AArch32 version of the
// thread context to be 344 bytes in size.
- static_assert(sizeof(ThreadContext32) == 0x158);
+ static_assert(sizeof(ThreadContext32) == 0x150);
struct ThreadContext64 {
- std::array<u64, 31> cpu_registers;
- u64 sp;
- u64 pc;
- u32 pstate;
- std::array<u8, 4> padding;
- std::array<u128, 32> vector_registers;
- u32 fpcr;
- u32 fpsr;
- u64 tpidr;
+ std::array<u64, 31> cpu_registers{};
+ u64 sp{};
+ u64 pc{};
+ u32 pstate{};
+ std::array<u8, 4> padding{};
+ std::array<u128, 32> vector_registers{};
+ u32 fpcr{};
+ u32 fpsr{};
+ u64 tpidr{};
};
// Internally within the kernel, it expects the AArch64 version of the
// thread context to be 800 bytes in size.
@@ -143,6 +148,8 @@ public:
*/
virtual void SetTPIDR_EL0(u64 value) = 0;
+ virtual void ChangeProcessorID(std::size_t new_core_id) = 0;
+
virtual void SaveContext(ThreadContext32& ctx) = 0;
virtual void SaveContext(ThreadContext64& ctx) = 0;
virtual void LoadContext(const ThreadContext32& ctx) = 0;
@@ -162,6 +169,9 @@ public:
std::string name;
};
+ static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
+ const ThreadContext64& ctx);
+
std::vector<BacktraceEntry> GetBacktrace() const;
/// fp (= r29) points to the last frame record.
@@ -175,6 +185,8 @@ public:
protected:
/// System context that this ARM interface is running under.
System& system;
+ CPUInterrupts& interrupt_handlers;
+ bool uses_wall_clock;
};
} // namespace Core
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
new file mode 100644
index 000000000..9c8898700
--- /dev/null
+++ b/src/core/arm/cpu_interrupt_handler.cpp
@@ -0,0 +1,25 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/thread.h"
+#include "core/arm/cpu_interrupt_handler.h"
+
+namespace Core {
+
+CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {}
+
+CPUInterruptHandler::~CPUInterruptHandler() = default;
+
+void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
+ if (is_interrupted_) {
+ interrupt_event->Set();
+ }
+ is_interrupted = is_interrupted_;
+}
+
+void CPUInterruptHandler::AwaitInterrupt() {
+ interrupt_event->Wait();
+}
+
+} // namespace Core
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
new file mode 100644
index 000000000..c20c280f1
--- /dev/null
+++ b/src/core/arm/cpu_interrupt_handler.h
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+namespace Common {
+class Event;
+}
+
+namespace Core {
+
+class CPUInterruptHandler {
+public:
+ CPUInterruptHandler();
+ ~CPUInterruptHandler();
+
+ CPUInterruptHandler(const CPUInterruptHandler&) = delete;
+ CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
+
+ CPUInterruptHandler(CPUInterruptHandler&&) = delete;
+ CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete;
+
+ bool IsInterrupted() const {
+ return is_interrupted;
+ }
+
+ void SetInterrupt(bool is_interrupted);
+
+ void AwaitInterrupt();
+
+private:
+ std::unique_ptr<Common::Event> interrupt_event;
+ std::atomic_bool is_interrupted{false};
+};
+
+} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index 187a972ac..6dc03f3b1 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -7,15 +7,18 @@
#include <dynarmic/A32/a32.h>
#include <dynarmic/A32/config.h>
#include <dynarmic/A32/context.h>
-#include "common/microprofile.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/page_table.h"
+#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
-#include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
+#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
+#include "core/settings.h"
namespace Core {
@@ -49,8 +52,22 @@ public:
parent.system.Memory().Write64(vaddr, value);
}
+ bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
+ return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
+ return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
+ return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
+ return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
+ }
+
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc,
+ MemoryReadCode(pc));
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
@@ -61,36 +78,46 @@ public:
case Dynarmic::A32::Exception::Breakpoint:
break;
}
- LOG_CRITICAL(HW_GPU, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
+ LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
UNIMPLEMENTED();
}
void CallSVC(u32 swi) override {
- Kernel::CallSVC(parent.system, swi);
+ Kernel::Svc::Call(parent.system, swi);
}
void AddTicks(u64 ticks) override {
+ if (parent.uses_wall_clock) {
+ return;
+ }
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
- u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
+ u64 amortized_ticks =
+ (ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
parent.system.CoreTiming().AddTicks(amortized_ticks);
num_interpreted_instructions = 0;
}
+
u64 GetTicksRemaining() override {
- return std::max(parent.system.CoreTiming().GetDowncount(), {});
+ if (parent.uses_wall_clock) {
+ if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
+ return minimum_run_cycles;
+ }
+ return 0U;
+ }
+ return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
ARM_Dynarmic_32& parent;
std::size_t num_interpreted_instructions{};
- u64 tpidrro_el0{};
- u64 tpidr_el0{};
+ static constexpr u64 minimum_run_cycles = 1000U;
};
std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& page_table,
@@ -99,26 +126,79 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
config.callbacks = cb.get();
// TODO(bunnei): Implement page table for 32-bit
// config.page_table = &page_table.pointers;
- config.coprocessors[15] = std::make_shared<DynarmicCP15>((u32*)&CP15_regs[0]);
+ config.coprocessors[15] = cp15;
config.define_unpredictable_behaviour = true;
+ static constexpr std::size_t PAGE_BITS = 12;
+ static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS);
+ config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
+ page_table.pointers.data());
+ config.absolute_offset_page_table = true;
+ config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
+ config.only_detect_misalignment_via_page_table_on_page_boundary = true;
+
+ // Multi-process state
+ config.processor_id = core_index;
+ config.global_monitor = &exclusive_monitor.monitor;
+
+ // Timing
+ config.wall_clock_cntpct = uses_wall_clock;
+
+ // Safe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
+ if (!Settings::values.cpuopt_page_tables) {
+ config.page_table = nullptr;
+ }
+ if (!Settings::values.cpuopt_block_linking) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::BlockLinking;
+ }
+ if (!Settings::values.cpuopt_return_stack_buffer) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::ReturnStackBuffer;
+ }
+ if (!Settings::values.cpuopt_fast_dispatcher) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::FastDispatch;
+ }
+ if (!Settings::values.cpuopt_context_elimination) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::GetSetElimination;
+ }
+ if (!Settings::values.cpuopt_const_prop) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::ConstProp;
+ }
+ if (!Settings::values.cpuopt_misc_ir) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::MiscIROpt;
+ }
+ if (!Settings::values.cpuopt_reduce_misalign_checks) {
+ config.only_detect_misalignment_via_page_table_on_page_boundary = false;
+ }
+ }
+
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ }
+
return std::make_unique<Dynarmic::A32::Jit>(config);
}
-MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_32, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
-
void ARM_Dynarmic_32::Run() {
- MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_32);
jit->Run();
}
void ARM_Dynarmic_32::Step() {
- cb->InterpreterFallback(jit->Regs()[15], 1);
+ jit->Step();
}
-ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor,
+ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers,
+ bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index)
- : ARM_Interface{system},
- cb(std::make_unique<DynarmicCallbacks32>(*this)), core_index{core_index},
+ : ARM_Interface{system, interrupt_handlers, uses_wall_clock},
+ cb(std::make_unique<DynarmicCallbacks32>(*this)),
+ cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index},
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
ARM_Dynarmic_32::~ARM_Dynarmic_32() = default;
@@ -154,32 +234,40 @@ void ARM_Dynarmic_32::SetPSTATE(u32 cpsr) {
}
u64 ARM_Dynarmic_32::GetTlsAddress() const {
- return CP15_regs[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)];
+ return cp15->uro;
}
void ARM_Dynarmic_32::SetTlsAddress(VAddr address) {
- CP15_regs[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)] = static_cast<u32>(address);
+ cp15->uro = static_cast<u32>(address);
}
u64 ARM_Dynarmic_32::GetTPIDR_EL0() const {
- return cb->tpidr_el0;
+ return cp15->uprw;
}
void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
- cb->tpidr_el0 = value;
+ cp15->uprw = static_cast<u32>(value);
+}
+
+void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) {
+ jit->ChangeProcessorID(new_core_id);
}
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
Dynarmic::A32::Context context;
jit->SaveContext(context);
ctx.cpu_registers = context.Regs();
+ ctx.extension_registers = context.ExtRegs();
ctx.cpsr = context.Cpsr();
+ ctx.fpscr = context.Fpscr();
}
void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
Dynarmic::A32::Context context;
context.Regs() = ctx.cpu_registers;
+ context.ExtRegs() = ctx.extension_registers;
context.SetCpsr(ctx.cpsr);
+ context.SetFpscr(ctx.fpscr);
jit->LoadContext(context);
}
@@ -188,10 +276,15 @@ void ARM_Dynarmic_32::PrepareReschedule() {
}
void ARM_Dynarmic_32::ClearInstructionCache() {
+ if (!jit) {
+ return;
+ }
jit->ClearCache();
}
-void ARM_Dynarmic_32::ClearExclusiveState() {}
+void ARM_Dynarmic_32::ClearExclusiveState() {
+ jit->ClearExclusiveState();
+}
void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
std::size_t new_address_space_size_in_bits) {
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index 143e46e4d..2bab31b92 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -9,25 +9,28 @@
#include <dynarmic/A32/a32.h>
#include <dynarmic/A64/a64.h>
-#include <dynarmic/A64/exclusive_monitor.h>
+#include <dynarmic/exclusive_monitor.h>
#include "common/common_types.h"
#include "common/hash.h"
#include "core/arm/arm_interface.h"
#include "core/arm/exclusive_monitor.h"
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
namespace Core {
+class CPUInterruptHandler;
class DynarmicCallbacks32;
+class DynarmicCP15;
class DynarmicExclusiveMonitor;
class System;
class ARM_Dynarmic_32 final : public ARM_Interface {
public:
- ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
+ ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
+ ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic_32() override;
void SetPC(u64 pc) override;
@@ -44,6 +47,7 @@ public:
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override;
+ void ChangeProcessorID(std::size_t new_core_id) override;
void SaveContext(ThreadContext32& ctx) override;
void SaveContext(ThreadContext64& ctx) override {}
@@ -66,12 +70,14 @@ private:
std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A32::Jit>, Common::PairHash>;
friend class DynarmicCallbacks32;
+ friend class DynarmicCP15;
+
std::unique_ptr<DynarmicCallbacks32> cb;
JitCacheType jit_cache;
std::shared_ptr<Dynarmic::A32::Jit> jit;
+ std::shared_ptr<DynarmicCP15> cp15;
std::size_t core_index;
DynarmicExclusiveMonitor& exclusive_monitor;
- std::array<u32, 84> CP15_regs{};
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index a53a58ba0..9f170a224 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -6,20 +6,21 @@
#include <memory>
#include <dynarmic/A64/a64.h>
#include <dynarmic/A64/config.h>
+#include "common/assert.h"
#include "common/logging/log.h"
-#include "common/microprofile.h"
+#include "common/page_table.h"
+#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/core_timing.h"
-#include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/svc.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
+#include "core/settings.h"
namespace Core {
@@ -64,17 +65,26 @@ public:
memory.Write64(vaddr + 8, value[1]);
}
+ bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
+ return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
+ return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
+ return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
+ return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
+ }
+ bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
+ return parent.system.Memory().WriteExclusive128(vaddr, value, expected);
+ }
+
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
- LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
- num_instructions, MemoryReadCode(pc));
-
- ARM_Interface::ThreadContext64 ctx;
- parent.SaveContext(ctx);
- parent.inner_unicorn.LoadContext(ctx);
- parent.inner_unicorn.ExecuteInstructions(num_instructions);
- parent.inner_unicorn.SaveContext(ctx);
- parent.LoadContext(ctx);
- num_interpreted_instructions += num_instructions;
+ LOG_ERROR(Core_ARM,
+ "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
+ num_instructions, MemoryReadCode(pc));
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
@@ -97,39 +107,50 @@ public:
}
[[fallthrough]];
default:
- ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})",
- static_cast<std::size_t>(exception), pc);
+ ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
+ static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
}
}
void CallSVC(u32 swi) override {
- Kernel::CallSVC(parent.system, swi);
+ Kernel::Svc::Call(parent.system, swi);
}
void AddTicks(u64 ticks) override {
+ if (parent.uses_wall_clock) {
+ return;
+ }
+
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
- u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
+ u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
parent.system.CoreTiming().AddTicks(amortized_ticks);
- num_interpreted_instructions = 0;
}
+
u64 GetTicksRemaining() override {
- return std::max(parent.system.CoreTiming().GetDowncount(), s64{0});
+ if (parent.uses_wall_clock) {
+ if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
+ return minimum_run_cycles;
+ }
+ return 0U;
+ }
+ return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
+
u64 GetCNTPCT() override {
- return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks());
+ return parent.system.CoreTiming().GetClockTicks();
}
ARM_Dynarmic_64& parent;
- std::size_t num_interpreted_instructions = 0;
u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0;
+ static constexpr u64 minimum_run_cycles = 1000U;
};
std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& page_table,
@@ -144,6 +165,8 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
config.page_table_address_space_bits = address_space_bits;
config.silently_mirror_page_table = false;
config.absolute_offset_page_table = true;
+ config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
+ config.only_detect_misalignment_via_page_table_on_page_boundary = true;
// Multi-process state
config.processor_id = core_index;
@@ -159,14 +182,52 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
// Unpredictable instructions
config.define_unpredictable_behaviour = true;
+ // Timing
+ config.wall_clock_cntpct = uses_wall_clock;
+
+ // Safe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
+ if (!Settings::values.cpuopt_page_tables) {
+ config.page_table = nullptr;
+ }
+ if (!Settings::values.cpuopt_block_linking) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::BlockLinking;
+ }
+ if (!Settings::values.cpuopt_return_stack_buffer) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::ReturnStackBuffer;
+ }
+ if (!Settings::values.cpuopt_fast_dispatcher) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::FastDispatch;
+ }
+ if (!Settings::values.cpuopt_context_elimination) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::GetSetElimination;
+ }
+ if (!Settings::values.cpuopt_const_prop) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::ConstProp;
+ }
+ if (!Settings::values.cpuopt_misc_ir) {
+ config.optimizations &= ~Dynarmic::OptimizationFlag::MiscIROpt;
+ }
+ if (!Settings::values.cpuopt_reduce_misalign_checks) {
+ config.only_detect_misalignment_via_page_table_on_page_boundary = false;
+ }
+ }
+
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ }
+
return std::make_shared<Dynarmic::A64::Jit>(config);
}
-MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_64, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
-
void ARM_Dynarmic_64::Run() {
- MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_64);
-
jit->Run();
}
@@ -174,12 +235,12 @@ void ARM_Dynarmic_64::Step() {
cb->InterpreterFallback(jit->GetPC(), 1);
}
-ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor,
+ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers,
+ bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index)
- : ARM_Interface{system},
- cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system},
- core_index{core_index}, exclusive_monitor{
- dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
+ : ARM_Interface{system, interrupt_handlers, uses_wall_clock},
+ cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index},
+ exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
ARM_Dynarmic_64::~ARM_Dynarmic_64() = default;
@@ -231,6 +292,10 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
cb->tpidr_el0 = value;
}
+void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) {
+ jit->ChangeProcessorID(new_core_id);
+}
+
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
ctx.cpu_registers = jit->GetRegisters();
ctx.sp = jit->GetSP();
@@ -258,6 +323,9 @@ void ARM_Dynarmic_64::PrepareReschedule() {
}
void ARM_Dynarmic_64::ClearInstructionCache() {
+ if (!jit) {
+ return;
+ }
jit->ClearCache();
}
@@ -277,44 +345,4 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
jit_cache.emplace(key, jit);
}
-DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
- : monitor(core_count), memory{memory} {}
-
-DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
-
-void DynarmicExclusiveMonitor::SetExclusive(std::size_t core_index, VAddr addr) {
- // Size doesn't actually matter.
- monitor.Mark(core_index, addr, 16);
-}
-
-void DynarmicExclusiveMonitor::ClearExclusive() {
- monitor.Clear();
-}
-
-bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
- return monitor.DoExclusiveOperation(core_index, vaddr, 1, [&] { memory.Write8(vaddr, value); });
-}
-
-bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
- return monitor.DoExclusiveOperation(core_index, vaddr, 2,
- [&] { memory.Write16(vaddr, value); });
-}
-
-bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
- return monitor.DoExclusiveOperation(core_index, vaddr, 4,
- [&] { memory.Write32(vaddr, value); });
-}
-
-bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
- return monitor.DoExclusiveOperation(core_index, vaddr, 8,
- [&] { memory.Write64(vaddr, value); });
-}
-
-bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
- return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] {
- memory.Write64(vaddr + 0, value[0]);
- memory.Write64(vaddr + 8, value[1]);
- });
-}
-
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index e71240a96..28e11a17d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -8,26 +8,26 @@
#include <unordered_map>
#include <dynarmic/A64/a64.h>
-#include <dynarmic/A64/exclusive_monitor.h>
#include "common/common_types.h"
#include "common/hash.h"
#include "core/arm/arm_interface.h"
#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
namespace Core {
class DynarmicCallbacks64;
+class CPUInterruptHandler;
class DynarmicExclusiveMonitor;
class System;
class ARM_Dynarmic_64 final : public ARM_Interface {
public:
- ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
+ ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
+ ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic_64() override;
void SetPC(u64 pc) override;
@@ -44,6 +44,7 @@ public:
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override;
+ void ChangeProcessorID(std::size_t new_core_id) override;
void SaveContext(ThreadContext32& ctx) override {}
void SaveContext(ThreadContext64& ctx) override;
@@ -69,30 +70,9 @@ private:
std::unique_ptr<DynarmicCallbacks64> cb;
JitCacheType jit_cache;
std::shared_ptr<Dynarmic::A64::Jit> jit;
- ARM_Unicorn inner_unicorn;
std::size_t core_index;
DynarmicExclusiveMonitor& exclusive_monitor;
};
-class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
-public:
- explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
- ~DynarmicExclusiveMonitor() override;
-
- void SetExclusive(std::size_t core_index, VAddr addr) override;
- void ClearExclusive() override;
-
- bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
- bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
- bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
- bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
- bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
-
-private:
- friend class ARM_Dynarmic_64;
- Dynarmic::A64::ExclusiveMonitor monitor;
- Memory::Memory& memory;
-};
-
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index 3fdcdebde..caefc09f4 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -2,79 +2,132 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <fmt/format.h>
+#include "common/logging/log.h"
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
using Callback = Dynarmic::A32::Coprocessor::Callback;
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
+template <>
+struct fmt::formatter<Dynarmic::A32::CoprocReg> {
+ constexpr auto parse(format_parse_context& ctx) {
+ return ctx.begin();
+ }
+ template <typename FormatContext>
+ auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) {
+ return format_to(ctx.out(), "cp{}", static_cast<size_t>(reg));
+ }
+};
+
+namespace Core {
+
+static u32 dummy_value;
+
std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1,
CoprocReg CRd, CoprocReg CRn,
CoprocReg CRm, unsigned opc2) {
- return {};
+ LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn,
+ CRm, opc2);
+ return std::nullopt;
}
CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
CoprocReg CRm, unsigned opc2) {
- // TODO(merry): Privileged CP15 registers
-
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
+ // CP15_FLUSH_PREFETCH_BUFFER
// This is a dummy write, we ignore the value written here.
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_FLUSH_PREFETCH_BUFFER)];
+ return &dummy_value;
}
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
switch (opc2) {
case 4:
+ // CP15_DATA_SYNC_BARRIER
// This is a dummy write, we ignore the value written here.
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_DATA_SYNC_BARRIER)];
+ return &dummy_value;
case 5:
+ // CP15_DATA_MEMORY_BARRIER
// This is a dummy write, we ignore the value written here.
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_DATA_MEMORY_BARRIER)];
- default:
- return {};
+ return &dummy_value;
}
}
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_UPRW)];
+ // CP15_THREAD_UPRW
+ return &uprw;
}
+ LOG_CRITICAL(Core_ARM, "CP15: mcr{} p15, {}, <Rt>, {}, {}, {}", two ? "2" : "", opc1, CRn, CRm,
+ opc2);
return {};
}
CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ LOG_CRITICAL(Core_ARM, "CP15: mcrr{} p15, {}, <Rt>, <Rt2>, {}", two ? "2" : "", opc, CRm);
return {};
}
CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
CoprocReg CRm, unsigned opc2) {
- // TODO(merry): Privileged CP15 registers
-
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
switch (opc2) {
case 2:
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_UPRW)];
+ // CP15_THREAD_UPRW
+ return &uprw;
case 3:
- return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)];
- default:
- return {};
+ // CP15_THREAD_URO
+ return &uro;
}
}
+ LOG_CRITICAL(Core_ARM, "CP15: mrc{} p15, {}, <Rt>, {}, {}, {}", two ? "2" : "", opc1, CRn, CRm,
+ opc2);
return {};
}
CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ if (!two && opc == 0 && CRm == CoprocReg::C14) {
+ // CNTPCT
+ const auto callback = static_cast<u64 (*)(Dynarmic::A32::Jit*, void*, u32, u32)>(
+ [](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 {
+ ARM_Dynarmic_32& parent = *(ARM_Dynarmic_32*)arg;
+ return parent.system.CoreTiming().GetClockTicks();
+ });
+ return Dynarmic::A32::Coprocessor::Callback{callback, (void*)&parent};
+ }
+
+ LOG_CRITICAL(Core_ARM, "CP15: mrrc{} p15, {}, <Rt>, <Rt2>, {}", two ? "2" : "", opc, CRm);
return {};
}
std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
std::optional<u8> option) {
- return {};
+ if (option) {
+ LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...], {}", two ? "2" : "",
+ long_transfer ? "l" : "", CRd, *option);
+ } else {
+ LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
+ long_transfer ? "l" : "", CRd);
+ }
+ return std::nullopt;
}
std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
std::optional<u8> option) {
- return {};
+ if (option) {
+ LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...], {}", two ? "2" : "",
+ long_transfer ? "l" : "", CRd, *option);
+ } else {
+ LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
+ long_transfer ? "l" : "", CRd);
+ }
+ return std::nullopt;
}
+
+} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index 07bcde5f9..dc6f4af3a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -10,128 +10,15 @@
#include <dynarmic/A32/coprocessor.h>
#include "common/common_types.h"
-enum class CP15Register {
- // c0 - Information registers
- CP15_MAIN_ID,
- CP15_CACHE_TYPE,
- CP15_TCM_STATUS,
- CP15_TLB_TYPE,
- CP15_CPU_ID,
- CP15_PROCESSOR_FEATURE_0,
- CP15_PROCESSOR_FEATURE_1,
- CP15_DEBUG_FEATURE_0,
- CP15_AUXILIARY_FEATURE_0,
- CP15_MEMORY_MODEL_FEATURE_0,
- CP15_MEMORY_MODEL_FEATURE_1,
- CP15_MEMORY_MODEL_FEATURE_2,
- CP15_MEMORY_MODEL_FEATURE_3,
- CP15_ISA_FEATURE_0,
- CP15_ISA_FEATURE_1,
- CP15_ISA_FEATURE_2,
- CP15_ISA_FEATURE_3,
- CP15_ISA_FEATURE_4,
+namespace Core {
- // c1 - Control registers
- CP15_CONTROL,
- CP15_AUXILIARY_CONTROL,
- CP15_COPROCESSOR_ACCESS_CONTROL,
-
- // c2 - Translation table registers
- CP15_TRANSLATION_BASE_TABLE_0,
- CP15_TRANSLATION_BASE_TABLE_1,
- CP15_TRANSLATION_BASE_CONTROL,
- CP15_DOMAIN_ACCESS_CONTROL,
- CP15_RESERVED,
-
- // c5 - Fault status registers
- CP15_FAULT_STATUS,
- CP15_INSTR_FAULT_STATUS,
- CP15_COMBINED_DATA_FSR = CP15_FAULT_STATUS,
- CP15_INST_FSR,
-
- // c6 - Fault Address registers
- CP15_FAULT_ADDRESS,
- CP15_COMBINED_DATA_FAR = CP15_FAULT_ADDRESS,
- CP15_WFAR,
- CP15_IFAR,
-
- // c7 - Cache operation registers
- CP15_WAIT_FOR_INTERRUPT,
- CP15_PHYS_ADDRESS,
- CP15_INVALIDATE_INSTR_CACHE,
- CP15_INVALIDATE_INSTR_CACHE_USING_MVA,
- CP15_INVALIDATE_INSTR_CACHE_USING_INDEX,
- CP15_FLUSH_PREFETCH_BUFFER,
- CP15_FLUSH_BRANCH_TARGET_CACHE,
- CP15_FLUSH_BRANCH_TARGET_CACHE_ENTRY,
- CP15_INVALIDATE_DATA_CACHE,
- CP15_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
- CP15_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
- CP15_INVALIDATE_DATA_AND_INSTR_CACHE,
- CP15_CLEAN_DATA_CACHE,
- CP15_CLEAN_DATA_CACHE_LINE_USING_MVA,
- CP15_CLEAN_DATA_CACHE_LINE_USING_INDEX,
- CP15_DATA_SYNC_BARRIER,
- CP15_DATA_MEMORY_BARRIER,
- CP15_CLEAN_AND_INVALIDATE_DATA_CACHE,
- CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
- CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
-
- // c8 - TLB operations
- CP15_INVALIDATE_ITLB,
- CP15_INVALIDATE_ITLB_SINGLE_ENTRY,
- CP15_INVALIDATE_ITLB_ENTRY_ON_ASID_MATCH,
- CP15_INVALIDATE_ITLB_ENTRY_ON_MVA,
- CP15_INVALIDATE_DTLB,
- CP15_INVALIDATE_DTLB_SINGLE_ENTRY,
- CP15_INVALIDATE_DTLB_ENTRY_ON_ASID_MATCH,
- CP15_INVALIDATE_DTLB_ENTRY_ON_MVA,
- CP15_INVALIDATE_UTLB,
- CP15_INVALIDATE_UTLB_SINGLE_ENTRY,
- CP15_INVALIDATE_UTLB_ENTRY_ON_ASID_MATCH,
- CP15_INVALIDATE_UTLB_ENTRY_ON_MVA,
-
- // c9 - Data cache lockdown register
- CP15_DATA_CACHE_LOCKDOWN,
-
- // c10 - TLB/Memory map registers
- CP15_TLB_LOCKDOWN,
- CP15_PRIMARY_REGION_REMAP,
- CP15_NORMAL_REGION_REMAP,
-
- // c13 - Thread related registers
- CP15_PID,
- CP15_CONTEXT_ID,
- CP15_THREAD_UPRW, // Thread ID register - User/Privileged Read/Write
- CP15_THREAD_URO, // Thread ID register - User Read Only (Privileged R/W)
- CP15_THREAD_PRW, // Thread ID register - Privileged R/W only.
-
- // c15 - Performance and TLB lockdown registers
- CP15_PERFORMANCE_MONITOR_CONTROL,
- CP15_CYCLE_COUNTER,
- CP15_COUNT_0,
- CP15_COUNT_1,
- CP15_READ_MAIN_TLB_LOCKDOWN_ENTRY,
- CP15_WRITE_MAIN_TLB_LOCKDOWN_ENTRY,
- CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS,
- CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS,
- CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE,
- CP15_TLB_DEBUG_CONTROL,
-
- // Skyeye defined
- CP15_TLB_FAULT_ADDR,
- CP15_TLB_FAULT_STATUS,
-
- // Not an actual register.
- // All registers should be defined above this.
- CP15_REGISTER_COUNT,
-};
+class ARM_Dynarmic_32;
class DynarmicCP15 final : public Dynarmic::A32::Coprocessor {
public:
using CoprocReg = Dynarmic::A32::CoprocReg;
- explicit DynarmicCP15(u32* cp15) : CP15(cp15){};
+ explicit DynarmicCP15(ARM_Dynarmic_32& parent) : parent(parent) {}
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
CoprocReg CRn, CoprocReg CRm,
@@ -147,6 +34,9 @@ public:
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
std::optional<u8> option) override;
-private:
- u32* CP15{};
+ ARM_Dynarmic_32& parent;
+ u32 uprw = 0;
+ u32 uro = 0;
};
+
+} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.cpp b/src/core/arm/dynarmic/arm_exclusive_monitor.cpp
new file mode 100644
index 000000000..4e209f6a5
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_exclusive_monitor.cpp
@@ -0,0 +1,76 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <memory>
+#include "core/arm/dynarmic/arm_exclusive_monitor.h"
+#include "core/memory.h"
+
+namespace Core {
+
+DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
+ : monitor(core_count), memory{memory} {}
+
+DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
+
+u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
+ return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
+}
+
+u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
+ return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
+}
+
+u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
+ return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
+}
+
+u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
+ return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
+}
+
+u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
+ return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
+ u128 result;
+ result[0] = memory.Read64(addr);
+ result[1] = memory.Read64(addr + 8);
+ return result;
+ });
+}
+
+void DynarmicExclusiveMonitor::ClearExclusive() {
+ monitor.Clear();
+}
+
+bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
+ return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
+ return memory.WriteExclusive8(vaddr, value, expected);
+ });
+}
+
+bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
+ return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
+ return memory.WriteExclusive16(vaddr, value, expected);
+ });
+}
+
+bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
+ return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
+ return memory.WriteExclusive32(vaddr, value, expected);
+ });
+}
+
+bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
+ return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
+ return memory.WriteExclusive64(vaddr, value, expected);
+ });
+}
+
+bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
+ return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
+ return memory.WriteExclusive128(vaddr, value, expected);
+ });
+}
+
+} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_exclusive_monitor.h b/src/core/arm/dynarmic/arm_exclusive_monitor.h
new file mode 100644
index 000000000..964f4a55d
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_exclusive_monitor.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+
+#include <dynarmic/exclusive_monitor.h>
+
+#include "common/common_types.h"
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
+#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#include "core/arm/exclusive_monitor.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Core {
+
+class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
+public:
+ explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
+ ~DynarmicExclusiveMonitor() override;
+
+ u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;
+ u16 ExclusiveRead16(std::size_t core_index, VAddr addr) override;
+ u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
+ u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
+ u128 ExclusiveRead128(std::size_t core_index, VAddr addr) override;
+ void ClearExclusive() override;
+
+ bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
+ bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
+ bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
+ bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
+ bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
+
+private:
+ friend class ARM_Dynarmic_32;
+ friend class ARM_Dynarmic_64;
+ Dynarmic::ExclusiveMonitor monitor;
+ Core::Memory::Memory& memory;
+};
+
+} // namespace Core
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index b32401e0b..d8cba369d 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#ifdef ARCHITECTURE_x86_64
-#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/memory.h"
diff --git a/src/core/arm/exclusive_monitor.h b/src/core/arm/exclusive_monitor.h
index 4ef418b90..62f6e6023 100644
--- a/src/core/arm/exclusive_monitor.h
+++ b/src/core/arm/exclusive_monitor.h
@@ -8,7 +8,7 @@
#include "common/common_types.h"
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
@@ -18,7 +18,11 @@ class ExclusiveMonitor {
public:
virtual ~ExclusiveMonitor();
- virtual void SetExclusive(std::size_t core_index, VAddr addr) = 0;
+ virtual u8 ExclusiveRead8(std::size_t core_index, VAddr addr) = 0;
+ virtual u16 ExclusiveRead16(std::size_t core_index, VAddr addr) = 0;
+ virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
+ virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
+ virtual u128 ExclusiveRead128(std::size_t core_index, VAddr addr) = 0;
virtual void ClearExclusive() = 0;
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
deleted file mode 100644
index 8a9800a96..000000000
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <unicorn/arm64.h>
-#include "common/assert.h"
-#include "common/microprofile.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/svc.h"
-
-namespace Core {
-
-// Load Unicorn DLL once on Windows using RAII
-#ifdef _MSC_VER
-#include <unicorn_dynload.h>
-struct LoadDll {
-private:
- LoadDll() {
- ASSERT(uc_dyn_load(NULL, 0));
- }
- ~LoadDll() {
- ASSERT(uc_dyn_free());
- }
- static LoadDll g_load_dll;
-};
-LoadDll LoadDll::g_load_dll;
-#endif
-
-#define CHECKED(expr) \
- do { \
- if (auto _cerr = (expr)) { \
- ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \
- uc_strerror(_cerr)); \
- } \
- } while (0)
-
-static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {
- GDBStub::BreakpointAddress bkpt =
- GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute);
- if (GDBStub::IsMemoryBreak() ||
- (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) {
- auto core = static_cast<ARM_Unicorn*>(user_data);
- core->RecordBreak(bkpt);
- uc_emu_stop(uc);
- }
-}
-
-static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
- void* user_data) {
- auto* const system = static_cast<System*>(user_data);
-
- ARM_Interface::ThreadContext64 ctx{};
- system->CurrentArmInterface().SaveContext(ctx);
- ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
- ctx.pc, ctx.cpu_registers[30]);
-
- return false;
-}
-
-ARM_Unicorn::ARM_Unicorn(System& system) : ARM_Interface{system} {
- CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
-
- auto fpv = 3 << 20;
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
-
- uc_hook hook{};
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, UINT64_MAX));
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0,
- UINT64_MAX));
- if (GDBStub::IsServerEnabled()) {
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, UINT64_MAX));
- last_bkpt_hit = false;
- }
-}
-
-ARM_Unicorn::~ARM_Unicorn() {
- CHECKED(uc_close(uc));
-}
-
-void ARM_Unicorn::SetPC(u64 pc) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc));
-}
-
-u64 ARM_Unicorn::GetPC() const {
- u64 val{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val));
- return val;
-}
-
-u64 ARM_Unicorn::GetReg(int regn) const {
- u64 val{};
- auto treg = UC_ARM64_REG_SP;
- if (regn <= 28) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn);
- } else if (regn < 31) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29);
- }
- CHECKED(uc_reg_read(uc, treg, &val));
- return val;
-}
-
-void ARM_Unicorn::SetReg(int regn, u64 val) {
- auto treg = UC_ARM64_REG_SP;
- if (regn <= 28) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn);
- } else if (regn < 31) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29);
- }
- CHECKED(uc_reg_write(uc, treg, &val));
-}
-
-u128 ARM_Unicorn::GetVectorReg(int /*index*/) const {
- UNIMPLEMENTED();
- static constexpr u128 res{};
- return res;
-}
-
-void ARM_Unicorn::SetVectorReg(int /*index*/, u128 /*value*/) {
- UNIMPLEMENTED();
-}
-
-u32 ARM_Unicorn::GetPSTATE() const {
- u64 nzcv{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &nzcv));
- return static_cast<u32>(nzcv);
-}
-
-void ARM_Unicorn::SetPSTATE(u32 pstate) {
- u64 nzcv = pstate;
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &nzcv));
-}
-
-VAddr ARM_Unicorn::GetTlsAddress() const {
- u64 base{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
- return base;
-}
-
-void ARM_Unicorn::SetTlsAddress(VAddr base) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
-}
-
-u64 ARM_Unicorn::GetTPIDR_EL0() const {
- u64 value{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDR_EL0, &value));
- return value;
-}
-
-void ARM_Unicorn::SetTPIDR_EL0(u64 value) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value));
-}
-
-void ARM_Unicorn::Run() {
- if (GDBStub::IsServerEnabled()) {
- ExecuteInstructions(std::max(4000000U, 0U));
- } else {
- ExecuteInstructions(
- std::max(std::size_t(system.CoreTiming().GetDowncount()), std::size_t{0}));
- }
-}
-
-void ARM_Unicorn::Step() {
- ExecuteInstructions(1);
-}
-
-MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
-
-void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
- MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
- CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
- system.CoreTiming().AddTicks(num_instructions);
- if (GDBStub::IsServerEnabled()) {
- if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
- uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
- }
-
- Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread();
- SaveContext(thread->GetContext64());
- if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
- last_bkpt_hit = false;
- GDBStub::Break();
- GDBStub::SendTrap(thread, 5);
- }
- }
-}
-
-void ARM_Unicorn::SaveContext(ThreadContext64& ctx) {
- int uregs[32];
- void* tregs[32];
-
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &ctx.sp));
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &ctx.pc));
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.pstate));
-
- for (auto i = 0; i < 29; ++i) {
- uregs[i] = UC_ARM64_REG_X0 + i;
- tregs[i] = &ctx.cpu_registers[i];
- }
- uregs[29] = UC_ARM64_REG_X29;
- tregs[29] = (void*)&ctx.cpu_registers[29];
- uregs[30] = UC_ARM64_REG_X30;
- tregs[30] = (void*)&ctx.cpu_registers[30];
-
- CHECKED(uc_reg_read_batch(uc, uregs, tregs, 31));
-
- for (int i = 0; i < 32; ++i) {
- uregs[i] = UC_ARM64_REG_Q0 + i;
- tregs[i] = &ctx.vector_registers[i];
- }
-
- CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32));
-}
-
-void ARM_Unicorn::LoadContext(const ThreadContext64& ctx) {
- int uregs[32];
- void* tregs[32];
-
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &ctx.sp));
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &ctx.pc));
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.pstate));
-
- for (int i = 0; i < 29; ++i) {
- uregs[i] = UC_ARM64_REG_X0 + i;
- tregs[i] = (void*)&ctx.cpu_registers[i];
- }
- uregs[29] = UC_ARM64_REG_X29;
- tregs[29] = (void*)&ctx.cpu_registers[29];
- uregs[30] = UC_ARM64_REG_X30;
- tregs[30] = (void*)&ctx.cpu_registers[30];
-
- CHECKED(uc_reg_write_batch(uc, uregs, tregs, 31));
-
- for (auto i = 0; i < 32; ++i) {
- uregs[i] = UC_ARM64_REG_Q0 + i;
- tregs[i] = (void*)&ctx.vector_registers[i];
- }
-
- CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32));
-}
-
-void ARM_Unicorn::PrepareReschedule() {
- CHECKED(uc_emu_stop(uc));
-}
-
-void ARM_Unicorn::ClearExclusiveState() {}
-
-void ARM_Unicorn::ClearInstructionCache() {}
-
-void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
- last_bkpt = bkpt;
- last_bkpt_hit = true;
-}
-
-void ARM_Unicorn::InterruptHook(uc_engine* uc, u32 int_no, void* user_data) {
- u32 esr{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
-
- const auto ec = esr >> 26;
- const auto iss = esr & 0xFFFFFF;
-
- auto* const arm_instance = static_cast<ARM_Unicorn*>(user_data);
-
- switch (ec) {
- case 0x15: // SVC
- Kernel::CallSVC(arm_instance->system, iss);
- break;
- }
-}
-
-} // namespace Core
diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h
deleted file mode 100644
index f30d13cb6..000000000
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <unicorn/unicorn.h>
-#include "common/common_types.h"
-#include "core/arm/arm_interface.h"
-#include "core/gdbstub/gdbstub.h"
-
-namespace Core {
-
-class System;
-
-class ARM_Unicorn final : public ARM_Interface {
-public:
- explicit ARM_Unicorn(System& system);
- ~ARM_Unicorn() override;
-
- void SetPC(u64 pc) override;
- u64 GetPC() const override;
- u64 GetReg(int index) const override;
- void SetReg(int index, u64 value) override;
- u128 GetVectorReg(int index) const override;
- void SetVectorReg(int index, u128 value) override;
- u32 GetPSTATE() const override;
- void SetPSTATE(u32 pstate) override;
- VAddr GetTlsAddress() const override;
- void SetTlsAddress(VAddr address) override;
- void SetTPIDR_EL0(u64 value) override;
- u64 GetTPIDR_EL0() const override;
- void PrepareReschedule() override;
- void ClearExclusiveState() override;
- void ExecuteInstructions(std::size_t num_instructions);
- void Run() override;
- void Step() override;
- void ClearInstructionCache() override;
- void PageTableChanged(Common::PageTable&, std::size_t) override {}
- void RecordBreak(GDBStub::BreakpointAddress bkpt);
-
- void SaveContext(ThreadContext32& ctx) override {}
- void SaveContext(ThreadContext64& ctx) override;
- void LoadContext(const ThreadContext32& ctx) override {}
- void LoadContext(const ThreadContext64& ctx) override;
-
-private:
- static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data);
-
- uc_engine* uc{};
- GDBStub::BreakpointAddress last_bkpt{};
- bool last_bkpt_hit = false;
-};
-
-} // namespace Core
diff --git a/src/core/constants.h b/src/core/constants.h
index 6d0ec022a..81c5cb279 100644
--- a/src/core/constants.h
+++ b/src/core/constants.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include "common/common_types.h"
// This is to consolidate system-wide constants that are used by multiple components of yuzu.
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3bd90d79f..5accdc783 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -8,12 +8,13 @@
#include "common/file_util.h"
#include "common/logging/log.h"
+#include "common/microprofile.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
+#include "core/device_memory.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/mode.h"
@@ -39,9 +40,11 @@
#include "core/hle/service/lm/manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/time/time_manager.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
+#include "core/network/network.h"
#include "core/perf_stats.h"
#include "core/reporter.h"
#include "core/settings.h"
@@ -50,6 +53,11 @@
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
+MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU0, "ARM JIT", "Dynarmic CPU 0", MP_RGB(255, 64, 64));
+MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU1, "ARM JIT", "Dynarmic CPU 1", MP_RGB(255, 64, 64));
+MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU2, "ARM JIT", "Dynarmic CPU 2", MP_RGB(255, 64, 64));
+MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU3, "ARM JIT", "Dynarmic CPU 3", MP_RGB(255, 64, 64));
+
namespace Core {
namespace {
@@ -106,7 +114,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
}
- if (FileUtil::IsDirectory(path))
+ if (Common::FS::IsDirectory(path))
return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
return vfs->OpenFile(path, FileSys::Mode::Read);
@@ -114,25 +122,24 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
- cpu_manager{system}, reporter{system}, applet_manager{system} {}
+ cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
- CoreManager& CurrentCoreManager() {
- return cpu_manager.GetCurrentCoreManager();
- }
+ ResultStatus Run() {
+ status = ResultStatus::Success;
- Kernel::PhysicalCore& CurrentPhysicalCore() {
- const auto index = cpu_manager.GetActiveCoreIndex();
- return kernel.PhysicalCore(index);
- }
+ kernel.Suspend(false);
+ core_timing.SyncPause(false);
+ cpu_manager.Pause(false);
- Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
- return kernel.PhysicalCore(index);
+ return status;
}
- ResultStatus RunLoop(bool tight_loop) {
+ ResultStatus Pause() {
status = ResultStatus::Success;
- cpu_manager.RunLoop(tight_loop);
+ core_timing.SyncPause(true);
+ kernel.Suspend(true);
+ cpu_manager.Pause(true);
return status;
}
@@ -140,14 +147,24 @@ struct System::Impl {
ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
LOG_DEBUG(HW_Memory, "initialized OK");
- core_timing.Initialize();
+ device_memory = std::make_unique<Core::DeviceMemory>();
+
+ is_multicore = Settings::values.use_multi_core.GetValue();
+ is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue();
+
+ kernel.SetMulticore(is_multicore);
+ cpu_manager.SetMulticore(is_multicore);
+ cpu_manager.SetAsyncGpu(is_async_gpu);
+ core_timing.SetMulticore(is_multicore);
+
+ core_timing.Initialize([&system]() { system.RegisterHostThread(); });
kernel.Initialize();
cpu_manager.Initialize();
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
Settings::values.custom_rtc_differential =
- Settings::values.custom_rtc.value_or(current_time) - current_time;
+ Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time;
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
@@ -162,21 +179,30 @@ struct System::Impl {
arp_manager.ResetAll();
telemetry_session = std::make_unique<Core::TelemetrySession>();
- service_manager = std::make_shared<Service::SM::ServiceManager>();
- Service::Init(service_manager, system);
- GDBStub::DeferStart();
-
- interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
gpu_core = VideoCore::CreateGPU(emu_window, system);
if (!gpu_core) {
return ResultStatus::ErrorVideoCore;
}
- gpu_core->Renderer().Rasterizer().SetupDirtyFlags();
+
+ service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
+
+ Service::Init(service_manager, system);
+ GDBStub::DeferStart();
+
+ interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
+
+ // Initialize time manager, which must happen after kernel is created
+ time_manager.Initialize();
is_powered_on = true;
exit_lock = false;
+ microprofile_dynarmic[0] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU0);
+ microprofile_dynarmic[1] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU1);
+ microprofile_dynarmic[2] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU2);
+ microprofile_dynarmic[3] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU3);
+
LOG_DEBUG(Core, "Initialized OK");
return ResultStatus::Success;
@@ -184,7 +210,7 @@ struct System::Impl {
ResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
const std::string& filepath) {
- app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
+ app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath));
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader;
@@ -198,10 +224,10 @@ struct System::Impl {
return init_result;
}
- telemetry_session->AddInitialInfo(*app_loader);
+ telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
auto main_process =
Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
- const auto [load_result, load_parameters] = app_loader->Load(*main_process);
+ const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
Shutdown();
@@ -248,14 +274,14 @@ struct System::Impl {
// Log last frame performance stats if game was loded
if (perf_stats) {
const auto perf_results = GetAndResetPerfStats();
- telemetry_session->AddField(Telemetry::FieldType::Performance,
- "Shutdown_EmulationSpeed",
+ constexpr auto performance = Common::Telemetry::FieldType::Performance;
+
+ telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
perf_results.emulation_speed * 100.0);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
- perf_results.game_fps);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
+ telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps);
+ telemetry_session->AddField(performance, "Shutdown_Frametime",
perf_results.frametime * 1000.0);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
+ telemetry_session->AddField(performance, "Mean_Frametime_MS",
perf_stats->GetMeanFrametime());
}
@@ -274,8 +300,7 @@ struct System::Impl {
service_manager.reset();
cheat_engine.reset();
telemetry_session.reset();
- perf_stats.reset();
- gpu_core.reset();
+ device_memory.reset();
// Close all CPU/threading state
cpu_manager.Shutdown();
@@ -286,6 +311,8 @@ struct System::Impl {
// Close app loader
app_loader.reset();
+ gpu_core.reset();
+ perf_stats.reset();
// Clear all applets
applet_manager.ClearAll();
@@ -311,7 +338,7 @@ struct System::Impl {
Service::Glue::ApplicationLaunchProperty launch{};
launch.title_id = process.GetTitleID();
- FileSys::PatchManager pm{launch.title_id};
+ FileSys::PatchManager pm{launch.title_id, fs_controller, *content_provider};
launch.version = pm.GetGameVersion().value_or(0);
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
@@ -346,7 +373,8 @@ struct System::Impl {
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
- Memory::Memory memory;
+ std::unique_ptr<Core::DeviceMemory> device_memory;
+ Core::Memory::Memory memory;
CpuManager cpu_manager;
bool is_powered_on = false;
bool exit_lock = false;
@@ -365,6 +393,7 @@ struct System::Impl {
/// Service State
Service::Glue::ARPManager arp_manager;
Service::LM::Manager lm_manager{reporter};
+ Service::Time::TimeManager time_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -372,36 +401,53 @@ struct System::Impl {
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
+ /// Network instance
+ Network::NetworkInstance network_instance;
+
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
std::unique_ptr<Core::PerfStats> perf_stats;
Core::FrameLimiter frame_limiter;
+
+ bool is_multicore{};
+ bool is_async_gpu{};
+
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
+ std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{};
};
System::System() : impl{std::make_unique<Impl>(*this)} {}
System::~System() = default;
-CoreManager& System::CurrentCoreManager() {
- return impl->CurrentCoreManager();
+CpuManager& System::GetCpuManager() {
+ return impl->cpu_manager;
+}
+
+const CpuManager& System::GetCpuManager() const {
+ return impl->cpu_manager;
}
-const CoreManager& System::CurrentCoreManager() const {
- return impl->CurrentCoreManager();
+System::ResultStatus System::Run() {
+ return impl->Run();
}
-System::ResultStatus System::RunLoop(bool tight_loop) {
- return impl->RunLoop(tight_loop);
+System::ResultStatus System::Pause() {
+ return impl->Pause();
}
System::ResultStatus System::SingleStep() {
- return RunLoop(false);
+ return ResultStatus::Success;
}
void System::InvalidateCpuInstructionCaches() {
impl->kernel.InvalidateAllInstructionCaches();
}
+void System::Shutdown() {
+ impl->Shutdown();
+}
+
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
return impl->Load(*this, emu_window, filepath);
}
@@ -411,7 +457,7 @@ bool System::IsPoweredOn() const {
}
void System::PrepareReschedule() {
- impl->CurrentPhysicalCore().Stop();
+ // Deprecated, does nothing, kept for backward compatibility.
}
void System::PrepareReschedule(const u32 core_index) {
@@ -431,31 +477,41 @@ const TelemetrySession& System::TelemetrySession() const {
}
ARM_Interface& System::CurrentArmInterface() {
- return impl->CurrentPhysicalCore().ArmInterface();
+ return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
}
const ARM_Interface& System::CurrentArmInterface() const {
- return impl->CurrentPhysicalCore().ArmInterface();
+ return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
}
std::size_t System::CurrentCoreIndex() const {
- return impl->cpu_manager.GetActiveCoreIndex();
+ std::size_t core = impl->kernel.GetCurrentHostThreadID();
+ ASSERT(core < Core::Hardware::NUM_CPU_CORES);
+ return core;
}
Kernel::Scheduler& System::CurrentScheduler() {
- return impl->CurrentPhysicalCore().Scheduler();
+ return impl->kernel.CurrentScheduler();
}
const Kernel::Scheduler& System::CurrentScheduler() const {
- return impl->CurrentPhysicalCore().Scheduler();
+ return impl->kernel.CurrentScheduler();
+}
+
+Kernel::PhysicalCore& System::CurrentPhysicalCore() {
+ return impl->kernel.CurrentPhysicalCore();
+}
+
+const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
+ return impl->kernel.CurrentPhysicalCore();
}
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
- return impl->GetPhysicalCore(core_index).Scheduler();
+ return impl->kernel.Scheduler(core_index);
}
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
- return impl->GetPhysicalCore(core_index).Scheduler();
+ return impl->kernel.Scheduler(core_index);
}
/// Gets the global scheduler
@@ -472,25 +528,28 @@ Kernel::Process* System::CurrentProcess() {
return impl->kernel.CurrentProcess();
}
-const Kernel::Process* System::CurrentProcess() const {
- return impl->kernel.CurrentProcess();
+Core::DeviceMemory& System::DeviceMemory() {
+ return *impl->device_memory;
}
-ARM_Interface& System::ArmInterface(std::size_t core_index) {
- return impl->GetPhysicalCore(core_index).ArmInterface();
+const Core::DeviceMemory& System::DeviceMemory() const {
+ return *impl->device_memory;
}
-const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
- return impl->GetPhysicalCore(core_index).ArmInterface();
+const Kernel::Process* System::CurrentProcess() const {
+ return impl->kernel.CurrentProcess();
}
-CoreManager& System::GetCoreManager(std::size_t core_index) {
- return impl->cpu_manager.GetCoreManager(core_index);
+ARM_Interface& System::ArmInterface(std::size_t core_index) {
+ auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
+ ASSERT(thread && !thread->IsHLEThread());
+ return thread->ArmInterface();
}
-const CoreManager& System::GetCoreManager(std::size_t core_index) const {
- ASSERT(core_index < NUM_CPU_CORES);
- return impl->cpu_manager.GetCoreManager(core_index);
+const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
+ auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
+ ASSERT(thread && !thread->IsHLEThread());
+ return thread->ArmInterface();
}
ExclusiveMonitor& System::Monitor() {
@@ -505,7 +564,7 @@ Memory::Memory& System::Memory() {
return impl->memory;
}
-const Memory::Memory& System::Memory() const {
+const Core::Memory::Memory& System::Memory() const {
return impl->memory;
}
@@ -577,15 +636,19 @@ const std::string& System::GetStatusDetails() const {
return impl->status_details;
}
-Loader::AppLoader& System::GetAppLoader() const {
+Loader::AppLoader& System::GetAppLoader() {
+ return *impl->app_loader;
+}
+
+const Loader::AppLoader& System::GetAppLoader() const {
return *impl->app_loader;
}
-void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
+void System::SetFilesystem(FileSys::VirtualFilesystem vfs) {
impl->virtual_filesystem = std::move(vfs);
}
-std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
+FileSys::VirtualFilesystem System::GetFilesystem() const {
return impl->virtual_filesystem;
}
@@ -669,6 +732,14 @@ const Service::LM::Manager& System::GetLogManager() const {
return impl->lm_manager;
}
+Service::Time::TimeManager& System::GetTimeManager() {
+ return impl->time_manager;
+}
+
+const Service::Time::TimeManager& System::GetTimeManager() const {
+ return impl->time_manager;
+}
+
void System::SetExitLock(bool locked) {
impl->exit_lock = locked;
}
@@ -685,14 +756,6 @@ const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const {
return impl->build_id;
}
-System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
- return impl->Init(*this, emu_window);
-}
-
-void System::Shutdown() {
- impl->Shutdown();
-}
-
Service::SM::ServiceManager& System::ServiceManager() {
return *impl->service_manager;
}
@@ -709,4 +772,18 @@ void System::RegisterHostThread() {
impl->kernel.RegisterHostThread();
}
+void System::EnterDynarmicProfile() {
+ std::size_t core = impl->kernel.GetCurrentHostThreadID();
+ impl->dynarmic_ticks[core] = MicroProfileEnter(impl->microprofile_dynarmic[core]);
+}
+
+void System::ExitDynarmicProfile() {
+ std::size_t core = impl->kernel.GetCurrentHostThreadID();
+ MicroProfileLeave(impl->microprofile_dynarmic[core], impl->dynarmic_ticks[core]);
+}
+
+bool System::IsMulticore() const {
+ return impl->is_multicore;
+}
+
} // namespace Core
diff --git a/src/core/core.h b/src/core/core.h
index 8d862a8e6..cd155625c 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -27,6 +27,7 @@ class VfsFilesystem;
namespace Kernel {
class GlobalScheduler;
class KernelCore;
+class PhysicalCore;
class Process;
class Scheduler;
} // namespace Kernel
@@ -36,9 +37,10 @@ class AppLoader;
enum class ResultStatus : u16;
} // namespace Loader
-namespace Memory {
+namespace Core::Memory {
struct CheatEntry;
-} // namespace Memory
+class Memory;
+} // namespace Core::Memory
namespace Service {
@@ -67,6 +69,10 @@ namespace SM {
class ServiceManager;
} // namespace SM
+namespace Time {
+class TimeManager;
+} // namespace Time
+
} // namespace Service
namespace Tegra {
@@ -86,14 +92,11 @@ namespace Core::Hardware {
class InterruptManager;
}
-namespace Memory {
-class Memory;
-}
-
namespace Core {
class ARM_Interface;
-class CoreManager;
+class CpuManager;
+class DeviceMemory;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
@@ -121,7 +124,7 @@ public:
* Gets the instance of the System singleton class.
* @returns Reference to the instance of the System singleton class.
*/
- static System& GetInstance() {
+ [[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() {
return s_instance;
}
@@ -138,22 +141,22 @@ public:
};
/**
- * Run the core CPU loop
- * This function runs the core for the specified number of CPU instructions before trying to
- * update hardware. This is much faster than SingleStep (and should be equivalent), as the CPU
- * is not required to do a full dispatch with each instruction. NOTE: the number of instructions
- * requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
- * update is requested (e.g. on a thread switch).
- * @param tight_loop If false, the CPU single-steps.
- * @return Result status, indicating whether or not the operation succeeded.
+ * Run the OS and Application
+ * This function will start emulation and run the relevant devices
*/
- ResultStatus RunLoop(bool tight_loop = true);
+ [[nodiscard]] ResultStatus Run();
+
+ /**
+ * Pause the OS and Application
+ * This function will pause emulation and stop the relevant devices
+ */
+ [[nodiscard]] ResultStatus Pause();
/**
* Step the CPU one instruction
* @return Result status, indicating whether or not the operation succeeded.
*/
- ResultStatus SingleStep();
+ [[nodiscard]] ResultStatus SingleStep();
/**
* Invalidate the CPU instruction caches
@@ -172,20 +175,20 @@ public:
* @param filepath String path to the executable application to load on the host file system.
* @returns ResultStatus code, indicating if the operation succeeded.
*/
- ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath);
+ [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath);
/**
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
* application).
* @returns True if the emulated system is powered on, otherwise false.
*/
- bool IsPoweredOn() const;
+ [[nodiscard]] bool IsPoweredOn() const;
/// Gets a reference to the telemetry session for this emulation session.
- Core::TelemetrySession& TelemetrySession();
+ [[nodiscard]] Core::TelemetrySession& TelemetrySession();
/// Gets a reference to the telemetry session for this emulation session.
- const Core::TelemetrySession& TelemetrySession() const;
+ [[nodiscard]] const Core::TelemetrySession& TelemetrySession() const;
/// Prepare the core emulation for a reschedule
void PrepareReschedule();
@@ -194,171 +197,178 @@ public:
void PrepareReschedule(u32 core_index);
/// Gets and resets core performance statistics
- PerfStatsResults GetAndResetPerfStats();
+ [[nodiscard]] PerfStatsResults GetAndResetPerfStats();
/// Gets an ARM interface to the CPU core that is currently running
- ARM_Interface& CurrentArmInterface();
+ [[nodiscard]] ARM_Interface& CurrentArmInterface();
/// Gets an ARM interface to the CPU core that is currently running
- const ARM_Interface& CurrentArmInterface() const;
+ [[nodiscard]] const ARM_Interface& CurrentArmInterface() const;
/// Gets the index of the currently running CPU core
- std::size_t CurrentCoreIndex() const;
+ [[nodiscard]] std::size_t CurrentCoreIndex() const;
/// Gets the scheduler for the CPU core that is currently running
- Kernel::Scheduler& CurrentScheduler();
+ [[nodiscard]] Kernel::Scheduler& CurrentScheduler();
/// Gets the scheduler for the CPU core that is currently running
- const Kernel::Scheduler& CurrentScheduler() const;
+ [[nodiscard]] const Kernel::Scheduler& CurrentScheduler() const;
+
+ /// Gets the physical core for the CPU core that is currently running
+ [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
+
+ /// Gets the physical core for the CPU core that is currently running
+ [[nodiscard]] const Kernel::PhysicalCore& CurrentPhysicalCore() const;
/// Gets a reference to an ARM interface for the CPU core with the specified index
- ARM_Interface& ArmInterface(std::size_t core_index);
+ [[nodiscard]] ARM_Interface& ArmInterface(std::size_t core_index);
/// Gets a const reference to an ARM interface from the CPU core with the specified index
- const ARM_Interface& ArmInterface(std::size_t core_index) const;
+ [[nodiscard]] const ARM_Interface& ArmInterface(std::size_t core_index) const;
- /// Gets a CPU interface to the CPU core with the specified index
- CoreManager& GetCoreManager(std::size_t core_index);
+ /// Gets a reference to the underlying CPU manager.
+ [[nodiscard]] CpuManager& GetCpuManager();
- /// Gets a CPU interface to the CPU core with the specified index
- const CoreManager& GetCoreManager(std::size_t core_index) const;
+ /// Gets a const reference to the underlying CPU manager
+ [[nodiscard]] const CpuManager& GetCpuManager() const;
/// Gets a reference to the exclusive monitor
- ExclusiveMonitor& Monitor();
+ [[nodiscard]] ExclusiveMonitor& Monitor();
/// Gets a constant reference to the exclusive monitor
- const ExclusiveMonitor& Monitor() const;
+ [[nodiscard]] const ExclusiveMonitor& Monitor() const;
/// Gets a mutable reference to the system memory instance.
- Memory::Memory& Memory();
+ [[nodiscard]] Core::Memory::Memory& Memory();
/// Gets a constant reference to the system memory instance.
- const Memory::Memory& Memory() const;
+ [[nodiscard]] const Core::Memory::Memory& Memory() const;
/// Gets a mutable reference to the GPU interface
- Tegra::GPU& GPU();
+ [[nodiscard]] Tegra::GPU& GPU();
/// Gets an immutable reference to the GPU interface.
- const Tegra::GPU& GPU() const;
+ [[nodiscard]] const Tegra::GPU& GPU() const;
/// Gets a mutable reference to the renderer.
- VideoCore::RendererBase& Renderer();
+ [[nodiscard]] VideoCore::RendererBase& Renderer();
/// Gets an immutable reference to the renderer.
- const VideoCore::RendererBase& Renderer() const;
+ [[nodiscard]] const VideoCore::RendererBase& Renderer() const;
/// Gets the scheduler for the CPU core with the specified index
- Kernel::Scheduler& Scheduler(std::size_t core_index);
+ [[nodiscard]] Kernel::Scheduler& Scheduler(std::size_t core_index);
/// Gets the scheduler for the CPU core with the specified index
- const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
+ [[nodiscard]] const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
/// Gets the global scheduler
- Kernel::GlobalScheduler& GlobalScheduler();
+ [[nodiscard]] Kernel::GlobalScheduler& GlobalScheduler();
/// Gets the global scheduler
- const Kernel::GlobalScheduler& GlobalScheduler() const;
+ [[nodiscard]] const Kernel::GlobalScheduler& GlobalScheduler() const;
+
+ /// Gets the manager for the guest device memory
+ [[nodiscard]] Core::DeviceMemory& DeviceMemory();
+
+ /// Gets the manager for the guest device memory
+ [[nodiscard]] const Core::DeviceMemory& DeviceMemory() const;
/// Provides a pointer to the current process
- Kernel::Process* CurrentProcess();
+ [[nodiscard]] Kernel::Process* CurrentProcess();
/// Provides a constant pointer to the current process.
- const Kernel::Process* CurrentProcess() const;
+ [[nodiscard]] const Kernel::Process* CurrentProcess() const;
/// Provides a reference to the core timing instance.
- Timing::CoreTiming& CoreTiming();
+ [[nodiscard]] Timing::CoreTiming& CoreTiming();
/// Provides a constant reference to the core timing instance.
- const Timing::CoreTiming& CoreTiming() const;
+ [[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
/// Provides a reference to the interrupt manager instance.
- Core::Hardware::InterruptManager& InterruptManager();
+ [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
/// Provides a constant reference to the interrupt manager instance.
- const Core::Hardware::InterruptManager& InterruptManager() const;
+ [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
/// Provides a reference to the kernel instance.
- Kernel::KernelCore& Kernel();
+ [[nodiscard]] Kernel::KernelCore& Kernel();
/// Provides a constant reference to the kernel instance.
- const Kernel::KernelCore& Kernel() const;
+ [[nodiscard]] const Kernel::KernelCore& Kernel() const;
/// Provides a reference to the internal PerfStats instance.
- Core::PerfStats& GetPerfStats();
+ [[nodiscard]] Core::PerfStats& GetPerfStats();
/// Provides a constant reference to the internal PerfStats instance.
- const Core::PerfStats& GetPerfStats() const;
+ [[nodiscard]] const Core::PerfStats& GetPerfStats() const;
/// Provides a reference to the frame limiter;
- Core::FrameLimiter& FrameLimiter();
+ [[nodiscard]] Core::FrameLimiter& FrameLimiter();
/// Provides a constant referent to the frame limiter
- const Core::FrameLimiter& FrameLimiter() const;
+ [[nodiscard]] const Core::FrameLimiter& FrameLimiter() const;
/// Gets the name of the current game
- Loader::ResultStatus GetGameName(std::string& out) const;
+ [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
void SetStatus(ResultStatus new_status, const char* details);
- const std::string& GetStatusDetails() const;
+ [[nodiscard]] const std::string& GetStatusDetails() const;
- Loader::AppLoader& GetAppLoader() const;
+ [[nodiscard]] Loader::AppLoader& GetAppLoader();
+ [[nodiscard]] const Loader::AppLoader& GetAppLoader() const;
- Service::SM::ServiceManager& ServiceManager();
- const Service::SM::ServiceManager& ServiceManager() const;
+ [[nodiscard]] Service::SM::ServiceManager& ServiceManager();
+ [[nodiscard]] const Service::SM::ServiceManager& ServiceManager() const;
- void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs);
+ void SetFilesystem(FileSys::VirtualFilesystem vfs);
- std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
+ [[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
u64 main_region_size);
void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
-
void SetDefaultAppletFrontendSet();
- Service::AM::Applets::AppletManager& GetAppletManager();
-
- const Service::AM::Applets::AppletManager& GetAppletManager() const;
+ [[nodiscard]] Service::AM::Applets::AppletManager& GetAppletManager();
+ [[nodiscard]] const Service::AM::Applets::AppletManager& GetAppletManager() const;
void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
- FileSys::ContentProvider& GetContentProvider();
+ [[nodiscard]] FileSys::ContentProvider& GetContentProvider();
+ [[nodiscard]] const FileSys::ContentProvider& GetContentProvider() const;
- const FileSys::ContentProvider& GetContentProvider() const;
-
- Service::FileSystem::FileSystemController& GetFileSystemController();
-
- const Service::FileSystem::FileSystemController& GetFileSystemController() const;
+ [[nodiscard]] Service::FileSystem::FileSystemController& GetFileSystemController();
+ [[nodiscard]] const Service::FileSystem::FileSystemController& GetFileSystemController() const;
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
FileSys::ContentProvider* provider);
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
- const Reporter& GetReporter() const;
-
- Service::Glue::ARPManager& GetARPManager();
-
- const Service::Glue::ARPManager& GetARPManager() const;
+ [[nodiscard]] const Reporter& GetReporter() const;
- Service::APM::Controller& GetAPMController();
+ [[nodiscard]] Service::Glue::ARPManager& GetARPManager();
+ [[nodiscard]] const Service::Glue::ARPManager& GetARPManager() const;
- const Service::APM::Controller& GetAPMController() const;
+ [[nodiscard]] Service::APM::Controller& GetAPMController();
+ [[nodiscard]] const Service::APM::Controller& GetAPMController() const;
- Service::LM::Manager& GetLogManager();
+ [[nodiscard]] Service::LM::Manager& GetLogManager();
+ [[nodiscard]] const Service::LM::Manager& GetLogManager() const;
- const Service::LM::Manager& GetLogManager() const;
+ [[nodiscard]] Service::Time::TimeManager& GetTimeManager();
+ [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
void SetExitLock(bool locked);
-
- bool GetExitLock() const;
+ [[nodiscard]] bool GetExitLock() const;
void SetCurrentProcessBuildID(const CurrentBuildProcessID& id);
-
- const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
+ [[nodiscard]] const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
/// Register a host thread as an emulated CPU Core.
void RegisterCoreThread(std::size_t id);
@@ -366,22 +376,17 @@ public:
/// Register a host thread as an auxiliary thread.
void RegisterHostThread();
-private:
- System();
+ /// Enter Dynarmic Microprofile
+ void EnterDynarmicProfile();
- /// Returns the currently running CPU core
- CoreManager& CurrentCoreManager();
+ /// Exit Dynarmic Microprofile
+ void ExitDynarmicProfile();
- /// Returns the currently running CPU core
- const CoreManager& CurrentCoreManager() const;
+ /// Tells if system is running on multicore.
+ [[nodiscard]] bool IsMulticore() const;
- /**
- * Initialize the emulated system.
- * @param emu_window Reference to the host-system window used for video output and keyboard
- * input.
- * @return ResultStatus code, indicating if the operation succeeded.
- */
- ResultStatus Init(Frontend::EmuWindow& emu_window);
+private:
+ System();
struct Impl;
std::unique_ptr<Impl> impl;
diff --git a/src/core/core_manager.cpp b/src/core/core_manager.cpp
deleted file mode 100644
index b6b797c80..000000000
--- a/src/core/core_manager.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <condition_variable>
-#include <mutex>
-
-#include "common/logging/log.h"
-#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-#include "core/core.h"
-#include "core/core_manager.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/physical_core.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/lock.h"
-#include "core/settings.h"
-
-namespace Core {
-
-CoreManager::CoreManager(System& system, std::size_t core_index)
- : global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
- core_index)},
- core_timing{system.CoreTiming()}, core_index{core_index} {}
-
-CoreManager::~CoreManager() = default;
-
-void CoreManager::RunLoop(bool tight_loop) {
- Reschedule();
-
- // If we don't have a currently active thread then don't execute instructions,
- // instead advance to the next event and try to yield to the next thread
- if (Kernel::GetCurrentThread() == nullptr) {
- LOG_TRACE(Core, "Core-{} idling", core_index);
- core_timing.Idle();
- } else {
- if (tight_loop) {
- physical_core.Run();
- } else {
- physical_core.Step();
- }
- }
- core_timing.Advance();
-
- Reschedule();
-}
-
-void CoreManager::SingleStep() {
- return RunLoop(false);
-}
-
-void CoreManager::PrepareReschedule() {
- physical_core.Stop();
-}
-
-void CoreManager::Reschedule() {
- // Lock the global kernel mutex when we manipulate the HLE state
- std::lock_guard lock(HLE::g_hle_lock);
-
- global_scheduler.SelectThread(core_index);
-
- physical_core.Scheduler().TryDoContextSwitch();
-}
-
-} // namespace Core
diff --git a/src/core/core_manager.h b/src/core/core_manager.h
deleted file mode 100644
index b14e723d7..000000000
--- a/src/core/core_manager.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <atomic>
-#include <cstddef>
-#include <memory>
-#include "common/common_types.h"
-
-namespace Kernel {
-class GlobalScheduler;
-class PhysicalCore;
-} // namespace Kernel
-
-namespace Core {
-class System;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-}
-
-namespace Memory {
-class Memory;
-}
-
-namespace Core {
-
-constexpr unsigned NUM_CPU_CORES{4};
-
-class CoreManager {
-public:
- CoreManager(System& system, std::size_t core_index);
- ~CoreManager();
-
- void RunLoop(bool tight_loop = true);
-
- void SingleStep();
-
- void PrepareReschedule();
-
- bool IsMainCore() const {
- return core_index == 0;
- }
-
- std::size_t CoreIndex() const {
- return core_index;
- }
-
-private:
- void Reschedule();
-
- Kernel::GlobalScheduler& global_scheduler;
- Kernel::PhysicalCore& physical_core;
- Timing::CoreTiming& core_timing;
-
- std::atomic<bool> reschedule_pending = false;
- std::size_t core_index;
-};
-
-} // namespace Core
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 46d4178c4..e6c8461a5 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -1,31 +1,29 @@
-// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
-// Licensed under GPLv2+
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/core_timing.h"
-
#include <algorithm>
#include <mutex>
#include <string>
#include <tuple>
-#include "common/assert.h"
-#include "common/thread.h"
+#include "common/microprofile.h"
+#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hardware_properties.h"
namespace Core::Timing {
-constexpr int MAX_SLICE_LENGTH = 10000;
+constexpr s64 MAX_SLICE_LENGTH = 4000;
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
return std::make_shared<EventType>(std::move(callback), std::move(name));
}
struct CoreTiming::Event {
- s64 time;
+ u64 time;
u64 fifo_order;
- u64 userdata;
+ std::uintptr_t user_data;
std::weak_ptr<EventType> type;
// Sort by time, unless the times are the same, in which case sort by
@@ -39,53 +37,92 @@ struct CoreTiming::Event {
}
};
-CoreTiming::CoreTiming() = default;
-CoreTiming::~CoreTiming() = default;
+CoreTiming::CoreTiming()
+ : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
-void CoreTiming::Initialize() {
- downcounts.fill(MAX_SLICE_LENGTH);
- time_slice.fill(MAX_SLICE_LENGTH);
- slice_length = MAX_SLICE_LENGTH;
- global_timer = 0;
- idled_cycles = 0;
- current_context = 0;
+CoreTiming::~CoreTiming() = default;
- // The time between CoreTiming being initialized and the first call to Advance() is considered
- // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
- // executing the first cycle of each slice to prepare the slice length and downcount for
- // that slice.
- is_global_timer_sane = true;
+void CoreTiming::ThreadEntry(CoreTiming& instance) {
+ constexpr char name[] = "yuzu:HostTiming";
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
+ instance.on_thread_init();
+ instance.ThreadLoop();
+}
+void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
+ on_thread_init = std::move(on_thread_init_);
event_fifo_id = 0;
-
- const auto empty_timed_callback = [](u64, s64) {};
+ shutting_down = false;
+ ticks = 0;
+ const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {};
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
+ if (is_multicore) {
+ timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
+ }
}
void CoreTiming::Shutdown() {
+ paused = true;
+ shutting_down = true;
+ pause_event.Set();
+ event.Set();
+ if (timer_thread) {
+ timer_thread->join();
+ }
ClearPendingEvents();
+ timer_thread.reset();
+ has_started = false;
}
-void CoreTiming::ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type,
- u64 userdata) {
- std::lock_guard guard{inner_mutex};
- const s64 timeout = GetTicks() + cycles_into_future;
+void CoreTiming::Pause(bool is_paused) {
+ paused = is_paused;
+ pause_event.Set();
+}
- // If this event needs to be scheduled before the next advance(), force one early
- if (!is_global_timer_sane) {
- ForceExceptionCheck(cycles_into_future);
+void CoreTiming::SyncPause(bool is_paused) {
+ if (is_paused == paused && paused_set == paused) {
+ return;
}
+ Pause(is_paused);
+ if (timer_thread) {
+ if (!is_paused) {
+ pause_event.Set();
+ }
+ event.Set();
+ while (paused_set != is_paused)
+ ;
+ }
+}
- event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
+bool CoreTiming::IsRunning() const {
+ return !paused_set;
+}
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+bool CoreTiming::HasPendingEvents() const {
+ return !(wait_set && event_queue.empty());
}
-void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) {
- std::lock_guard guard{inner_mutex};
+void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
+ const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data) {
+ {
+ std::scoped_lock scope{basic_lock};
+ const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
+
+ event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type});
+ std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ }
+ event.Set();
+}
+
+void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data) {
+ std::scoped_lock scope{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
- return e.type.lock().get() == event_type.get() && e.userdata == userdata;
+ return e.type.lock().get() == event_type.get() && e.user_data == user_data;
});
// Removing random items breaks the invariant so we have to re-establish it.
@@ -95,21 +132,39 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u
}
}
-u64 CoreTiming::GetTicks() const {
- u64 ticks = static_cast<u64>(global_timer);
- if (!is_global_timer_sane) {
- ticks += accumulated_ticks;
+void CoreTiming::AddTicks(u64 ticks) {
+ this->ticks += ticks;
+ downcount -= static_cast<s64>(ticks);
+}
+
+void CoreTiming::Idle() {
+ if (!event_queue.empty()) {
+ const u64 next_event_time = event_queue.front().time;
+ const u64 next_ticks = nsToCycles(std::chrono::nanoseconds(next_event_time)) + 10U;
+ if (next_ticks > ticks) {
+ ticks = next_ticks;
+ }
+ return;
}
- return ticks;
+ ticks += 1000U;
}
-u64 CoreTiming::GetIdleTicks() const {
- return static_cast<u64>(idled_cycles);
+void CoreTiming::ResetTicks() {
+ downcount = MAX_SLICE_LENGTH;
}
-void CoreTiming::AddTicks(u64 ticks) {
- accumulated_ticks += ticks;
- downcounts[current_context] -= static_cast<s64>(ticks);
+u64 CoreTiming::GetCPUTicks() const {
+ if (is_multicore) {
+ return clock->GetCPUCycles();
+ }
+ return ticks;
+}
+
+u64 CoreTiming::GetClockTicks() const {
+ if (is_multicore) {
+ return clock->GetClockCycles();
+ }
+ return CpuCyclesToClockCycles(ticks);
}
void CoreTiming::ClearPendingEvents() {
@@ -117,7 +172,7 @@ void CoreTiming::ClearPendingEvents() {
}
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
- std::lock_guard guard{inner_mutex};
+ std::scoped_lock lock{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get();
@@ -130,97 +185,69 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
}
}
-void CoreTiming::ForceExceptionCheck(s64 cycles) {
- cycles = std::max<s64>(0, cycles);
- if (downcounts[current_context] <= cycles) {
- return;
- }
-
- // downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int
- // here. Account for cycles already executed by adjusting the g.slice_length
- downcounts[current_context] = static_cast<int>(cycles);
-}
-
-std::optional<u64> CoreTiming::NextAvailableCore(const s64 needed_ticks) const {
- const u64 original_context = current_context;
- u64 next_context = (original_context + 1) % num_cpu_cores;
- while (next_context != original_context) {
- if (time_slice[next_context] >= needed_ticks) {
- return {next_context};
- } else if (time_slice[next_context] >= 0) {
- return std::nullopt;
- }
- next_context = (next_context + 1) % num_cpu_cores;
- }
- return std::nullopt;
-}
-
-void CoreTiming::Advance() {
- std::unique_lock<std::mutex> guard(inner_mutex);
-
- const u64 cycles_executed = accumulated_ticks;
- time_slice[current_context] = std::max<s64>(0, time_slice[current_context] - accumulated_ticks);
- global_timer += cycles_executed;
-
- is_global_timer_sane = true;
+std::optional<s64> CoreTiming::Advance() {
+ std::scoped_lock lock{advance_lock, basic_lock};
+ global_timer = GetGlobalTimeNs().count();
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back();
- inner_mutex.unlock();
+ basic_lock.unlock();
- if (auto event_type{evt.type.lock()}) {
- event_type->callback(evt.userdata, global_timer - evt.time);
+ if (const auto event_type{evt.type.lock()}) {
+ event_type->callback(
+ evt.user_data, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)});
}
- inner_mutex.lock();
+ basic_lock.lock();
+ global_timer = GetGlobalTimeNs().count();
}
- is_global_timer_sane = false;
-
- // Still events left (scheduled in the future)
if (!event_queue.empty()) {
- const s64 needed_ticks =
- std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH);
- const auto next_core = NextAvailableCore(needed_ticks);
- if (next_core) {
- downcounts[*next_core] = needed_ticks;
- }
+ const s64 next_time = event_queue.front().time - global_timer;
+ return next_time;
+ } else {
+ return std::nullopt;
}
-
- accumulated_ticks = 0;
-
- downcounts[current_context] = time_slice[current_context];
}
-void CoreTiming::ResetRun() {
- downcounts.fill(MAX_SLICE_LENGTH);
- time_slice.fill(MAX_SLICE_LENGTH);
- current_context = 0;
- // Still events left (scheduled in the future)
- if (!event_queue.empty()) {
- const s64 needed_ticks =
- std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH);
- downcounts[current_context] = needed_ticks;
+void CoreTiming::ThreadLoop() {
+ has_started = true;
+ while (!shutting_down) {
+ while (!paused) {
+ paused_set = false;
+ const auto next_time = Advance();
+ if (next_time) {
+ if (*next_time > 0) {
+ std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
+ event.WaitFor(next_time_ns);
+ }
+ } else {
+ wait_set = true;
+ event.Wait();
+ }
+ wait_set = false;
+ }
+ paused_set = true;
+ clock->Pause(true);
+ pause_event.Wait();
+ clock->Pause(false);
}
-
- is_global_timer_sane = false;
- accumulated_ticks = 0;
}
-void CoreTiming::Idle() {
- accumulated_ticks += downcounts[current_context];
- idled_cycles += downcounts[current_context];
- downcounts[current_context] = 0;
+std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
+ if (is_multicore) {
+ return clock->GetTimeNS();
+ }
+ return CyclesToNs(ticks);
}
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
- return std::chrono::microseconds{GetTicks() * 1000000 / Hardware::BASE_CLOCK_RATE};
-}
-
-s64 CoreTiming::GetDowncount() const {
- return downcounts[current_context];
+ if (is_multicore) {
+ return clock->GetTimeUS();
+ }
+ return CyclesToUs(ticks);
}
} // namespace Core::Timing
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index d50f4eb8a..b0b6036e4 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -1,24 +1,29 @@
-// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
-// Licensed under GPLv2+
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
+#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
+#include <thread>
#include <vector>
#include "common/common_types.h"
-#include "common/threadsafe_queue.h"
+#include "common/spin_lock.h"
+#include "common/thread.h"
+#include "common/wall_clock.h"
namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
-using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>;
+using TimedCallback =
+ std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>;
/// Contains the characteristics of a particular event.
struct EventType {
@@ -36,12 +41,12 @@ struct EventType {
* in main CPU clock cycles.
*
* To schedule an event, you first have to register its type. This is where you pass in the
- * callback. You then schedule events using the type id you get back.
+ * callback. You then schedule events using the type ID you get back.
*
- * The int cyclesLate that the callbacks get is how many cycles late it was.
+ * The s64 ns_late that the callbacks get is how many ns late it was.
* So to schedule a new event on a regular basis:
* inside callback:
- * ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
+ * ScheduleEvent(period_in_ns - ns_late, callback, "whatever")
*/
class CoreTiming {
public:
@@ -56,58 +61,71 @@ public:
/// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
/// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
- void Initialize();
+ void Initialize(std::function<void()>&& on_thread_init_);
/// Tears down all timing related functionality.
void Shutdown();
- /// After the first Advance, the slice lengths and the downcount will be reduced whenever an
- /// event is scheduled earlier than the current values.
- ///
- /// Scheduling from a callback will not update the downcount until the Advance() completes.
- void ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type,
- u64 userdata = 0);
+ /// Sets if emulation is multicore or single core, must be set before Initialize
+ void SetMulticore(bool is_multicore) {
+ this->is_multicore = is_multicore;
+ }
- void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
+ /// Check if it's using host timing.
+ bool IsHostTiming() const {
+ return is_multicore;
+ }
- /// We only permit one event of each type in the queue at a time.
- void RemoveEvent(const std::shared_ptr<EventType>& event_type);
+ /// Pauses/Unpauses the execution of the timer thread.
+ void Pause(bool is_paused);
- void ForceExceptionCheck(s64 cycles);
+ /// Pauses/Unpauses the execution of the timer thread and waits until paused.
+ void SyncPause(bool is_paused);
- /// This should only be called from the emu thread, if you are calling it any other thread,
- /// you are doing something evil
- u64 GetTicks() const;
+ /// Checks if core timing is running.
+ bool IsRunning() const;
- u64 GetIdleTicks() const;
+ /// Checks if the timer thread has started.
+ bool HasStarted() const {
+ return has_started;
+ }
+
+ /// Checks if there are any pending time events.
+ bool HasPendingEvents() const;
+
+ /// Schedules an event in core timing
+ void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
+ const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0);
+
+ void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
+
+ /// We only permit one event of each type in the queue at a time.
+ void RemoveEvent(const std::shared_ptr<EventType>& event_type);
void AddTicks(u64 ticks);
- /// Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
- /// the previous timing slice and begins the next one, you must Advance from the previous
- /// slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
- /// Advance() is required to initialize the slice length before the first cycle of emulated
- /// instructions is executed.
- void Advance();
+ void ResetTicks();
- /// Pretend that the main CPU has executed enough cycles to reach the next event.
void Idle();
- std::chrono::microseconds GetGlobalTimeUs() const;
+ s64 GetDowncount() const {
+ return downcount;
+ }
- void ResetRun();
+ /// Returns current time in emulated CPU cycles
+ u64 GetCPUTicks() const;
- s64 GetDowncount() const;
+ /// Returns current time in emulated in Clock cycles
+ u64 GetClockTicks() const;
- void SwitchContext(u64 new_context) {
- current_context = new_context;
- }
+ /// Returns current time in microseconds.
+ std::chrono::microseconds GetGlobalTimeUs() const;
- bool CanCurrentContextRun() const {
- return time_slice[current_context] > 0;
- }
+ /// Returns current time in nanoseconds.
+ std::chrono::nanoseconds GetGlobalTimeNs() const;
- std::optional<u64> NextAvailableCore(const s64 needed_ticks) const;
+ /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
+ std::optional<s64> Advance();
private:
struct Event;
@@ -115,21 +133,12 @@ private:
/// Clear all pending events. This should ONLY be done on exit.
void ClearPendingEvents();
- static constexpr u64 num_cpu_cores = 4;
+ static void ThreadEntry(CoreTiming& instance);
+ void ThreadLoop();
- s64 global_timer = 0;
- s64 idled_cycles = 0;
- s64 slice_length = 0;
- u64 accumulated_ticks = 0;
- std::array<s64, num_cpu_cores> downcounts{};
- // Slice of time assigned to each core per run.
- std::array<s64, num_cpu_cores> time_slice{};
- u64 current_context = 0;
+ std::unique_ptr<Common::WallClock> clock;
- // Are we in a function that has been called from Advance()
- // If events are scheduled from a function that gets called from Advance(),
- // don't change slice_length and downcount.
- bool is_global_timer_sane = false;
+ u64 global_timer = 0;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
@@ -139,8 +148,23 @@ private:
u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost;
-
- std::mutex inner_mutex;
+ Common::Event event{};
+ Common::Event pause_event{};
+ Common::SpinLock basic_lock{};
+ Common::SpinLock advance_lock{};
+ std::unique_ptr<std::thread> timer_thread;
+ std::atomic<bool> paused{};
+ std::atomic<bool> paused_set{};
+ std::atomic<bool> wait_set{};
+ std::atomic<bool> shutting_down{};
+ std::atomic<bool> has_started{};
+ std::function<void()> on_thread_init{};
+
+ bool is_multicore{};
+
+ /// Cycle timing
+ u64 ticks{};
+ s64 downcount{};
};
/// Creates a core timing event with the given name and callback.
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index de50d3b14..8ce8e602e 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -8,6 +8,7 @@
#include <limits>
#include "common/logging/log.h"
#include "common/uint128.h"
+#include "core/hardware_properties.h"
namespace Core::Timing {
@@ -38,15 +39,23 @@ s64 usToCycles(std::chrono::microseconds us) {
}
s64 nsToCycles(std::chrono::nanoseconds ns) {
- if (static_cast<u64>(ns.count() / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
- LOG_ERROR(Core_Timing, "Integer overflow, use max value");
- return std::numeric_limits<s64>::max();
- }
- if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) {
- LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return Hardware::BASE_CLOCK_RATE * (ns.count() / 1000000000);
- }
- return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000;
+ const u128 temporal = Common::Multiply64Into128(ns.count(), Hardware::BASE_CLOCK_RATE);
+ return Common::Divide128On32(temporal, static_cast<u32>(1000000000)).first;
+}
+
+u64 msToClockCycles(std::chrono::milliseconds ns) {
+ const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
+ return Common::Divide128On32(temp, 1000).first;
+}
+
+u64 usToClockCycles(std::chrono::microseconds ns) {
+ const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
+ return Common::Divide128On32(temp, 1000000).first;
+}
+
+u64 nsToClockCycles(std::chrono::nanoseconds ns) {
+ const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
+ return Common::Divide128On32(temp, 1000000000).first;
}
u64 CpuCyclesToClockCycles(u64 ticks) {
@@ -54,4 +63,22 @@ u64 CpuCyclesToClockCycles(u64 ticks) {
return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
}
+std::chrono::milliseconds CyclesToMs(s64 cycles) {
+ const u128 temporal = Common::Multiply64Into128(cycles, 1000);
+ u64 ms = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
+ return std::chrono::milliseconds(ms);
+}
+
+std::chrono::nanoseconds CyclesToNs(s64 cycles) {
+ const u128 temporal = Common::Multiply64Into128(cycles, 1000000000);
+ u64 ns = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
+ return std::chrono::nanoseconds(ns);
+}
+
+std::chrono::microseconds CyclesToUs(s64 cycles) {
+ const u128 temporal = Common::Multiply64Into128(cycles, 1000000);
+ u64 us = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
+ return std::chrono::microseconds(us);
+}
+
} // namespace Core::Timing
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index addc72b19..e4a046bf9 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -6,25 +6,18 @@
#include <chrono>
#include "common/common_types.h"
-#include "core/hardware_properties.h"
namespace Core::Timing {
s64 msToCycles(std::chrono::milliseconds ms);
s64 usToCycles(std::chrono::microseconds us);
s64 nsToCycles(std::chrono::nanoseconds ns);
-
-inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
- return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE);
-}
-
-inline std::chrono::nanoseconds CyclesToNs(s64 cycles) {
- return std::chrono::nanoseconds(cycles * 1000000000 / Hardware::BASE_CLOCK_RATE);
-}
-
-inline std::chrono::microseconds CyclesToUs(s64 cycles) {
- return std::chrono::microseconds(cycles * 1000000 / Hardware::BASE_CLOCK_RATE);
-}
+u64 msToClockCycles(std::chrono::milliseconds ns);
+u64 usToClockCycles(std::chrono::microseconds ns);
+u64 nsToClockCycles(std::chrono::nanoseconds ns);
+std::chrono::milliseconds CyclesToMs(s64 cycles);
+std::chrono::nanoseconds CyclesToNs(s64 cycles);
+std::chrono::microseconds CyclesToUs(s64 cycles);
u64 CpuCyclesToClockCycles(u64 ticks);
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 70ddbdcca..983210197 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -2,80 +2,371 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/fiber.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
+#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/thread.h"
+#include "video_core/gpu.h"
namespace Core {
CpuManager::CpuManager(System& system) : system{system} {}
CpuManager::~CpuManager() = default;
+void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) {
+ cpu_manager.RunThread(core);
+}
+
void CpuManager::Initialize() {
- for (std::size_t index = 0; index < core_managers.size(); ++index) {
- core_managers[index] = std::make_unique<CoreManager>(system, index);
+ running_mode = true;
+ if (is_multicore) {
+ for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ core_data[core].host_thread =
+ std::make_unique<std::thread>(ThreadStart, std::ref(*this), core);
+ }
+ } else {
+ core_data[0].host_thread = std::make_unique<std::thread>(ThreadStart, std::ref(*this), 0);
}
}
void CpuManager::Shutdown() {
- for (auto& cpu_core : core_managers) {
- cpu_core.reset();
+ running_mode = false;
+ Pause(false);
+ if (is_multicore) {
+ for (auto& data : core_data) {
+ data.host_thread->join();
+ data.host_thread.reset();
+ }
+ } else {
+ core_data[0].host_thread->join();
+ core_data[0].host_thread.reset();
+ }
+}
+
+std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
+ return GuestThreadFunction;
+}
+
+std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
+ return IdleThreadFunction;
+}
+
+std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
+ return SuspendThreadFunction;
+}
+
+void CpuManager::GuestThreadFunction(void* cpu_manager_) {
+ CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
+ if (cpu_manager->is_multicore) {
+ cpu_manager->MultiCoreRunGuestThread();
+ } else {
+ cpu_manager->SingleCoreRunGuestThread();
+ }
+}
+
+void CpuManager::GuestRewindFunction(void* cpu_manager_) {
+ CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
+ if (cpu_manager->is_multicore) {
+ cpu_manager->MultiCoreRunGuestLoop();
+ } else {
+ cpu_manager->SingleCoreRunGuestLoop();
+ }
+}
+
+void CpuManager::IdleThreadFunction(void* cpu_manager_) {
+ CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
+ if (cpu_manager->is_multicore) {
+ cpu_manager->MultiCoreRunIdleThread();
+ } else {
+ cpu_manager->SingleCoreRunIdleThread();
}
}
-CoreManager& CpuManager::GetCoreManager(std::size_t index) {
- return *core_managers.at(index);
+void CpuManager::SuspendThreadFunction(void* cpu_manager_) {
+ CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
+ if (cpu_manager->is_multicore) {
+ cpu_manager->MultiCoreRunSuspendThread();
+ } else {
+ cpu_manager->SingleCoreRunSuspendThread();
+ }
}
-const CoreManager& CpuManager::GetCoreManager(std::size_t index) const {
- return *core_managers.at(index);
+void* CpuManager::GetStartFuncParamater() {
+ return static_cast<void*>(this);
}
-CoreManager& CpuManager::GetCurrentCoreManager() {
- // Otherwise, use single-threaded mode active_core variable
- return *core_managers[active_core];
+///////////////////////////////////////////////////////////////////////////////
+/// MultiCore ///
+///////////////////////////////////////////////////////////////////////////////
+
+void CpuManager::MultiCoreRunGuestThread() {
+ auto& kernel = system.Kernel();
+ {
+ auto& sched = kernel.CurrentScheduler();
+ sched.OnThreadStart();
+ }
+ MultiCoreRunGuestLoop();
}
-const CoreManager& CpuManager::GetCurrentCoreManager() const {
- // Otherwise, use single-threaded mode active_core variable
- return *core_managers[active_core];
+void CpuManager::MultiCoreRunGuestLoop() {
+ auto& kernel = system.Kernel();
+ auto* thread = kernel.CurrentScheduler().GetCurrentThread();
+ while (true) {
+ auto* physical_core = &kernel.CurrentPhysicalCore();
+ auto& arm_interface = thread->ArmInterface();
+ system.EnterDynarmicProfile();
+ while (!physical_core->IsInterrupted()) {
+ arm_interface.Run();
+ physical_core = &kernel.CurrentPhysicalCore();
+ }
+ system.ExitDynarmicProfile();
+ arm_interface.ClearExclusiveState();
+ auto& scheduler = kernel.CurrentScheduler();
+ scheduler.TryDoContextSwitch();
+ }
}
-void CpuManager::RunLoop(bool tight_loop) {
- if (GDBStub::IsServerEnabled()) {
- GDBStub::HandlePacket();
+void CpuManager::MultiCoreRunIdleThread() {
+ auto& kernel = system.Kernel();
+ while (true) {
+ auto& physical_core = kernel.CurrentPhysicalCore();
+ physical_core.Idle();
+ auto& scheduler = kernel.CurrentScheduler();
+ scheduler.TryDoContextSwitch();
+ }
+}
- // If the loop is halted and we want to step, use a tiny (1) number of instructions to
- // execute. Otherwise, get out of the loop function.
- if (GDBStub::GetCpuHaltFlag()) {
- if (GDBStub::GetCpuStepFlag()) {
- tight_loop = false;
- } else {
- return;
+void CpuManager::MultiCoreRunSuspendThread() {
+ auto& kernel = system.Kernel();
+ {
+ auto& sched = kernel.CurrentScheduler();
+ sched.OnThreadStart();
+ }
+ while (true) {
+ auto core = kernel.GetCurrentHostThreadID();
+ auto& scheduler = kernel.CurrentScheduler();
+ Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
+ ASSERT(scheduler.ContextSwitchPending());
+ ASSERT(core == kernel.GetCurrentHostThreadID());
+ scheduler.TryDoContextSwitch();
+ }
+}
+
+void CpuManager::MultiCorePause(bool paused) {
+ if (!paused) {
+ bool all_not_barrier = false;
+ while (!all_not_barrier) {
+ all_not_barrier = true;
+ for (const auto& data : core_data) {
+ all_not_barrier &= !data.is_running.load() && data.initialized.load();
+ }
+ }
+ for (auto& data : core_data) {
+ data.enter_barrier->Set();
+ }
+ if (paused_state.load()) {
+ bool all_barrier = false;
+ while (!all_barrier) {
+ all_barrier = true;
+ for (const auto& data : core_data) {
+ all_barrier &= data.is_paused.load() && data.initialized.load();
+ }
+ }
+ for (auto& data : core_data) {
+ data.exit_barrier->Set();
}
}
+ } else {
+ /// Wait until all cores are paused.
+ bool all_barrier = false;
+ while (!all_barrier) {
+ all_barrier = true;
+ for (const auto& data : core_data) {
+ all_barrier &= data.is_paused.load() && data.initialized.load();
+ }
+ }
+ /// Don't release the barrier
}
+ paused_state = paused;
+}
- auto& core_timing = system.CoreTiming();
- core_timing.ResetRun();
- bool keep_running{};
- do {
- keep_running = false;
- for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
- core_timing.SwitchContext(active_core);
- if (core_timing.CanCurrentContextRun()) {
- core_managers[active_core]->RunLoop(tight_loop);
+///////////////////////////////////////////////////////////////////////////////
+/// SingleCore ///
+///////////////////////////////////////////////////////////////////////////////
+
+void CpuManager::SingleCoreRunGuestThread() {
+ auto& kernel = system.Kernel();
+ {
+ auto& sched = kernel.CurrentScheduler();
+ sched.OnThreadStart();
+ }
+ SingleCoreRunGuestLoop();
+}
+
+void CpuManager::SingleCoreRunGuestLoop() {
+ auto& kernel = system.Kernel();
+ auto* thread = kernel.CurrentScheduler().GetCurrentThread();
+ while (true) {
+ auto* physical_core = &kernel.CurrentPhysicalCore();
+ auto& arm_interface = thread->ArmInterface();
+ system.EnterDynarmicProfile();
+ if (!physical_core->IsInterrupted()) {
+ arm_interface.Run();
+ physical_core = &kernel.CurrentPhysicalCore();
+ }
+ system.ExitDynarmicProfile();
+ thread->SetPhantomMode(true);
+ system.CoreTiming().Advance();
+ thread->SetPhantomMode(false);
+ arm_interface.ClearExclusiveState();
+ PreemptSingleCore();
+ auto& scheduler = kernel.Scheduler(current_core);
+ scheduler.TryDoContextSwitch();
+ }
+}
+
+void CpuManager::SingleCoreRunIdleThread() {
+ auto& kernel = system.Kernel();
+ while (true) {
+ auto& physical_core = kernel.CurrentPhysicalCore();
+ PreemptSingleCore(false);
+ system.CoreTiming().AddTicks(1000U);
+ idle_count++;
+ auto& scheduler = physical_core.Scheduler();
+ scheduler.TryDoContextSwitch();
+ }
+}
+
+void CpuManager::SingleCoreRunSuspendThread() {
+ auto& kernel = system.Kernel();
+ {
+ auto& sched = kernel.CurrentScheduler();
+ sched.OnThreadStart();
+ }
+ while (true) {
+ auto core = kernel.GetCurrentHostThreadID();
+ auto& scheduler = kernel.CurrentScheduler();
+ Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
+ ASSERT(scheduler.ContextSwitchPending());
+ ASSERT(core == kernel.GetCurrentHostThreadID());
+ scheduler.TryDoContextSwitch();
+ }
+}
+
+void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
+ std::size_t old_core = current_core;
+ auto& scheduler = system.Kernel().Scheduler(old_core);
+ Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ if (idle_count >= 4 || from_running_enviroment) {
+ if (!from_running_enviroment) {
+ system.CoreTiming().Idle();
+ idle_count = 0;
+ }
+ current_thread->SetPhantomMode(true);
+ system.CoreTiming().Advance();
+ current_thread->SetPhantomMode(false);
+ }
+ current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
+ system.CoreTiming().ResetTicks();
+ scheduler.Unload();
+ auto& next_scheduler = system.Kernel().Scheduler(current_core);
+ Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
+ /// May have changed scheduler
+ auto& current_scheduler = system.Kernel().Scheduler(current_core);
+ current_scheduler.Reload();
+ auto* currrent_thread2 = current_scheduler.GetCurrentThread();
+ if (!currrent_thread2->IsIdleThread()) {
+ idle_count = 0;
+ }
+}
+
+void CpuManager::SingleCorePause(bool paused) {
+ if (!paused) {
+ bool all_not_barrier = false;
+ while (!all_not_barrier) {
+ all_not_barrier = !core_data[0].is_running.load() && core_data[0].initialized.load();
+ }
+ core_data[0].enter_barrier->Set();
+ if (paused_state.load()) {
+ bool all_barrier = false;
+ while (!all_barrier) {
+ all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
}
- keep_running |= core_timing.CanCurrentContextRun();
+ core_data[0].exit_barrier->Set();
+ }
+ } else {
+ /// Wait until all cores are paused.
+ bool all_barrier = false;
+ while (!all_barrier) {
+ all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
}
- } while (keep_running);
+ /// Don't release the barrier
+ }
+ paused_state = paused;
+}
+
+void CpuManager::Pause(bool paused) {
+ if (is_multicore) {
+ MultiCorePause(paused);
+ } else {
+ SingleCorePause(paused);
+ }
+}
- if (GDBStub::IsServerEnabled()) {
- GDBStub::SetCpuStepFlag(false);
+void CpuManager::RunThread(std::size_t core) {
+ /// Initialization
+ system.RegisterCoreThread(core);
+ std::string name;
+ if (is_multicore) {
+ name = "yuzu:CPUCore_" + std::to_string(core);
+ } else {
+ name = "yuzu:CPUThread";
+ }
+ MicroProfileOnThreadCreate(name.c_str());
+ Common::SetCurrentThreadName(name.c_str());
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ auto& data = core_data[core];
+ data.enter_barrier = std::make_unique<Common::Event>();
+ data.exit_barrier = std::make_unique<Common::Event>();
+ data.host_context = Common::Fiber::ThreadToFiber();
+ data.is_running = false;
+ data.initialized = true;
+ const bool sc_sync = !is_async_gpu && !is_multicore;
+ bool sc_sync_first_use = sc_sync;
+ /// Running
+ while (running_mode) {
+ data.is_running = false;
+ data.enter_barrier->Wait();
+ if (sc_sync_first_use) {
+ system.GPU().ObtainContext();
+ sc_sync_first_use = false;
+ }
+ auto& scheduler = system.Kernel().CurrentScheduler();
+ Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ data.is_running = true;
+ Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
+ data.is_running = false;
+ data.is_paused = true;
+ data.exit_barrier->Wait();
+ data.is_paused = false;
}
+ /// Time to cleanup
+ data.host_context->Exit();
+ data.enter_barrier.reset();
+ data.exit_barrier.reset();
+ data.initialized = false;
+
+ MicroProfileOnThreadExit();
}
} // namespace Core
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
index 97554d1bb..17420c941 100644
--- a/src/core/cpu_manager.h
+++ b/src/core/cpu_manager.h
@@ -5,12 +5,22 @@
#pragma once
#include <array>
+#include <atomic>
+#include <functional>
#include <memory>
+#include <thread>
+
+#include "common/fiber.h"
+#include "common/thread.h"
#include "core/hardware_properties.h"
+namespace Common {
+class Event;
+class Fiber;
+} // namespace Common
+
namespace Core {
-class CoreManager;
class System;
class CpuManager {
@@ -24,24 +34,74 @@ public:
CpuManager& operator=(const CpuManager&) = delete;
CpuManager& operator=(CpuManager&&) = delete;
+ /// Sets if emulation is multicore or single core, must be set before Initialize
+ void SetMulticore(bool is_multicore) {
+ this->is_multicore = is_multicore;
+ }
+
+ /// Sets if emulation is using an asynchronous GPU.
+ void SetAsyncGpu(bool is_async_gpu) {
+ this->is_async_gpu = is_async_gpu;
+ }
+
void Initialize();
void Shutdown();
- CoreManager& GetCoreManager(std::size_t index);
- const CoreManager& GetCoreManager(std::size_t index) const;
+ void Pause(bool paused);
- CoreManager& GetCurrentCoreManager();
- const CoreManager& GetCurrentCoreManager() const;
+ static std::function<void(void*)> GetGuestThreadStartFunc();
+ static std::function<void(void*)> GetIdleThreadStartFunc();
+ static std::function<void(void*)> GetSuspendThreadStartFunc();
+ void* GetStartFuncParamater();
- std::size_t GetActiveCoreIndex() const {
- return active_core;
- }
+ void PreemptSingleCore(bool from_running_enviroment = true);
- void RunLoop(bool tight_loop);
+ std::size_t CurrentCore() const {
+ return current_core.load();
+ }
private:
- std::array<std::unique_ptr<CoreManager>, Hardware::NUM_CPU_CORES> core_managers;
- std::size_t active_core{}; ///< Active core, only used in single thread mode
+ static void GuestThreadFunction(void* cpu_manager);
+ static void GuestRewindFunction(void* cpu_manager);
+ static void IdleThreadFunction(void* cpu_manager);
+ static void SuspendThreadFunction(void* cpu_manager);
+
+ void MultiCoreRunGuestThread();
+ void MultiCoreRunGuestLoop();
+ void MultiCoreRunIdleThread();
+ void MultiCoreRunSuspendThread();
+ void MultiCorePause(bool paused);
+
+ void SingleCoreRunGuestThread();
+ void SingleCoreRunGuestLoop();
+ void SingleCoreRunIdleThread();
+ void SingleCoreRunSuspendThread();
+ void SingleCorePause(bool paused);
+
+ static void ThreadStart(CpuManager& cpu_manager, std::size_t core);
+
+ void RunThread(std::size_t core);
+
+ struct CoreData {
+ std::shared_ptr<Common::Fiber> host_context;
+ std::unique_ptr<Common::Event> enter_barrier;
+ std::unique_ptr<Common::Event> exit_barrier;
+ std::atomic<bool> is_running;
+ std::atomic<bool> is_paused;
+ std::atomic<bool> initialized;
+ std::unique_ptr<std::thread> host_thread;
+ };
+
+ std::atomic<bool> running_mode{};
+ std::atomic<bool> paused_state{};
+
+ std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
+
+ bool is_async_gpu{};
+ bool is_multicore{};
+ std::atomic<std::size_t> current_core{};
+ std::size_t idle_count{};
+ static constexpr std::size_t max_cycle_runs = 5;
System& system;
};
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 4be76bb43..6a9734812 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <mbedtls/cipher.h>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -10,8 +11,10 @@
namespace Core::Crypto {
namespace {
-std::vector<u8> CalculateNintendoTweak(std::size_t sector_id) {
- std::vector<u8> out(0x10);
+using NintendoTweak = std::array<u8, 16>;
+
+NintendoTweak CalculateNintendoTweak(std::size_t sector_id) {
+ NintendoTweak out{};
for (std::size_t i = 0xF; i <= 0xF; --i) {
out[i] = sector_id & 0xFF;
sector_id >>= 8;
@@ -64,13 +67,6 @@ AESCipher<Key, KeySize>::~AESCipher() {
}
template <typename Key, std::size_t KeySize>
-void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
- ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) ||
- mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0,
- "Failed to set IV on mbedtls ciphers.");
-}
-
-template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const {
auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
@@ -120,10 +116,17 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8*
for (std::size_t i = 0; i < size; i += sector_size) {
SetIV(CalculateNintendoTweak(sector_id++));
- Transcode<u8, u8>(src + i, sector_size, dest + i, op);
+ Transcode(src + i, sector_size, dest + i, op);
}
}
+template <typename Key, std::size_t KeySize>
+void AESCipher<Key, KeySize>::SetIVImpl(const u8* data, std::size_t size) {
+ ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, data, size) ||
+ mbedtls_cipher_set_iv(&ctx->decryption_context, data, size)) == 0,
+ "Failed to set IV on mbedtls ciphers.");
+}
+
template class AESCipher<Key128>;
template class AESCipher<Key256>;
} // namespace Core::Crypto
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index edc4ab910..e2a304186 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -6,7 +6,6 @@
#include <memory>
#include <type_traits>
-#include <vector>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
@@ -32,10 +31,12 @@ class AESCipher {
public:
AESCipher(Key key, Mode mode);
-
~AESCipher();
- void SetIV(std::vector<u8> iv);
+ template <typename ContiguousContainer>
+ void SetIV(const ContiguousContainer& container) {
+ SetIVImpl(std::data(container), std::size(container));
+ }
template <typename Source, typename Dest>
void Transcode(const Source* src, std::size_t size, Dest* dest, Op op) const {
@@ -59,6 +60,8 @@ public:
std::size_t sector_size, Op op);
private:
+ void SetIVImpl(const u8* data, std::size_t size);
+
std::unique_ptr<CipherContext> ctx;
};
} // namespace Core::Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 902841c77..5c84bb0a4 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include "common/assert.h"
#include "core/crypto/ctr_encryption_layer.h"
@@ -10,8 +11,7 @@ namespace Core::Crypto {
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_,
std::size_t base_offset)
- : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
- iv(16, 0) {}
+ : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR) {}
std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const {
if (length == 0)
@@ -39,9 +39,8 @@ std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t o
return read + Read(data + read, length - read, offset + read);
}
-void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) {
- const auto length = std::min(iv_.size(), iv.size());
- iv.assign(iv_.cbegin(), iv_.cbegin() + length);
+void CTREncryptionLayer::SetIV(const IVData& iv_) {
+ iv = iv_;
}
void CTREncryptionLayer::UpdateIV(std::size_t offset) const {
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
index a7bf810f4..a2429f001 100644
--- a/src/core/crypto/ctr_encryption_layer.h
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -4,7 +4,8 @@
#pragma once
-#include <vector>
+#include <array>
+
#include "core/crypto/aes_util.h"
#include "core/crypto/encryption_layer.h"
#include "core/crypto/key_manager.h"
@@ -14,18 +15,20 @@ namespace Core::Crypto {
// Sits on top of a VirtualFile and provides CTR-mode AES decription.
class CTREncryptionLayer : public EncryptionLayer {
public:
+ using IVData = std::array<u8, 16>;
+
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, std::size_t base_offset);
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
- void SetIV(const std::vector<u8>& iv);
+ void SetIV(const IVData& iv);
private:
std::size_t base_offset;
// Must be mutable as operations modify cipher contexts.
mutable AESCipher<Key128> cipher;
- mutable std::vector<u8> iv;
+ mutable IVData iv{};
void UpdateIV(std::size_t offset) const;
};
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 87e6a1fd3..da15f764a 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -23,7 +23,6 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
-#include "core/core.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
@@ -36,18 +35,86 @@
#include "core/settings.h"
namespace Core::Crypto {
+namespace {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
constexpr u64 FULL_TICKET_SIZE = 0x400;
-using namespace Common;
+using Common::AsArray;
-const std::array<SHA256Hash, 2> eticket_source_hashes{
- "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
- "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
+// clang-format off
+constexpr std::array eticket_source_hashes{
+ AsArray("B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"), // eticket_rsa_kek_source
+ AsArray("E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"), // eticket_rsa_kekek_source
};
+// clang-format on
-const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
+constexpr std::array<std::pair<std::string_view, KeyIndex<S128KeyType>>, 30> s128_file_id{{
+ {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
+ {"eticket_rsa_kek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
+ {"eticket_rsa_kekek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
+ {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
+ {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
+ {"rsa_oaep_kek_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
+ {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
+ {"aes_kek_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
+ {"aes_key_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
+ {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
+ {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
+ {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
+ {"key_area_key_application_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Application)}},
+ {"key_area_key_ocean_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Ocean)}},
+ {"key_area_key_system_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::System)}},
+ {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
+ {"keyblob_mac_key_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}},
+ {"tsec_key", {S128KeyType::TSEC, 0, 0}},
+ {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
+ {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
+ {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
+ {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
+ {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
+}};
+
+auto Find128ByName(std::string_view name) {
+ return std::find_if(s128_file_id.begin(), s128_file_id.end(),
+ [&name](const auto& pair) { return pair.first == name; });
+}
+
+constexpr std::array<std::pair<std::string_view, KeyIndex<S256KeyType>>, 6> s256_file_id{{
+ {"header_key", {S256KeyType::Header, 0, 0}},
+ {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
+ {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
+ {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
+ {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
+ {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
+}};
+
+auto Find256ByName(std::string_view name) {
+ return std::find_if(s256_file_id.begin(), s256_file_id.end(),
+ [&name](const auto& pair) { return pair.first == name; });
+}
+
+using KeyArray = std::array<std::pair<std::pair<S128KeyType, u64>, std::string_view>, 7>;
+constexpr KeyArray KEYS_VARIABLE_LENGTH{{
{{S128KeyType::Master, 0}, "master_key_"},
{{S128KeyType::Package1, 0}, "package1_key_"},
{{S128KeyType::Package2, 0}, "package2_key_"},
@@ -55,14 +122,13 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
-};
+}};
-namespace {
template <std::size_t Size>
bool IsAllZeroArray(const std::array<u8, Size>& array) {
return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
}
-} // namespace
+} // Anonymous namespace
u64 GetSignatureTypeDataSize(SignatureType type) {
switch (type) {
@@ -94,13 +160,13 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
}
SignatureType Ticket::GetSignatureType() const {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->sig_type;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->sig_type;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->sig_type;
}
@@ -108,13 +174,13 @@ SignatureType Ticket::GetSignatureType() const {
}
TicketData& Ticket::GetData() {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
@@ -122,13 +188,13 @@ TicketData& Ticket::GetData() {
}
const TicketData& Ticket::GetData() const {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
@@ -231,8 +297,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
- if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
+ if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
return {};
+ }
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
@@ -259,27 +326,30 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
}
std::optional<Key128> DeriveSDSeed() {
- const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/8000000000000043",
- "rb+");
- if (!save_43.IsOpen())
- return {};
+ const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/8000000000000043",
+ "rb+");
+ if (!save_43.IsOpen()) {
+ return std::nullopt;
+ }
- const FileUtil::IOFile sd_private(
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
- if (!sd_private.IsOpen())
- return {};
+ const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
+ "/Nintendo/Contents/private",
+ "rb+");
+ if (!sd_private.IsOpen()) {
+ return std::nullopt;
+ }
std::array<u8, 0x10> private_seed{};
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
- return {};
+ return std::nullopt;
}
std::array<u8, 0x10> buffer{};
std::size_t offset = 0;
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
if (!save_43.Seek(offset, SEEK_SET)) {
- return {};
+ return std::nullopt;
}
save_43.ReadBytes(buffer.data(), buffer.size());
@@ -289,23 +359,26 @@ std::optional<Key128> DeriveSDSeed() {
}
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
- return {};
+ return std::nullopt;
}
Key128 seed{};
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
- return {};
+ return std::nullopt;
}
return seed;
}
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) {
return Loader::ResultStatus::ErrorMissingSDKEKSource;
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
+ }
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) {
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
+ }
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) {
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
+ }
const auto sd_kek_source =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
@@ -318,14 +391,17 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
keys.SetKey(S128KeyType::SDKek, sd_kek);
- if (!keys.HasKey(S128KeyType::SDSeed))
+ if (!keys.HasKey(S128KeyType::SDSeed)) {
return Loader::ResultStatus::ErrorMissingSDSeed;
+ }
const auto sd_seed = keys.GetKey(S128KeyType::SDSeed);
- if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)))
+ if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) {
return Loader::ResultStatus::ErrorMissingSDSaveKeySource;
- if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)))
+ }
+ if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) {
return Loader::ResultStatus::ErrorMissingSDNCAKeySource;
+ }
std::array<Key256, 2> sd_key_sources{
keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)),
@@ -334,8 +410,9 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
// Combine sources and seed
for (auto& source : sd_key_sources) {
- for (std::size_t i = 0; i < source.size(); ++i)
- source[i] ^= sd_seed[i & 0xF];
+ for (std::size_t i = 0; i < source.size(); ++i) {
+ source[i] = static_cast<u8>(source[i] ^ sd_seed[i & 0xF]);
+ }
}
AESCipher<Key128> cipher(sd_kek, Mode::ECB);
@@ -353,9 +430,10 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
return Loader::ResultStatus::Success;
}
-std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
- if (!ticket_save.IsOpen())
+std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
+ if (!ticket_save.IsOpen()) {
return {};
+ }
std::vector<u8> buffer(ticket_save.GetSize());
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
@@ -415,7 +493,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
offset = i + 1;
break;
} else if (data[i] != 0x0) {
- return {};
+ return std::nullopt;
}
}
@@ -425,16 +503,18 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& key) {
const auto issuer = ticket.GetData().issuer;
- if (IsAllZeroArray(issuer))
- return {};
+ if (IsAllZeroArray(issuer)) {
+ return std::nullopt;
+ }
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
}
Key128 rights_id = ticket.GetData().rights_id;
- if (rights_id == Key128{})
- return {};
+ if (rights_id == Key128{}) {
+ return std::nullopt;
+ }
if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
@@ -466,15 +546,17 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
std::array<u8, 0xDF> m_2;
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
- if (m_0 != 0)
- return {};
+ if (m_0 != 0) {
+ return std::nullopt;
+ }
m_1 = m_1 ^ MGF1<0x20>(m_2);
m_2 = m_2 ^ MGF1<0xDF>(m_1);
const auto offset = FindTicketOffset(m_2);
- if (!offset)
- return {};
+ if (!offset) {
+ return std::nullopt;
+ }
ASSERT(*offset > 0);
Key128 key_temp{};
@@ -485,8 +567,8 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
KeyManager::KeyManager() {
// Initialize keys
- const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
if (Settings::values.use_dev_keys) {
dev_mode = true;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
@@ -504,34 +586,39 @@ KeyManager::KeyManager() {
}
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
- if (base.size() < begin + length)
+ if (base.size() < begin + length) {
return false;
+ }
return std::all_of(base.begin() + begin, base.begin() + begin + length,
[](u8 c) { return std::isxdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
std::ifstream file;
- OpenFStream(file, filename, std::ios_base::in);
- if (!file.is_open())
+ Common::FS::OpenFStream(file, filename, std::ios_base::in);
+ if (!file.is_open()) {
return;
+ }
std::string line;
while (std::getline(file, line)) {
std::vector<std::string> out;
std::stringstream stream(line);
std::string item;
- while (std::getline(stream, item, '='))
+ while (std::getline(stream, item, '=')) {
out.push_back(std::move(item));
+ }
- if (out.size() != 2)
+ if (out.size() != 2) {
continue;
+ }
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
- if (out[0].compare(0, 1, "#") == 0)
+ if (out[0].compare(0, 1, "#") == 0) {
continue;
+ }
if (is_title_keys) {
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
@@ -541,24 +628,26 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
} else {
out[0] = Common::ToLower(out[0]);
- if (s128_file_id.find(out[0]) != s128_file_id.end()) {
- const auto index = s128_file_id.at(out[0]);
- Key128 key = Common::HexStringToArray<16>(out[1]);
+ if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) {
+ const auto& index = iter128->second;
+ const Key128 key = Common::HexStringToArray<16>(out[1]);
s128_keys[{index.type, index.field1, index.field2}] = key;
- } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
- const auto index = s256_file_id.at(out[0]);
- Key256 key = Common::HexStringToArray<32>(out[1]);
+ } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) {
+ const auto& index = iter256->second;
+ const Key256 key = Common::HexStringToArray<32>(out[1]);
s256_keys[{index.type, index.field1, index.field2}] = key;
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
out[0].compare(0, 9, "keyblob_k") != 0) {
- if (!ValidCryptoRevisionString(out[0], 8, 2))
+ if (!ValidCryptoRevisionString(out[0], 8, 2)) {
continue;
+ }
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
- if (!ValidCryptoRevisionString(out[0], 18, 2))
+ if (!ValidCryptoRevisionString(out[0], 18, 2)) {
continue;
+ }
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
@@ -566,8 +655,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
- if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
+ if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
continue;
+ }
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
@@ -602,10 +692,11 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title) {
- if (FileUtil::Exists(dir1 + DIR_SEP + filename))
+ if (Common::FS::Exists(dir1 + DIR_SEP + filename)) {
LoadFromFile(dir1 + DIR_SEP + filename, title);
- else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
+ } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) {
LoadFromFile(dir2 + DIR_SEP + filename, title);
+ }
}
bool KeyManager::BaseDeriveNecessary() const {
@@ -613,8 +704,9 @@ bool KeyManager::BaseDeriveNecessary() const {
return !HasKey(key_type, index1, index2);
};
- if (check_key_existence(S256KeyType::Header))
+ if (check_key_existence(S256KeyType::Header)) {
return true;
+ }
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
if (check_key_existence(S128KeyType::Master, i) ||
@@ -639,14 +731,16 @@ bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
}
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
- if (!HasKey(id, field1, field2))
+ if (!HasKey(id, field1, field2)) {
return {};
+ }
return s128_keys.at({id, field1, field2});
}
Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
- if (!HasKey(id, field1, field2))
+ if (!HasKey(id, field1, field2)) {
return {};
+ }
return s256_keys.at({id, field1, field2});
}
@@ -668,7 +762,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
template <size_t Size>
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key) {
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
std::string filename = "title.keys_autogenerated";
if (category == KeyCategory::Standard) {
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
@@ -677,9 +771,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
}
const auto path = yuzu_keys_dir + DIR_SEP + filename;
- const auto add_info_text = !FileUtil::Exists(path);
- FileUtil::CreateFullPath(path);
- FileUtil::IOFile file{path, "a"};
+ const auto add_info_text = !Common::FS::Exists(path);
+ Common::FS::CreateFullPath(path);
+ Common::FS::IOFile file{path, "a"};
if (!file.IsOpen()) {
return;
}
@@ -695,8 +789,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
- if (s128_keys.find({id, field1, field2}) != s128_keys.end())
+ if (s128_keys.find({id, field1, field2}) != s128_keys.end() || key == Key128{}) {
return;
+ }
if (id == S128KeyType::Titlekey) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
@@ -711,19 +806,21 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
}
const auto iter2 = std::find_if(
- s128_file_id.begin(), s128_file_id.end(),
- [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
+ s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2);
});
- if (iter2 != s128_file_id.end())
+ if (iter2 != s128_file_id.end()) {
WriteKeyToFile(category, iter2->first, key);
+ }
// Variable cases
if (id == S128KeyType::KeyArea) {
- static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
- "key_area_key_ocean_{:02X}",
- "key_area_key_system_{:02X}"};
+ static constexpr std::array<const char*, 3> kak_names = {
+ "key_area_key_application_{:02X}",
+ "key_area_key_ocean_{:02X}",
+ "key_area_key_system_{:02X}",
+ };
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
} else if (id == S128KeyType::Master) {
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
@@ -745,43 +842,46 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
}
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
- if (s256_keys.find({id, field1, field2}) != s256_keys.end())
+ if (s256_keys.find({id, field1, field2}) != s256_keys.end() || key == Key256{}) {
return;
+ }
const auto iter = std::find_if(
- s256_file_id.begin(), s256_file_id.end(),
- [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
+ s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2);
});
- if (iter != s256_file_id.end())
+ if (iter != s256_file_id.end()) {
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
+ }
s256_keys[{id, field1, field2}] = key;
}
bool KeyManager::KeyFileExists(bool title) {
- const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
if (title) {
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
}
if (Settings::values.use_dev_keys) {
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
}
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
}
void KeyManager::DeriveSDSeedLazy() {
- if (HasKey(S128KeyType::SDSeed))
+ if (HasKey(S128KeyType::SDSeed)) {
return;
+ }
const auto res = DeriveSDSeed();
- if (res)
+ if (res) {
SetKey(S128KeyType::SDSeed, *res);
+ }
}
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
@@ -793,11 +893,13 @@ static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
}
void KeyManager::DeriveBase() {
- if (!BaseDeriveNecessary())
+ if (!BaseDeriveNecessary()) {
return;
+ }
- if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
+ if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) {
return;
+ }
const auto has_bis = [this](u64 id) {
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
@@ -814,10 +916,11 @@ void KeyManager::DeriveBase() {
static_cast<u64>(BISKeyType::Tweak));
};
- if (has_bis(2) && !has_bis(3))
+ if (has_bis(2) && !has_bis(3)) {
copy_bis(2, 3);
- else if (has_bis(3) && !has_bis(2))
+ } else if (has_bis(3) && !has_bis(2)) {
copy_bis(3, 2);
+ }
std::bitset<32> revisions(0xFFFFFFFF);
for (size_t i = 0; i < revisions.size(); ++i) {
@@ -827,15 +930,17 @@ void KeyManager::DeriveBase() {
}
}
- if (!revisions.any())
+ if (!revisions.any()) {
return;
+ }
const auto sbk = GetKey(S128KeyType::SecureBoot);
const auto tsec = GetKey(S128KeyType::TSEC);
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!revisions[i])
+ if (!revisions[i]) {
continue;
+ }
// Derive keyblob key
const auto key = DeriveKeyblobKey(
@@ -844,16 +949,18 @@ void KeyManager::DeriveBase() {
SetKey(S128KeyType::Keyblob, key, i);
// Derive keyblob MAC key
- if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
+ if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) {
continue;
+ }
const auto mac_key = DeriveKeyblobMACKey(
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
- if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
+ if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) {
continue;
+ }
// Decrypt keyblob
if (keyblobs[i] == std::array<u8, 0x90>{}) {
@@ -877,16 +984,19 @@ void KeyManager::DeriveBase() {
revisions.set();
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!HasKey(S128KeyType::Master, i))
+ if (!HasKey(S128KeyType::Master, i)) {
revisions.reset(i);
+ }
}
- if (!revisions.any())
+ if (!revisions.any()) {
return;
+ }
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!revisions[i])
+ if (!revisions[i]) {
continue;
+ }
// Derive general purpose keys
DeriveGeneralPurposeKeys(i);
@@ -911,21 +1021,24 @@ void KeyManager::DeriveBase() {
}
}
-void KeyManager::DeriveETicket(PartitionDataManager& data) {
+void KeyManager::DeriveETicket(PartitionDataManager& data,
+ const FileSys::ContentProvider& provider) {
// ETicket keys
- const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
- 0x0100000000000033, FileSys::ContentRecordType::Program);
+ const auto es = provider.GetEntry(0x0100000000000033, FileSys::ContentRecordType::Program);
- if (es == nullptr)
+ if (es == nullptr) {
return;
+ }
const auto exefs = es->GetExeFS();
- if (exefs == nullptr)
+ if (exefs == nullptr) {
return;
+ }
const auto main = exefs->GetFile("main");
- if (main == nullptr)
+ if (main == nullptr) {
return;
+ }
const auto bytes = main->ReadAllBytes();
@@ -935,16 +1048,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
const auto seed3 = data.GetRSAKekSeed3();
const auto mask0 = data.GetRSAKekMask0();
- if (eticket_kek != Key128{})
+ if (eticket_kek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
+ }
if (eticket_kekek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kekek,
static_cast<size_t>(SourceKeyType::ETicketKekek));
}
- if (seed3 != Key128{})
+ if (seed3 != Key128{}) {
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
- if (mask0 != Key128{})
+ }
+ if (mask0 != Key128{}) {
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
+ }
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
mask0 == Key128{}) {
return;
@@ -970,8 +1086,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
- if (eticket_final == Key128{})
+ if (eticket_final == Key128{}) {
return;
+ }
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
@@ -986,18 +1103,20 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
void KeyManager::PopulateTickets() {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return;
+ }
- if (!common_tickets.empty() && !personal_tickets.empty())
+ if (!common_tickets.empty() && !personal_tickets.empty()) {
return;
+ }
- const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/80000000000000e1",
- "rb+");
- const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/80000000000000e2",
- "rb+");
+ const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/80000000000000e1",
+ "rb+");
+ const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/80000000000000e2",
+ "rb+");
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
@@ -1007,8 +1126,10 @@ void KeyManager::PopulateTickets() {
for (std::size_t i = 0; i < res.size(); ++i) {
const auto common = i < idx;
const auto pair = ParseTicket(res[i], rsa_key);
- if (!pair)
+ if (!pair) {
continue;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1037,27 +1158,33 @@ void KeyManager::SynthesizeTickets() {
}
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
- if (key == Key128{})
+ if (key == Key128{}) {
return;
+ }
SetKey(id, key, field1, field2);
}
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
- if (key == Key256{})
+ if (key == Key256{}) {
return;
+ }
+
SetKey(id, key, field1, field2);
}
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
- if (!BaseDeriveNecessary())
+ if (!BaseDeriveNecessary()) {
return;
+ }
- if (!data.HasBoot0())
+ if (!data.HasBoot0()) {
return;
+ }
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
- if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
+ if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) {
continue;
+ }
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
encrypted_keyblobs[i]);
@@ -1079,8 +1206,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
static_cast<u64>(SourceKeyType::Keyblob), i);
}
- if (data.HasFuses())
+ if (data.HasFuses()) {
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
+ }
DeriveBase();
@@ -1094,8 +1222,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
const auto masters = data.GetTZMasterKeys(latest_master);
for (size_t i = 0; i < masters.size(); ++i) {
- if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
+ if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) {
SetKey(S128KeyType::Master, masters[i], i);
+ }
}
DeriveBase();
@@ -1105,8 +1234,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
std::array<Key128, 0x20> package2_keys{};
for (size_t i = 0; i < package2_keys.size(); ++i) {
- if (HasKey(S128KeyType::Package2, i))
+ if (HasKey(S128KeyType::Package2, i)) {
package2_keys[i] = GetKey(S128KeyType::Package2, i);
+ }
}
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
@@ -1144,12 +1274,15 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
bool KeyManager::AddTicketCommon(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return false;
+ }
const auto pair = ParseTicket(raw, rsa_key);
- if (!pair)
+ if (!pair) {
return false;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1160,12 +1293,15 @@ bool KeyManager::AddTicketCommon(Ticket raw) {
bool KeyManager::AddTicketPersonalized(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return false;
+ }
const auto pair = ParseTicket(raw, rsa_key);
- if (!pair)
+ if (!pair) {
return false;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1173,57 +1309,4 @@ bool KeyManager::AddTicketPersonalized(Ticket raw) {
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
return true;
}
-
-const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
- {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
- {"eticket_rsa_kek_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
- {"eticket_rsa_kekek_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
- {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
- {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
- {"rsa_oaep_kek_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
- {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
- {"aes_kek_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
- {"aes_key_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
- {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
- {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
- {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
- {"key_area_key_application_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_ocean_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_system_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::System)}},
- {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
- {"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
- {"tsec_key", {S128KeyType::TSEC, 0, 0}},
- {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
- {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
- {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
- {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
- {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
-};
-
-const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
- {"header_key", {S256KeyType::Header, 0, 0}},
- {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
- {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
- {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
- {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
- {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
-};
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 7265c4171..0a7220286 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -10,17 +10,20 @@
#include <string>
#include <variant>
-#include <boost/container/flat_map.hpp>
#include <fmt/format.h>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
+namespace FileSys {
+class ContentProvider;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -223,7 +226,16 @@ bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
class KeyManager {
public:
- KeyManager();
+ static KeyManager& Instance() {
+ static KeyManager instance;
+ return instance;
+ }
+
+ KeyManager(const KeyManager&) = delete;
+ KeyManager& operator=(const KeyManager&) = delete;
+
+ KeyManager(KeyManager&&) = delete;
+ KeyManager& operator=(KeyManager&&) = delete;
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
@@ -244,7 +256,7 @@ public:
bool BaseDeriveNecessary() const;
void DeriveBase();
- void DeriveETicket(PartitionDataManager& data);
+ void DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider);
void PopulateTickets();
void SynthesizeTickets();
@@ -257,6 +269,8 @@ public:
bool AddTicketPersonalized(Ticket raw);
private:
+ KeyManager();
+
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
@@ -282,9 +296,6 @@ private:
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
-
- static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
- static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -297,7 +308,7 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
std::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
-std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
+std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
// (offset 0x140-0x144 is zero)
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index d64302f2e..5f1c86a09 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -26,8 +26,9 @@
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_vector.h"
+#include "core/loader/loader.h"
-using namespace Common;
+using Common::AsArray;
namespace Core::Crypto {
@@ -47,105 +48,123 @@ struct Package2Header {
};
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
-const std::array<SHA256Hash, 0x10> source_hashes{
- "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
- "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
- "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
- "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
- "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
- "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
- "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
- "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
- "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
- "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
- "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
- "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
- "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
- "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
- "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
- "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
+// clang-format off
+constexpr std::array source_hashes{
+ AsArray("B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"), // keyblob_mac_key_source
+ AsArray("7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"), // master_key_source
+ AsArray("21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"), // package2_key_source
+ AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // aes_kek_generation_source
+ AsArray("FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"), // aes_key_generation_source
+ AsArray("C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"), // titlekek_source
+ AsArray("04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"), // key_area_key_application_source
+ AsArray("FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"), // key_area_key_ocean_source
+ AsArray("1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"), // key_area_key_system_source
+ AsArray("6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"), // sd_card_kek_source
+ AsArray("D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"), // sd_card_save_key_source
+ AsArray("2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"), // sd_card_nca_key_source
+ AsArray("1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"), // header_kek_source
+ AsArray("8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"), // header_key_source
+ AsArray("D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"), // rsa_kek_seed3
+ AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // rsa_kek_mask0
};
-
-const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
- "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
- "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
- "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
- "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
- "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
- "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
+// clang-format on
+
+// clang-format off
+constexpr std::array keyblob_source_hashes{
+ AsArray("8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"), // keyblob_key_source_00
+ AsArray("2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"), // keyblob_key_source_01
+ AsArray("61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"), // keyblob_key_source_02
+ AsArray("8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"), // keyblob_key_source_03
+ AsArray("95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"), // keyblob_key_source_04
+ AsArray("3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"), // keyblob_key_source_05
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_06
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_07
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_08
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_09
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0F
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_10
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_11
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_12
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_13
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_14
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_15
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_16
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_17
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_18
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_19
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1F
};
-
-const std::array<SHA256Hash, 0x20> master_key_hashes{
- "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
- "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
- "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
- "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
- "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
- "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
+// clang-format on
+
+// clang-format off
+constexpr std::array master_key_hashes{
+ AsArray("0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"), // master_key_00
+ AsArray("4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"), // master_key_01
+ AsArray("79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"), // master_key_02
+ AsArray("4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"), // master_key_03
+ AsArray("75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"), // master_key_04
+ AsArray("EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"), // master_key_05
+ AsArray("9497E6779F5D840F2BBA1DE4E95BA1D6F21EFC94717D5AE5CA37D7EC5BD37A19"), // master_key_06
+ AsArray("4EC96B8CB01B8DCE382149443430B2B6EBCB2983348AFA04A25E53609DABEDF6"), // master_key_07
+
+ AsArray("2998E2E23609BC2675FF062A2D64AF5B1B78DFF463B24119D64A1B64F01B2D51"), // master_key_08
+ AsArray("9D486A98067C44B37CF173D3BF577891EB6081FF6B4A166347D9DBBF7025076B"), // master_key_09
+ AsArray("4EC5A237A75A083A9C5F6CF615601522A7F822D06BD4BA32612C9CEBBB29BD45"), // master_key_0A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0F
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_10
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_11
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_12
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_13
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_14
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_15
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_16
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_17
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_18
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_19
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1F
};
+// clang-format on
+
+static constexpr u8 CalculateMaxKeyblobSourceHash() {
+ const auto is_zero = [](const auto& data) {
+ // TODO: Replace with std::all_of whenever mingw decides to update their
+ // libraries to include the constexpr variant of it.
+ for (const auto element : data) {
+ if (element != 0) {
+ return false;
+ }
+ }
+ return true;
+ };
-static u8 CalculateMaxKeyblobSourceHash() {
for (s8 i = 0x1F; i >= 0; --i) {
- if (keyblob_source_hashes[i] != SHA256Hash{})
+ if (!is_zero(keyblob_source_hashes[i])) {
return static_cast<u8>(i + 1);
+ }
}
return 0;
@@ -202,8 +221,8 @@ static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<
return out;
}
-FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
- const std::string& name) {
+static FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
+ const std::string& name) {
const auto upper = Common::ToUpper(name);
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
@@ -345,14 +364,12 @@ FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) con
return package2.at(static_cast<size_t>(type));
}
-bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
-
- const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
+static bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
Package2Header temp = header;
AESCipher<Key128> cipher(key, Mode::CTR);
- cipher.SetIV(iv);
- cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
- Op::Decrypt);
+ cipher.SetIV(header.header_ctr);
+ cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - sizeof(Package2Header::signature),
+ &temp.header_ctr, Op::Decrypt);
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
header = temp;
return true;
@@ -389,7 +406,7 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
auto c = a->ReadAllBytes();
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
- cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
+ cipher.SetIV(header.section_ctr[1]);
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c);
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
new file mode 100644
index 000000000..0c4b440ed
--- /dev/null
+++ b/src/core/device_memory.cpp
@@ -0,0 +1,12 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/device_memory.h"
+
+namespace Core {
+
+DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {}
+DeviceMemory::~DeviceMemory() = default;
+
+} // namespace Core
diff --git a/src/core/device_memory.h b/src/core/device_memory.h
new file mode 100644
index 000000000..5b1ae28f3
--- /dev/null
+++ b/src/core/device_memory.h
@@ -0,0 +1,47 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/virtual_buffer.h"
+
+namespace Core {
+
+namespace DramMemoryMap {
+enum : u64 {
+ Base = 0x80000000ULL,
+ Size = 0x100000000ULL,
+ End = Base + Size,
+ KernelReserveBase = Base + 0x60000,
+ SlabHeapBase = KernelReserveBase + 0x85000,
+ SlapHeapSize = 0xa21000,
+ SlabHeapEnd = SlabHeapBase + SlapHeapSize,
+};
+}; // namespace DramMemoryMap
+
+class DeviceMemory : NonCopyable {
+public:
+ explicit DeviceMemory();
+ ~DeviceMemory();
+
+ template <typename T>
+ PAddr GetPhysicalAddr(const T* ptr) const {
+ return (reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(buffer.data())) +
+ DramMemoryMap::Base;
+ }
+
+ u8* GetPointer(PAddr addr) {
+ return buffer.data() + (addr - DramMemoryMap::Base);
+ }
+
+ const u8* GetPointer(PAddr addr) const {
+ return buffer.data() + (addr - DramMemoryMap::Base);
+ }
+
+private:
+ Common::VirtualBuffer<u8> buffer;
+};
+
+} // namespace Core
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 0af44f340..7c6304ff0 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -4,14 +4,17 @@
#include <fmt/format.h>
#include "common/file_util.h"
-#include "core/core.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/registered_cache.h"
-#include "core/settings.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
+constexpr u64 NAND_USER_SIZE = 0x680000000; // 26624 MiB
+constexpr u64 NAND_SYSTEM_SIZE = 0xA0000000; // 2560 MiB
+constexpr u64 NAND_TOTAL_SIZE = 0x747C00000; // 29820 MiB
+
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir dump_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
dump_root(std::move(dump_root_)),
@@ -78,11 +81,11 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
}
}
-VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
- Core::Crypto::KeyManager keys;
- Core::Crypto::PartitionDataManager pdm{
- Core::System::GetInstance().GetFilesystem()->OpenDirectory(
- FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
+VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
+ VirtualFilesystem file_system) const {
+ auto& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
+ Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
keys.PopulateFromPartitionData(pdm);
switch (id) {
@@ -110,30 +113,29 @@ VirtualDir BISFactory::GetImageDirectory() const {
u64 BISFactory::GetSystemNANDFreeSpace() const {
const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system");
- if (sys_dir == nullptr)
- return 0;
+ if (sys_dir == nullptr) {
+ return GetSystemNANDTotalSpace();
+ }
return GetSystemNANDTotalSpace() - sys_dir->GetSize();
}
u64 BISFactory::GetSystemNANDTotalSpace() const {
- return static_cast<u64>(Settings::values.nand_system_size);
+ return NAND_SYSTEM_SIZE;
}
u64 BISFactory::GetUserNANDFreeSpace() const {
- const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user");
- if (usr_dir == nullptr)
- return 0;
-
- return GetUserNANDTotalSpace() - usr_dir->GetSize();
+ // For some reason games such as BioShock 1 checks whether this is exactly 0x680000000 bytes.
+ // Set the free space to be 1 MiB less than the total as a workaround to this issue.
+ return GetUserNANDTotalSpace() - 0x100000;
}
u64 BISFactory::GetUserNANDTotalSpace() const {
- return static_cast<u64>(Settings::values.nand_user_size);
+ return NAND_USER_SIZE;
}
u64 BISFactory::GetFullNANDTotalSpace() const {
- return static_cast<u64>(Settings::values.nand_total_size);
+ return NAND_TOTAL_SIZE;
}
VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 8f0451c98..136485881 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -6,7 +6,8 @@
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
@@ -51,7 +52,7 @@ public:
VirtualDir GetModificationDumpRoot(u64 title_id) const;
VirtualDir OpenPartition(BisPartitionId id) const;
- VirtualFile OpenPartitionStorage(BisPartitionId id) const;
+ VirtualFile OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const;
VirtualDir GetImageDirectory() const;
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 07d0c8d5d..956da68f7 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -8,11 +8,11 @@
#include <fmt/ostream.h>
#include "common/logging/log.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_offset.h"
@@ -31,7 +31,8 @@ constexpr std::array partition_names{
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
- partitions(partition_names.size()), partitions_raw(partition_names.size()) {
+ partitions(partition_names.size()),
+ partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;
@@ -178,7 +179,7 @@ u32 XCI::GetSystemUpdateVersion() {
return 0;
for (const auto& file : update->GetFiles()) {
- NCA nca{file, nullptr, 0, keys};
+ NCA nca{file, nullptr, 0};
if (nca.GetStatus() != Loader::ResultStatus::Success)
continue;
@@ -286,7 +287,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
continue;
}
- auto nca = std::make_shared<NCA>(file, nullptr, 0, keys);
+ auto nca = std::make_shared<NCA>(file, nullptr, 0);
if (nca->IsUpdate()) {
continue;
}
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index c2ee0ea99..2d0a0f285 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -9,9 +9,12 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -140,6 +143,6 @@ private:
u64 update_normal_partition_end;
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index b8bbdd1ef..76af47ff9 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -10,10 +10,10 @@
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
@@ -118,9 +118,9 @@ static bool IsValidNCA(const NCAHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset,
- Core::Crypto::KeyManager keys_)
- : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)), keys(std::move(keys_)) {
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
+ : file(std::move(file_)),
+ bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
@@ -323,7 +323,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
subsection_buckets.back().entries.push_back({size, {0}, 0});
- std::optional<Core::Crypto::Key128> key = {};
+ std::optional<Core::Crypto::Key128> key;
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
@@ -442,18 +442,18 @@ std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
- return {};
+ return std::nullopt;
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
- return {};
+ return std::nullopt;
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
- return {};
+ return std::nullopt;
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -477,7 +477,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
case NCASectionCryptoType::BKTR:
LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
- std::optional<Core::Crypto::Key128> key = {};
+ std::optional<Core::Crypto::Key128> key;
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
@@ -496,9 +496,10 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
starting_offset);
- std::vector<u8> iv(16);
- for (u8 i = 0; i < 8; ++i)
- iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
+ Core::Crypto::CTREncryptionLayer::IVData iv{};
+ for (std::size_t i = 0; i < 8; ++i) {
+ iv[i] = s_header.raw.section_ctr[8 - i - 1];
+ }
out->SetIV(iv);
return std::static_pointer_cast<VfsFile>(out);
}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index e249079b5..69292232a 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -99,8 +99,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
class NCA : public ReadOnlyVfsDirectory {
public:
explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
- u64 bktr_base_ivfc_offset = 0,
- Core::Crypto::KeyManager keys = Core::Crypto::KeyManager());
+ u64 bktr_base_ivfc_offset = 0);
~NCA() override;
Loader::ResultStatus GetStatus() const;
@@ -159,7 +158,7 @@ private:
bool encrypted = false;
bool is_update = false;
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index f155a1341..b0a130345 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -5,6 +5,7 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -95,6 +96,10 @@ u32 NACP::GetSupportedLanguages() const {
return raw.supported_languages;
}
+u64 NACP::GetDeviceSaveDataSize() const {
+ return raw.device_save_data_size;
+}
+
std::vector<u8> NACP::GetRawBytes() const {
std::vector<u8> out(sizeof(RawNACP));
std::memcpy(out.data(), &raw, sizeof(RawNACP));
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 2d8c251ac..403c4219a 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
@@ -83,7 +83,7 @@ enum class Language : u8 {
Italian = 7,
Dutch = 8,
CanadianFrench = 9,
- Portugese = 10,
+ Portuguese = 10,
Russian = 11,
Korean = 12,
Taiwanese = 13,
@@ -113,6 +113,7 @@ public:
u32 GetSupportedLanguages() const;
std::vector<u8> GetRawBytes() const;
bool GetUserAccountSwitchLock() const;
+ u64 GetDeviceSaveDataSize() const;
private:
RawNACP raw{};
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index d126ae8dd..c52fafb6f 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -240,7 +240,7 @@ RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
RomFSBuildContext::~RomFSBuildContext() = default;
-std::map<u64, VirtualFile> RomFSBuildContext::Build() {
+std::multimap<u64, VirtualFile> RomFSBuildContext::Build() {
const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
dir_hash_table_size = 4 * dir_hash_table_entry_count;
@@ -266,8 +266,9 @@ std::map<u64, VirtualFile> RomFSBuildContext::Build() {
cur_file->offset = file_partition_size;
file_partition_size += cur_file->size;
cur_file->entry_offset = entry_offset;
- entry_offset += sizeof(RomFSFileEntry) +
- Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4);
+ entry_offset +=
+ static_cast<u32>(sizeof(RomFSFileEntry) +
+ Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4));
prev_file = cur_file;
}
// Assign deferred parent/sibling ownership.
@@ -284,8 +285,9 @@ std::map<u64, VirtualFile> RomFSBuildContext::Build() {
for (const auto& it : directories) {
cur_dir = it.second;
cur_dir->entry_offset = entry_offset;
- entry_offset += sizeof(RomFSDirectoryEntry) +
- Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4);
+ entry_offset +=
+ static_cast<u32>(sizeof(RomFSDirectoryEntry) +
+ Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4));
}
// Assign deferred parent/sibling ownership.
for (auto it = directories.rbegin(); it->second != root; ++it) {
@@ -294,7 +296,7 @@ std::map<u64, VirtualFile> RomFSBuildContext::Build() {
cur_dir->parent->child = cur_dir;
}
- std::map<u64, VirtualFile> out;
+ std::multimap<u64, VirtualFile> out;
// Populate file tables.
for (const auto& it : files) {
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index a62502193..049de180b 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -43,7 +43,7 @@ public:
~RomFSBuildContext();
// This finalizes the context.
- std::map<u64, VirtualFile> Build();
+ std::multimap<u64, VirtualFile> Build();
private:
VirtualDir base;
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index a08a70efd..a6101f1c0 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -245,9 +245,11 @@ void IPSwitchCompiler::Parse() {
// Read rest of patch
while (true) {
- if (i + 1 >= lines.size())
+ if (i + 1 >= lines.size()) {
break;
- const auto patch_line = lines[++i];
+ }
+
+ const auto& patch_line = lines[++i];
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
@@ -297,7 +299,7 @@ void IPSwitchCompiler::Parse() {
patch_text->GetName(), offset, Common::HexToString(replace));
}
- patch.records.insert_or_assign(offset, std::move(replace));
+ patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
}
patches.push_back(std::move(patch));
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index 76313679d..ef93ef3ed 100644
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -2,9 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
+
#include "common/string_util.h"
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/vfs_offset.h"
+#include "core/loader/loader.h"
namespace FileSys {
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 324a57384..044c554d3 100644
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -4,10 +4,17 @@
#pragma once
+#include <array>
+#include <vector>
+
#include "common/common_funcs.h"
+#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs_types.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h
index c95205668..2b4f21073 100644
--- a/src/core/file_sys/mode.h
+++ b/src/core/file_sys/mode.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/common_funcs.h"
#include "common/common_types.h"
namespace FileSys {
@@ -11,13 +12,11 @@ namespace FileSys {
enum class Mode : u32 {
Read = 1,
Write = 2,
- ReadWrite = 3,
+ ReadWrite = Read | Write,
Append = 4,
- WriteAppend = 6,
+ WriteAppend = Write | Append,
};
-inline u32 operator&(Mode lhs, Mode rhs) {
- return static_cast<u32>(lhs) & static_cast<u32>(rhs);
-}
+DECLARE_ENUM_FLAG_OPERATORS(Mode)
} // namespace FileSys
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 93d0df6b9..3596541b2 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -107,7 +108,7 @@ std::vector<u8> CNMT::Serialize() const {
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
}
- auto offset = header.table_offset;
+ u64_le offset = header.table_offset;
for (const auto& rec : content_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 1f82fff0a..53535e5f5 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
class CNMT;
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
index 0090cc6c4..5990a2fd5 100644
--- a/src/core/file_sys/nca_patch.cpp
+++ b/src/core/file_sys/nca_patch.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <cstddef>
#include <cstring>
@@ -11,6 +12,49 @@
#include "core/file_sys/nca_patch.h"
namespace FileSys {
+namespace {
+template <bool Subsection, typename BlockType, typename BucketType>
+std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
+ const BucketType& buckets) {
+ if constexpr (Subsection) {
+ const auto& last_bucket = buckets[block.number_buckets - 1];
+ if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
+ return {block.number_buckets - 1, last_bucket.number_entries};
+ }
+ } else {
+ ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
+ }
+
+ std::size_t bucket_id = std::count_if(
+ block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
+ [&offset](u64 base_offset) { return base_offset <= offset; });
+
+ const auto& bucket = buckets[bucket_id];
+
+ if (bucket.number_entries == 1) {
+ return {bucket_id, 0};
+ }
+
+ std::size_t low = 0;
+ std::size_t mid = 0;
+ std::size_t high = bucket.number_entries - 1;
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (bucket.entries[mid].address_patch > offset) {
+ high = mid - 1;
+ } else {
+ if (mid == bucket.number_entries - 1 ||
+ bucket.entries[mid + 1].address_patch > offset) {
+ return {bucket_id, mid};
+ }
+
+ low = mid + 1;
+ }
+ }
+
+ UNREACHABLE_MSG("Offset could not be found in BKTR block.");
+}
+} // Anonymous namespace
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
@@ -66,7 +110,7 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
// Calculate AES IV
- std::vector<u8> iv(16);
+ std::array<u8, 16> iv{};
auto subsection_ctr = subsection.ctr;
auto offset_iv = section_offset + base_offset;
for (std::size_t i = 0; i < section_ctr.size(); ++i)
@@ -109,46 +153,6 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
return raw_read;
}
-template <bool Subsection, typename BlockType, typename BucketType>
-std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
- BucketType buckets) const {
- if constexpr (Subsection) {
- const auto last_bucket = buckets[block.number_buckets - 1];
- if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
- return {block.number_buckets - 1, last_bucket.number_entries};
- } else {
- ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
- }
-
- std::size_t bucket_id = std::count_if(
- block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
- [&offset](u64 base_offset) { return base_offset <= offset; });
-
- const auto bucket = buckets[bucket_id];
-
- if (bucket.number_entries == 1)
- return {bucket_id, 0};
-
- std::size_t low = 0;
- std::size_t mid = 0;
- std::size_t high = bucket.number_entries - 1;
- while (low <= high) {
- mid = (low + high) / 2;
- if (bucket.entries[mid].address_patch > offset) {
- high = mid - 1;
- } else {
- if (mid == bucket.number_entries - 1 ||
- bucket.entries[mid + 1].address_patch > offset) {
- return {bucket_id, mid};
- }
-
- low = mid + 1;
- }
- }
-
- UNREACHABLE_MSG("Offset could not be found in BKTR block.");
-}
-
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
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:
bool Rename(std::string_view name) override;
private:
- template <bool Subsection, typename BlockType, typename BucketType>
- std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block,
- BucketType buckets) const;
-
RelocationEntry GetRelocationEntry(u64 offset) const;
RelocationEntry GetNextRelocationEntry(u64 offset) const;
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 846986736..48a2ed4d4 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -21,7 +21,7 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const {
magic == Common::MakeMagic('P', 'F', 'S', '0');
}
-PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
+PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// At least be as large as the header
if (file->GetSize() < sizeof(Header)) {
status = Loader::ResultStatus::ErrorBadPFSHeader;
@@ -89,11 +89,11 @@ std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const {
return sizes;
}
-std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
+std::vector<VirtualFile> PartitionFilesystem::GetFiles() const {
return pfs_files;
}
-std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
+std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const {
return {};
}
@@ -101,7 +101,7 @@ std::string PartitionFilesystem::GetName() const {
return is_hfs ? "HFS0" : "PFS0";
}
-std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
+VirtualDir PartitionFilesystem::GetParentDirectory() const {
// TODO(DarkLordZach): Add support for nested containers.
return nullptr;
}
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 279193b19..0f831148e 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -24,7 +24,7 @@ namespace FileSys {
*/
class PartitionFilesystem : public ReadOnlyVfsDirectory {
public:
- explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
+ explicit PartitionFilesystem(VirtualFile file);
~PartitionFilesystem() override;
Loader::ResultStatus GetStatus() const;
@@ -32,10 +32,10 @@ public:
std::map<std::string, u64> GetFileOffsets() const;
std::map<std::string, u64> GetFileSizes() const;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
void PrintDebugInfo() const;
private:
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index e77e82b8d..e9d1607d0 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -10,6 +10,7 @@
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
@@ -26,8 +27,9 @@
#include "core/settings.h"
namespace FileSys {
+namespace {
-constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
+constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
@@ -35,20 +37,85 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
};
-std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
+enum class TitleVersionFormat : u8 {
+ ThreeElements, ///< vX.Y.Z
+ FourElements, ///< vX.Y.Z.W
+};
+
+std::string FormatTitleVersion(u32 version,
+ TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
std::array<u8, sizeof(u32)> bytes{};
- bytes[0] = version % SINGLE_BYTE_MODULUS;
+ bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
for (std::size_t i = 1; i < bytes.size(); ++i) {
version /= SINGLE_BYTE_MODULUS;
- bytes[i] = version % SINGLE_BYTE_MODULUS;
+ bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
}
- if (format == TitleVersionFormat::FourElements)
+ if (format == TitleVersionFormat::FourElements) {
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
+ }
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
-PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
+// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
+// doesn't have a directory with name.
+VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
+#ifdef _WIN32
+ return dir->GetSubdirectory(name);
+#else
+ const auto subdirs = dir->GetSubdirectories();
+ for (const auto& subdir : subdirs) {
+ std::string dir_name = Common::ToLower(subdir->GetName());
+ if (dir_name == name) {
+ return subdir;
+ }
+ }
+
+ return nullptr;
+#endif
+}
+
+std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
+ u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
+ const auto build_id_raw = Common::HexToString(build_id_, upper);
+ const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
+ const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
+
+ if (file == nullptr) {
+ LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ std::vector<u8> data(file->GetSize());
+ if (file->Read(data.data(), data.size()) != data.size()) {
+ LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ const Core::Memory::TextCheatParser parser;
+ return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
+}
+
+void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
+ if (to.empty()) {
+ to += with;
+ } else {
+ to += ", ";
+ to += with;
+ }
+}
+
+bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
+ return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
+}
+} // Anonymous namespace
+
+PatchManager::PatchManager(u64 title_id_,
+ const Service::FileSystem::FileSystemController& fs_controller_,
+ const ContentProvider& content_provider_)
+ : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
PatchManager::~PatchManager() = default;
@@ -64,34 +131,30 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (Settings::values.dump_exefs) {
LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
- const auto dump_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
+ const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
VfsRawCopyD(exefs, exefs_dir);
}
}
- const auto& installed = Core::System::GetInstance().GetContentProvider();
-
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
+ const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
- FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
}
// LayeredExeFS
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir != nullptr && load_dir->GetSize() > 0) {
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(
@@ -104,7 +167,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
- auto exefs_dir = subdir->GetSubdirectory("exefs");
+ auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
if (exefs_dir != nullptr)
layers.push_back(std::move(exefs_dir));
}
@@ -130,7 +193,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend())
continue;
- auto exefs_dir = subdir->GetSubdirectory("exefs");
+ auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
@@ -177,8 +240,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
if (Settings::values.dump_nso) {
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
title_id);
- const auto dump_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
+ const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
@@ -190,8 +252,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return nso;
@@ -228,14 +289,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return out;
}
-bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
+bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return false;
@@ -248,37 +308,9 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
-namespace {
-std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
- const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
- const VirtualDir& base_path, bool upper) {
- const auto build_id_raw = Common::HexToString(build_id_, upper);
- const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
- const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
-
- if (file == nullptr) {
- LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
- title_id, build_id);
- return std::nullopt;
- }
-
- std::vector<u8> data(file->GetSize());
- if (file->Read(data.data(), data.size()) != data.size()) {
- LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
- title_id, build_id);
- return std::nullopt;
- }
-
- Memory::TextCheatParser parser;
- return parser.Parse(
- system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
-}
-
-} // Anonymous namespace
-
-std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
- const Core::System& system, const std::array<u8, 32>& build_id_) const {
- const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
+std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
+ const BuildID& build_id_) const {
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return {};
@@ -289,22 +321,20 @@ std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- std::vector<Memory::CheatEntry> out;
+ std::vector<Core::Memory::CheatEntry> out;
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend()) {
continue;
}
- auto cheats_dir = subdir->GetSubdirectory("cheats");
+ auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
if (cheats_dir != nullptr) {
- auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
- if (res.has_value()) {
+ if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
continue;
}
- res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
- if (res.has_value()) {
+ if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
}
}
@@ -313,9 +343,9 @@ std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
return out;
}
-static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
+ const Service::FileSystem::FileSystemController& fs_controller) {
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
@@ -340,11 +370,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
continue;
}
- auto romfs_dir = subdir->GetSubdirectory("romfs");
+ auto romfs_dir = FindSubdirectoryCaseless(subdir, "romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
- auto ext_dir = subdir->GetSubdirectory("romfs_ext");
+ auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::move(ext_dir));
}
@@ -377,19 +407,19 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
- if (type == ContentRecordType::Program || type == ContentRecordType::Data)
+ if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
LOG_INFO(Loader, "{}", log_string);
- else
+ } else {
LOG_DEBUG(Loader, "{}", log_string);
+ }
- if (romfs == nullptr)
+ if (romfs == nullptr) {
return romfs;
-
- const auto& installed = Core::System::GetInstance().GetContentProvider();
+ }
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed.GetEntryRaw(update_tid, type);
+ const auto update = content_provider.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
@@ -400,7 +430,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
- FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (!update_disabled && update_raw != nullptr) {
@@ -413,34 +443,24 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
}
// LayeredFS
- ApplyLayeredFS(romfs, title_id, type);
+ ApplyLayeredFS(romfs, title_id, type, fs_controller);
return romfs;
}
-static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
- if (to.empty())
- to += with;
- else
- to += ", " + with;
-}
-
-static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
- return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
-}
-
-std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
- VirtualFile update_raw) const {
- if (title_id == 0)
+PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
+ if (title_id == 0) {
return {};
+ }
+
std::map<std::string, std::string, std::less<>> out;
- const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- PatchManager update{update_tid};
- auto [nacp, discard_icon_file] = update.GetControlMetadata();
+ PatchManager update{update_tid, fs_controller, content_provider};
+ const auto metadata = update.GetControlMetadata();
+ const auto& nacp = metadata.first;
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
@@ -449,13 +469,12 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (nacp != nullptr) {
out.insert_or_assign(update_label, nacp->GetVersionString());
} else {
- if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
- const auto meta_ver = installed.GetEntryVersion(update_tid);
+ if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
+ const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, "");
} else {
- out.insert_or_assign(
- update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
+ out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
}
} else if (update_raw != nullptr) {
out.insert_or_assign(update_label, "PACKED");
@@ -463,13 +482,12 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
// General Mods (LayeredFS and IPS)
- const auto mod_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
- const auto exefs_dir = mod->GetSubdirectory("exefs");
+ const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs");
if (IsDirValidAndNonEmpty(exefs_dir)) {
bool ips = false;
bool ipswitch = false;
@@ -493,9 +511,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (layeredfs)
AppendCommaIfNotEmpty(types, "LayeredExeFS");
}
- if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
+ if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
- if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
+ if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
AppendCommaIfNotEmpty(types, "Cheats");
if (types.empty())
@@ -508,13 +526,15 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
// DLC
- const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
+ const auto dlc_entries =
+ content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [this, &installed](const ContentProviderEntry& entry) {
+ [this](const ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
- installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
+ content_provider.GetEntry(entry)->GetStatus() ==
+ Loader::ResultStatus::Success;
});
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
@@ -535,49 +555,52 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
std::optional<u32> PatchManager::GetGameVersion() const {
- const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto update_tid = GetUpdateTitleID(title_id);
- if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
- return installed.GetEntryVersion(update_tid);
+ if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
+ return content_provider.GetEntryVersion(update_tid);
}
- return installed.GetEntryVersion(title_id);
+ return content_provider.GetEntryVersion(title_id);
}
-std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto& installed = Core::System::GetInstance().GetContentProvider();
-
- const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
- if (base_control_nca == nullptr)
+PatchManager::Metadata PatchManager::GetControlMetadata() const {
+ const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
+ if (base_control_nca == nullptr) {
return {};
+ }
return ParseControlNCA(*base_control_nca);
}
-std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
+PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
const auto base_romfs = nca.GetRomFS();
- if (base_romfs == nullptr)
+ if (base_romfs == nullptr) {
return {};
+ }
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
- if (romfs == nullptr)
+ if (romfs == nullptr) {
return {};
+ }
const auto extracted = ExtractRomFS(romfs);
- if (extracted == nullptr)
+ if (extracted == nullptr) {
return {};
+ }
auto nacp_file = extracted->GetFile("control.nacp");
- if (nacp_file == nullptr)
+ if (nacp_file == nullptr) {
nacp_file = extracted->GetFile("Control.nacp");
+ }
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
- icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
- if (icon_file != nullptr)
+ icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
+ if (icon_file != nullptr) {
break;
+ }
}
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 e857e6e82..fb1853035 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -6,83 +6,89 @@
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/memory/dmnt_cheat_types.h"
namespace Core {
class System;
}
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace FileSys {
+class ContentProvider;
class NCA;
class NACP;
-enum class TitleVersionFormat : u8 {
- ThreeElements, ///< vX.Y.Z
- FourElements, ///< vX.Y.Z.W
-};
-
-std::string FormatTitleVersion(u32 version,
- TitleVersionFormat format = TitleVersionFormat::ThreeElements);
-
// A centralized class to manage patches to games.
class PatchManager {
public:
- explicit PatchManager(u64 title_id);
+ using BuildID = std::array<u8, 0x20>;
+ using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
+ using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
+
+ explicit PatchManager(u64 title_id_,
+ const Service::FileSystem::FileSystemController& fs_controller_,
+ const ContentProvider& content_provider_);
~PatchManager();
- u64 GetTitleID() const;
+ [[nodiscard]] u64 GetTitleID() const;
// Currently tracked ExeFS patches:
// - Game Updates
- VirtualDir PatchExeFS(VirtualDir exefs) const;
+ [[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
- std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
+ [[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
+ const std::string& name) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
- bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
+ [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
// Creates a CheatList object with all
- std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
- const std::array<u8, 0x20>& build_id) const;
+ [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
+ const BuildID& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
- VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
- ContentRecordType type = ContentRecordType::Program,
- VirtualFile update_raw = nullptr) const;
+ [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
+ ContentRecordType type = ContentRecordType::Program,
+ VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
- std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
- VirtualFile update_raw = nullptr) const;
+ [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
// std::nullopt
- std::optional<u32> GetGameVersion() const;
+ [[nodiscard]] std::optional<u32> GetGameVersion() const;
// Given title_id of the program, attempts to get the control data of the update and parse
// it, falling back to the base control data.
- std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
+ [[nodiscard]] Metadata GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
- std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
+ [[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
private:
- std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) const;
+ [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const;
u64 title_id;
+ const Service::FileSystem::FileSystemController& fs_controller;
+ const ContentProvider& content_provider;
};
} // namespace FileSys
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 1d6c30962..9cf49bf44 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "core/file_sys/program_metadata.h"
+#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace FileSys {
@@ -51,6 +52,17 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
return Loader::ResultStatus::Success;
}
+/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
+ ProgramMetadata result;
+
+ result.LoadManual(
+ true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/,
+ 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x00100000 /*main_thread_stack_size*/,
+ {}, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, {} /*capabilities*/);
+
+ return result;
+}
+
void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space,
s32 main_thread_prio, u32 main_thread_core,
u32 main_thread_stack_size, u64 title_id,
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index f8759a396..455532567 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -9,7 +9,7 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace Loader {
enum class ResultStatus : u16;
@@ -44,9 +44,13 @@ public:
ProgramMetadata();
~ProgramMetadata();
+ /// Gets a default ProgramMetadata configuration, should only be used for homebrew formats where
+ /// we do not have an NPDM file
+ static ProgramMetadata GetDefault();
+
Loader::ResultStatus Load(VirtualFile file);
- // Load from parameters instead of NPDM file, used for KIP
+ /// Load from parameters instead of NPDM file, used for KIP
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
u32 main_thread_core, u32 main_thread_stack_size, u64 title_id,
u64 filesystem_permissions, KernelCapabilityDescriptors capabilities);
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 6e9cf67ef..da01002d5 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -257,8 +257,7 @@ std::vector<NcaID> PlaceholderCache::List() const {
for (const auto& sdir : dir->GetSubdirectories()) {
for (const auto& file : sdir->GetFiles()) {
const auto name = file->GetName();
- if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' &&
- name[35] == 'a') {
+ if (name.length() == 36 && name.ends_with(".nca")) {
out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32)));
}
}
@@ -344,15 +343,18 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
ContentRecordType type) {
- if (map.find(title_id) == map.end())
- return {};
-
- const auto& cnmt = map.at(title_id);
+ const auto cmnt_iter = map.find(title_id);
+ if (cmnt_iter == map.cend()) {
+ return std::nullopt;
+ }
- const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
+ const auto& cnmt = cmnt_iter->second;
+ const auto& content_records = cnmt.GetContentRecords();
+ const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
[type](const ContentRecord& rec) { return rec.type == type; });
- if (iter == cnmt.GetContentRecords().end())
- return {};
+ if (iter == content_records.cend()) {
+ return std::nullopt;
+ }
return std::make_optional(iter->nca_id);
}
@@ -408,7 +410,7 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
if (file == nullptr)
continue;
- const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0, keys);
+ const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0);
if (nca->GetStatus() != Loader::ResultStatus::Success ||
nca->GetType() != NCAContentType::Meta) {
continue;
@@ -467,14 +469,16 @@ VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType ty
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
- if (meta_iter != meta.end())
+ if (meta_iter != meta.cend()) {
return meta_iter->second.GetTitleVersion();
+ }
const auto yuzu_meta_iter = yuzu_meta.find(title_id);
- if (yuzu_meta_iter != yuzu_meta.end())
+ if (yuzu_meta_iter != yuzu_meta.cend()) {
return yuzu_meta_iter->second.GetTitleVersion();
+ }
- return {};
+ return std::nullopt;
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
@@ -486,7 +490,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_unique<NCA>(raw, nullptr, 0, keys);
+ return std::make_unique<NCA>(raw, nullptr, 0);
}
template <typename T>
@@ -560,57 +564,139 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
return InstallResult::ErrorMetaFailed;
}
- // Install Metadata File
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
const auto meta_id = Common::HexStringToArray<16>(meta_id_raw);
- const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id);
- if (res != InstallResult::Success)
- return res;
+ if ((*meta_iter)->GetSubdirectories().empty()) {
+ LOG_ERROR(Loader,
+ "The file you are attempting to install does not contain a section0 within the "
+ "metadata NCA and is therefore malformed. Verify that the file is valid.");
+ return InstallResult::ErrorMetaFailed;
+ }
- // Install all the other NCAs
const auto section0 = (*meta_iter)->GetSubdirectories()[0];
+
+ if (section0->GetFiles().empty()) {
+ LOG_ERROR(Loader,
+ "The file you are attempting to install does not contain a CNMT within the "
+ "metadata NCA and is therefore malformed. Verify that the file is valid.");
+ return InstallResult::ErrorMetaFailed;
+ }
+
const auto cnmt_file = section0->GetFiles()[0];
const CNMT cnmt(cnmt_file);
+
+ const auto title_id = cnmt.GetTitleID();
+ const auto result = RemoveExistingEntry(title_id);
+
+ // Install Metadata File
+ const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id);
+ if (res != InstallResult::Success) {
+ return res;
+ }
+
+ // Install all the other NCAs
for (const auto& record : cnmt.GetContentRecords()) {
// Ignore DeltaFragments, they are not useful to us
- if (record.type == ContentRecordType::DeltaFragment)
+ if (record.type == ContentRecordType::DeltaFragment) {
continue;
+ }
const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
- if (nca == nullptr)
+ if (nca == nullptr) {
return InstallResult::ErrorCopyFailed;
+ }
const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
- if (res2 != InstallResult::Success)
+ if (res2 != InstallResult::Success) {
return res2;
+ }
}
Refresh();
+ if (result) {
+ return InstallResult::OverwriteExisting;
+ }
return InstallResult::Success;
}
InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
- CNMTHeader header{
- nca.GetTitleId(), ///< Title ID
- 0, ///< Ignore/Default title version
- type, ///< Type
- {}, ///< Padding
- 0x10, ///< Default table offset
- 1, ///< 1 Content Entry
- 0, ///< No Meta Entries
- {}, ///< Padding
+ const CNMTHeader header{
+ .title_id = nca.GetTitleId(),
+ .title_version = 0,
+ .type = type,
+ .reserved = {},
+ .table_offset = 0x10,
+ .number_content_entries = 1,
+ .number_meta_entries = 0,
+ .attributes = 0,
+ .reserved2 = {},
+ .is_committed = 0,
+ .required_download_system_version = 0,
+ .reserved3 = {},
};
- OptionalHeader opt_header{0, 0};
+ const OptionalHeader opt_header{0, 0};
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}};
const auto& data = nca.GetBaseFile()->ReadBytes(0x100000);
mbedtls_sha256_ret(data.data(), data.size(), c_rec.hash.data(), 0);
- memcpy(&c_rec.nca_id, &c_rec.hash, 16);
+ std::memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
- if (!RawInstallYuzuMeta(new_cnmt))
+ if (!RawInstallYuzuMeta(new_cnmt)) {
return InstallResult::ErrorMetaFailed;
+ }
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
+bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
+ const auto delete_nca = [this](const NcaID& id) {
+ const auto path = GetRelativePathFromNcaID(id, false, true, false);
+
+ const bool isFile = dir->GetFileRelative(path) != nullptr;
+ const bool isDir = dir->GetDirectoryRelative(path) != nullptr;
+
+ if (isFile) {
+ return dir->DeleteFile(path);
+ } else if (isDir) {
+ return dir->DeleteSubdirectoryRecursive(path);
+ }
+
+ return false;
+ };
+
+ // If an entry exists in the registered cache, remove it
+ if (HasEntry(title_id, ContentRecordType::Meta)) {
+ LOG_INFO(Loader,
+ "Previously installed entry (v{}) for title_id={:016X} detected! "
+ "Attempting to remove...",
+ GetEntryVersion(title_id).value_or(0), title_id);
+
+ // Get all the ncas associated with the current CNMT and delete them
+ const auto meta_old_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{});
+ const auto program_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{});
+ const auto data_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{});
+ const auto control_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{});
+ const auto html_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{});
+ const auto legal_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{});
+
+ const auto deleted_meta = delete_nca(meta_old_id);
+ const auto deleted_program = delete_nca(program_id);
+ const auto deleted_data = delete_nca(data_id);
+ const auto deleted_control = delete_nca(control_id);
+ const auto deleted_html = delete_nca(html_id);
+ const auto deleted_legal = delete_nca(legal_id);
+
+ return deleted_meta && (deleted_meta || deleted_program || deleted_data ||
+ deleted_control || deleted_html || deleted_legal);
+ }
+
+ return false;
+}
+
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
std::optional<NcaID> override_id) {
@@ -641,12 +727,13 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
LOG_WARNING(Loader, "Overwriting existing NCA...");
VirtualDir c_dir;
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
- c_dir->DeleteFile(FileUtil::GetFilename(path));
+ c_dir->DeleteFile(Common::FS::GetFilename(path));
}
auto out = dir->CreateFileRelative(path);
- if (out == nullptr)
+ if (out == nullptr) {
return InstallResult::ErrorCopyFailed;
+ }
return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
: InstallResult::ErrorCopyFailed;
}
@@ -848,7 +935,8 @@ VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordT
VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto iter =
std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
- const auto [title_type, content_type, e_title_id] = entry.first;
+ const auto content_type = std::get<1>(entry.first);
+ const auto e_title_id = std::get<2>(entry.first);
return content_type == type && e_title_id == title_id;
});
if (iter == entries.end())
@@ -860,7 +948,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
const auto res = GetEntryRaw(title_id, type);
if (res == nullptr)
return nullptr;
- return std::make_unique<NCA>(res, nullptr, 0, keys);
+ return std::make_unique<NCA>(res, nullptr, 0);
}
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index d1eec240e..5b414b0f0 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -34,6 +34,7 @@ using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile
enum class InstallResult {
Success,
+ OverwriteExisting,
ErrorAlreadyExists,
ErrorCopyFailed,
ErrorMetaFailed,
@@ -88,7 +89,7 @@ public:
protected:
// A single instance of KeyManager to be used by GetEntry()
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
};
class PlaceholderCache {
@@ -132,9 +133,9 @@ public:
// Parsing function defines the conversion from raw file to NCA. If there are other steps
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
// parsing function.
- explicit RegisteredCache(VirtualDir dir,
- ContentProviderParsingFunction parsing_function =
- [](const VirtualFile& file, const NcaID& id) { return file; });
+ explicit RegisteredCache(
+ VirtualDir dir, ContentProviderParsingFunction parsing_function =
+ [](const VirtualFile& file, const NcaID& id) { return file; });
~RegisteredCache() override;
void Refresh() override;
@@ -168,6 +169,9 @@ public:
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
+ // Removes an existing entry based on title id
+ bool RemoveExistingEntry(u64 title_id) const;
+
private:
template <typename T>
void IterateAllMetadata(std::vector<T>& out,
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 2fd07ed04..82e683782 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -4,7 +4,6 @@
#pragma once
-#include <array>
#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 418a39a7e..987199747 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -6,7 +6,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
@@ -19,7 +18,9 @@
namespace FileSys {
-RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
+RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
+ Service::FileSystem::FileSystemController& controller)
+ : content_provider{provider}, filesystem_controller{controller} {
// Load the RomFS from the app
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
LOG_ERROR(Service_FS, "Unable to read RomFS!");
@@ -36,49 +37,50 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
}
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
- if (!updatable)
+ if (!updatable) {
return MakeResult<VirtualFile>(file);
+ }
- const PatchManager patch_manager(current_process_title_id);
+ const PatchManager patch_manager{current_process_title_id, filesystem_controller,
+ content_provider};
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
ContentRecordType type) const {
- std::shared_ptr<NCA> res;
-
- switch (storage) {
- case StorageId::None:
- res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
- break;
- case StorageId::NandSystem:
- res =
- Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
- title_id, type);
- break;
- case StorageId::NandUser:
- res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
- title_id, type);
- break;
- case StorageId::SdCard:
- res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
- title_id, type);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
- }
-
+ const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return RESULT_UNKNOWN;
}
+
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return RESULT_UNKNOWN;
}
+
return MakeResult<VirtualFile>(romfs);
}
+std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
+ ContentRecordType type) const {
+ switch (storage) {
+ case StorageId::None:
+ return content_provider.GetEntry(title_id, type);
+ case StorageId::NandSystem:
+ return filesystem_controller.GetSystemNANDContents()->GetEntry(title_id, type);
+ case StorageId::NandUser:
+ return filesystem_controller.GetUserNANDContents()->GetEntry(title_id, type);
+ case StorageId::SdCard:
+ return filesystem_controller.GetSDMCContents()->GetEntry(title_id, type);
+ case StorageId::Host:
+ case StorageId::GameCard:
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
+ return nullptr;
+ }
+}
+
} // 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 {
class AppLoader;
} // namespace Loader
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace FileSys {
+class ContentProvider;
+class NCA;
+
enum class ContentRecordType : u8;
enum class StorageId : u8 {
@@ -29,18 +36,26 @@ enum class StorageId : u8 {
/// File system interface to the RomFS archive
class RomFSFactory {
public:
- explicit RomFSFactory(Loader::AppLoader& app_loader);
+ explicit RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
+ Service::FileSystem::FileSystemController& controller);
~RomFSFactory();
void SetPackedUpdate(VirtualFile update_raw);
- ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
- ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
+ [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
+ [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
+ ContentRecordType type) const;
private:
+ [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
+ ContentRecordType type) const;
+
VirtualFile file;
VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
+
+ ContentProvider& content_provider;
+ Service::FileSystem::FileSystemController& filesystem_controller;
};
} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index f3def93ab..ba4efee3a 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -17,23 +17,23 @@ constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
namespace {
-void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
+void PrintSaveDataAttributeWarnings(SaveDataAttribute meta) {
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
if (meta.zero_1 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x28 is non-zero ({:016X}).",
meta.zero_1);
}
if (meta.zero_2 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x30 is non-zero ({:016X}).",
meta.zero_2);
}
if (meta.zero_3 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x38 is non-zero ({:016X}).",
meta.zero_3);
}
@@ -41,32 +41,32 @@ void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
if (meta.type == SaveDataType::SystemSaveData && meta.title_id != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is SystemSaveData but title_id is "
+ "Possibly incorrect SaveDataAttribute, type is SystemSaveData but title_id is "
"non-zero ({:016X}).",
meta.title_id);
}
if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
+ "Possibly incorrect SaveDataAttribute, type is DeviceSaveData but user_id is "
"non-zero ({:016X}{:016X})",
meta.user_id[1], meta.user_id[0]);
}
}
-bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataDescriptor& desc) {
- return desc.type == SaveDataType::CacheStorage || desc.type == SaveDataType::TemporaryStorage ||
+bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataAttribute& attr) {
+ return attr.type == SaveDataType::CacheStorage || attr.type == SaveDataType::TemporaryStorage ||
(space == SaveDataSpaceId::NandUser && ///< Normal Save Data -- Current Title & User
- desc.type == SaveDataType::SaveData && desc.title_id == 0 && desc.save_id == 0);
+ (attr.type == SaveDataType::SaveData || attr.type == SaveDataType::DeviceSaveData) &&
+ attr.title_id == 0 && attr.save_id == 0);
}
} // Anonymous namespace
-std::string SaveDataDescriptor::DebugInfo() const {
- return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, "
- "save_id={:016X}, "
+std::string SaveDataAttribute::DebugInfo() const {
+ return fmt::format("[title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, type={:02X}, "
"rank={}, index={}]",
- static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
+ title_id, user_id[1], user_id[0], save_id, static_cast<u8>(type),
static_cast<u8>(rank), index);
}
@@ -79,8 +79,8 @@ SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save
SaveDataFactory::~SaveDataFactory() = default;
ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
- const SaveDataDescriptor& meta) const {
- PrintSaveDataDescriptorWarnings(meta);
+ const SaveDataAttribute& meta) const {
+ PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -97,7 +97,7 @@ ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
}
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
- const SaveDataDescriptor& meta) const {
+ const SaveDataAttribute& meta) const {
const auto save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -139,8 +139,10 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
- if (type == SaveDataType::SaveData && title_id == 0) {
- title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
+ if (title_id == 0) {
+ title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ }
}
std::string out = GetSaveDataSpaceIdPath(space);
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 991e57aa1..6625bbbd8 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -21,6 +21,7 @@ enum class SaveDataSpaceId : u8 {
TemporaryStorage = 3,
SdCardUser = 4,
ProperSystem = 100,
+ SafeMode = 101,
};
enum class SaveDataType : u8 {
@@ -30,28 +31,50 @@ enum class SaveDataType : u8 {
DeviceSaveData = 3,
TemporaryStorage = 4,
CacheStorage = 5,
+ SystemBcat = 6,
};
enum class SaveDataRank : u8 {
- Primary,
- Secondary,
+ Primary = 0,
+ Secondary = 1,
};
-struct SaveDataDescriptor {
- u64_le title_id;
+enum class SaveDataFlags : u32 {
+ None = (0 << 0),
+ KeepAfterResettingSystemSaveData = (1 << 0),
+ KeepAfterRefurbishment = (1 << 1),
+ KeepAfterResettingSystemSaveDataWithoutUserSaveData = (1 << 2),
+ NeedsSecureDelete = (1 << 3),
+};
+
+struct SaveDataAttribute {
+ u64 title_id;
u128 user_id;
- u64_le save_id;
+ u64 save_id;
SaveDataType type;
SaveDataRank rank;
- u16_le index;
+ u16 index;
INSERT_PADDING_BYTES(4);
- u64_le zero_1;
- u64_le zero_2;
- u64_le zero_3;
+ u64 zero_1;
+ u64 zero_2;
+ u64 zero_3;
std::string DebugInfo() const;
};
-static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size.");
+static_assert(sizeof(SaveDataAttribute) == 0x40, "SaveDataAttribute has incorrect size.");
+
+struct SaveDataExtraData {
+ SaveDataAttribute attr;
+ u64 owner_id;
+ s64 timestamp;
+ SaveDataFlags flags;
+ INSERT_PADDING_BYTES(4);
+ s64 available_size;
+ s64 journal_size;
+ s64 commit_id;
+ std::array<u8, 0x190> unused;
+};
+static_assert(sizeof(SaveDataExtraData) == 0x200, "SaveDataExtraData has incorrect size.");
struct SaveDataSize {
u64 normal;
@@ -64,8 +87,8 @@ public:
explicit SaveDataFactory(VirtualDir dir);
~SaveDataFactory();
- ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
- ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
+ ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 5113a1ca6..cb56d8f2d 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -5,11 +5,13 @@
#include <memory>
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/sdmc_factory.h"
+#include "core/file_sys/vfs.h"
#include "core/file_sys/xts_archive.h"
-#include "core/settings.h"
namespace FileSys {
+constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB
+
SDMCFactory::SDMCFactory(VirtualDir dir_)
: dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
@@ -46,7 +48,7 @@ u64 SDMCFactory::GetSDMCFreeSpace() const {
}
u64 SDMCFactory::GetSDMCTotalSpace() const {
- return static_cast<u64>(Settings::values.sdmc_size);
+ return SDMC_TOTAL_SIZE;
}
} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 42dc4e08a..2bb92ba93 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -5,7 +5,7 @@
#pragma once
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/hle/result.h"
namespace FileSys {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index ef3084681..90641d23b 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -19,42 +19,10 @@
#include "core/loader/loader.h"
namespace FileSys {
-namespace {
-void SetTicketKeys(const std::vector<VirtualFile>& files) {
- Core::Crypto::KeyManager keys;
-
- for (const auto& ticket_file : files) {
- if (ticket_file == nullptr) {
- continue;
- }
-
- if (ticket_file->GetExtension() != "tik") {
- continue;
- }
-
- if (ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
- continue;
- }
-
- Core::Crypto::Key128 key{};
- ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
-
- // We get the name without the extension in order to create the rights ID.
- std::string name_only(ticket_file->GetName());
- name_only.erase(name_only.size() - 4);
-
- const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
- u128 rights_id;
- std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
- keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
- }
-}
-} // Anonymous namespace
NSP::NSP(VirtualFile file_)
: file(std::move(file_)), status{Loader::ResultStatus::Success},
- pfs(std::make_shared<PartitionFilesystem>(file)) {
+ pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus();
return;
@@ -232,6 +200,35 @@ VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
+void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
+ for (const auto& ticket_file : files) {
+ if (ticket_file == nullptr) {
+ continue;
+ }
+
+ if (ticket_file->GetExtension() != "tik") {
+ continue;
+ }
+
+ if (ticket_file->GetSize() <
+ Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+ continue;
+ }
+
+ Core::Crypto::Key128 key{};
+ ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
+
+ // We get the name without the extension in order to create the rights ID.
+ std::string name_only(ticket_file->GetName());
+ name_only.erase(name_only.size() - 4);
+
+ const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
+ u128 rights_id;
+ std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
+ keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ }
+}
+
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
@@ -267,9 +264,9 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
}
const CNMT cnmt(inner_file);
- auto& ncas_title = ncas[cnmt.GetTitleID()];
- ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
+ ncas[cnmt.GetTitleID()][{cnmt.GetType(), ContentRecordType::Meta}] = nca;
+
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexToString(rec.nca_id, false);
auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
@@ -285,14 +282,33 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue;
}
- auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys);
+ auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
+
if (next_nca->GetType() == NCAContentType::Program) {
- program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
+ program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
}
- if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
- (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
- (cnmt.GetTitleID() & 0x800) != 0)) {
- ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
+
+ if (next_nca->GetStatus() != Loader::ResultStatus::Success &&
+ next_nca->GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ continue;
+ }
+
+ // If the last 3 hexadecimal digits of the CNMT TitleID is 0x800 or is missing the
+ // BKTRBaseRomFS, this is an update NCA. Otherwise, this is a base NCA.
+ if ((cnmt.GetTitleID() & 0x800) != 0 ||
+ next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
+ // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
+ // update NCA.
+ if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
+ (next_nca->GetTitleId() & 0x800) == 0) {
+ ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
+ std::move(next_nca);
+ } else {
+ ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
+ }
+ } else {
+ ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
}
}
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index ee9b6ce17..c70a11b5b 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -59,6 +63,7 @@ public:
VirtualDir GetParentDirectory() const override;
private:
+ void SetTicketKeys(const std::vector<VirtualFile>& files);
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
@@ -73,7 +78,7 @@ private:
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys;
VirtualFile romfs;
VirtualDir exefs;
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
index 6a9add87c..d65c7d234 100644
--- a/src/core/file_sys/system_archive/mii_model.cpp
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -27,20 +27,14 @@ VirtualDir MiiModel() {
auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
std::vector<VirtualDir>{}, "data");
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
- MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
- MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
- MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
- MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
- MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
- MiiModelData::SHAPE_MID, "ShapeMid.dat"));
-
- return std::move(out);
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::SHAPE_MID, "ShapeMid.dat"));
+
+ return out;
}
} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
index f4443784d..100d3c5db 100644
--- a/src/core/file_sys/system_archive/ng_word.cpp
+++ b/src/core/file_sys/system_archive/ng_word.cpp
@@ -24,19 +24,18 @@ constexpr std::array<u8, 30> WORD_TXT{
} // namespace NgWord1Data
VirtualDir NgWord1() {
- std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES);
+ std::vector<VirtualFile> files;
+ files.reserve(NgWord1Data::NUMBER_WORD_TXT_FILES);
for (std::size_t i = 0; i < files.size(); ++i) {
- files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
- NgWord1Data::WORD_TXT, fmt::format("{}.txt", i));
+ files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, fmt::format("{}.txt", i)));
}
- files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
- NgWord1Data::WORD_TXT, "common.txt"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>(
- NgWord1Data::VERSION_DAT, "version.dat"));
+ files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, "common.txt"));
+ files.push_back(MakeArrayFile(NgWord1Data::VERSION_DAT, "version.dat"));
- return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+ return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{},
+ "data");
}
namespace NgWord2Data {
@@ -55,27 +54,22 @@ constexpr std::array<u8, 0x2C> AC_NX_DATA{
} // namespace NgWord2Data
VirtualDir NgWord2() {
- std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3);
+ std::vector<VirtualFile> files;
+ files.reserve(NgWord2Data::NUMBER_AC_NX_FILES * 3);
for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) {
- files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i));
- files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i));
- files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i)));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i)));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i)));
}
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>(
- NgWord2Data::VERSION_DAT, "version.dat"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::VERSION_DAT, "version.dat"));
- return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+ return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{},
+ "data");
}
} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp
index 2c05eb42e..c5cdf7d9b 100644
--- a/src/core/file_sys/system_archive/shared_font.cpp
+++ b/src/core/file_sys/system_archive/shared_font.cpp
@@ -23,7 +23,7 @@ VirtualFile PackBFTTF(const std::array<u8, Size>& data, const std::string& name)
std::vector<u8> bfttf(Size + sizeof(u64));
- u64 offset = 0;
+ size_t offset = 0;
Service::NS::EncryptSharedFont(vec, bfttf, offset);
return std::make_shared<VectorVfsFile>(std::move(bfttf), name);
}
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index 6e22f97b0..aa313de66 100644
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -12,17 +12,17 @@ namespace SystemVersionData {
// This section should reflect the best system version to describe yuzu's HLE api.
// TODO(DarkLordZach): Update when HLE gets better.
-constexpr u8 VERSION_MAJOR = 5;
-constexpr u8 VERSION_MINOR = 1;
-constexpr u8 VERSION_MICRO = 0;
+constexpr u8 VERSION_MAJOR = 10;
+constexpr u8 VERSION_MINOR = 0;
+constexpr u8 VERSION_MICRO = 2;
-constexpr u8 REVISION_MAJOR = 3;
+constexpr u8 REVISION_MAJOR = 1;
constexpr u8 REVISION_MINOR = 0;
constexpr char PLATFORM_STRING[] = "NX";
-constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd";
-constexpr char DISPLAY_VERSION[] = "5.1.0";
-constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0";
+constexpr char VERSION_HASH[] = "f90143fa8bbc061d4f68c35f95f04f8080c0ecdc";
+constexpr char DISPLAY_VERSION[] = "10.0.2";
+constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 10.0.2-1.0";
} // namespace SystemVersionData
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index 9806bd197..8fd005012 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <vector>
+
#include "common/swap.h"
#include "core/file_sys/system_archive/time_zone_binary.h"
#include "core/file_sys/vfs_vector.h"
@@ -615,43 +618,49 @@ static constexpr std::array<u8, 9633> LOCATION_NAMES{
0x0a};
static VirtualFile GenerateDefaultTimeZoneFile() {
- struct {
+ struct TimeZoneInfo {
s64_be at;
- INSERT_PADDING_BYTES(7);
+ std::array<u8, 7> padding1;
std::array<char, 4> time_zone_chars;
- INSERT_PADDING_BYTES(2);
+ std::array<u8, 2> padding2;
std::array<char, 6> time_zone_name;
- } time_zone_info{};
+ };
- const VirtualFile file{std::make_shared<VectorVfsFile>(
- std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(time_zone_info)),
+ VirtualFile file{std::make_shared<VectorVfsFile>(
+ std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(TimeZoneInfo)),
"GMT")};
- Service::Time::TimeZone::TzifHeader header{};
- header.magic = 0x545a6966;
- header.version = 0x32;
- header.ttis_gmt_count = 0x1;
- header.ttis_std_count = 0x1;
- header.time_count = 0x1;
- header.type_count = 0x1;
- header.char_count = 0x4;
+ const Service::Time::TimeZone::TzifHeader header{
+ .magic = 0x545a6966,
+ .version = 0x32,
+ .ttis_gmt_count = 1,
+ .ttis_std_count = 1,
+ .time_count = 1,
+ .type_count = 1,
+ .char_count = 4,
+ };
file->WriteObject(header, 0);
- time_zone_info.at = 0xf8;
- time_zone_info.time_zone_chars = {'G', 'M', 'T', '\0'};
- time_zone_info.time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'};
+ const TimeZoneInfo time_zone_info{
+ .at = 0xf8,
+ .padding1 = {},
+ .time_zone_chars = {'G', 'M', 'T', '\0'},
+ .padding2 = {},
+ .time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'},
+ };
file->WriteObject(time_zone_info, sizeof(Service::Time::TimeZone::TzifHeader));
return file;
}
VirtualDir TimeZoneBinary() {
- const std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>(
+ std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{GenerateDefaultTimeZoneFile()}, std::vector<VirtualDir>{},
"zoneinfo")};
- const std::vector<VirtualFile> root_files{
- std::make_shared<ArrayVfsFile<LOCATION_NAMES.size()>>(LOCATION_NAMES, "binaryList.txt")};
- return std::make_shared<VectorVfsDirectory>(root_files, root_dirs, "data");
+ std::vector<VirtualFile> root_files{MakeArrayFile(LOCATION_NAMES, "binaryList.txt")};
+
+ return std::make_shared<VectorVfsDirectory>(std::move(root_files), std::move(root_dirs),
+ "data");
}
} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index e33327ef0..b2f026b6d 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -30,7 +30,7 @@ bool VfsFilesystem::IsWritable() const {
}
VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
if (root->GetFileRelative(path) != nullptr)
return VfsEntryType::File;
if (root->GetDirectoryRelative(path) != nullptr)
@@ -40,22 +40,22 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
}
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->GetFileRelative(path);
}
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->CreateFileRelative(path);
}
VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path = FileUtil::SanitizePath(old_path_);
- const auto new_path = FileUtil::SanitizePath(new_path_);
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
// VfsDirectory impls are only required to implement copy across the current directory.
- if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) {
- if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path)))
+ if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
+ if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
return nullptr;
return OpenFile(new_path, Mode::ReadWrite);
}
@@ -76,8 +76,8 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
}
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
- const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
- const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
// Again, non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyFile(sanitized_old_path, sanitized_new_path);
@@ -89,26 +89,26 @@ VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view
}
bool VfsFilesystem::DeleteFile(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_);
- auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
if (parent == nullptr)
return false;
- return parent->DeleteFile(FileUtil::GetFilename(path));
+ return parent->DeleteFile(Common::FS::GetFilename(path));
}
VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->GetDirectoryRelative(path);
}
VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->CreateDirectoryRelative(path);
}
VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path = FileUtil::SanitizePath(old_path_);
- const auto new_path = FileUtil::SanitizePath(new_path_);
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
// Non-default impls are highly encouraged to provide a more optimized version of this.
auto old_dir = OpenDirectory(old_path, Mode::Read);
@@ -139,8 +139,8 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
}
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
- const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
- const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
// Non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
@@ -152,28 +152,29 @@ VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_v
}
bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_);
- auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
if (parent == nullptr)
return false;
- return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path));
+ return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
}
VfsFile::~VfsFile() = default;
std::string VfsFile::GetExtension() const {
- return std::string(FileUtil::GetExtensionFromFilename(GetName()));
+ return std::string(Common::FS::GetExtensionFromFilename(GetName()));
}
VfsDirectory::~VfsDirectory() = default;
std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
u8 out{};
- std::size_t size = Read(&out, 1, offset);
- if (size == 1)
+ const std::size_t size = Read(&out, sizeof(u8), offset);
+ if (size == 1) {
return out;
+ }
- return {};
+ return std::nullopt;
}
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
@@ -203,7 +204,7 @@ std::string VfsFile::GetFullPath() const {
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -239,7 +240,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -301,7 +302,7 @@ std::size_t VfsDirectory::GetSize() const {
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -320,7 +321,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
}
}
- return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path));
+ return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
@@ -332,7 +333,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -351,7 +352,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
}
}
- return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path));
+ return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index 16d801c0c..e0ff70174 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -11,7 +11,7 @@
namespace FileSys {
-static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
+static bool VerifyConcatenationMapContinuity(const std::multimap<u64, VirtualFile>& map) {
const auto last_valid = --map.end();
for (auto iter = map.begin(); iter != last_valid;) {
const auto old = iter++;
@@ -27,12 +27,12 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
: name(std::move(name)) {
std::size_t next_offset = 0;
for (const auto& file : files_) {
- files[next_offset] = file;
+ files.emplace(next_offset, file);
next_offset += file->GetSize();
}
}
-ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
+ConcatenatedVfsFile::ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files_, std::string name)
: files(std::move(files_)), name(std::move(name)) {
ASSERT(VerifyConcatenationMapContinuity(files));
}
@@ -50,7 +50,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> f
}
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
- std::map<u64, VirtualFile> files,
+ std::multimap<u64, VirtualFile> files,
std::string name) {
if (files.empty())
return nullptr;
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index c90f9d5d1..7a26343c0 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -15,7 +15,7 @@ namespace FileSys {
// read-only.
class ConcatenatedVfsFile : public VfsFile {
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
- ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
+ ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files, std::string name);
public:
~ConcatenatedVfsFile() override;
@@ -25,7 +25,7 @@ public:
/// Convenience function that turns a map of offsets to files into a concatenated file, filling
/// gaps with a given filler byte.
- static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files,
+ static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::multimap<u64, VirtualFile> files,
std::string name);
std::string GetName() const override;
@@ -40,7 +40,7 @@ public:
private:
// Maps starting offset to file -- more efficient.
- std::map<u64, VirtualFile> files;
+ std::multimap<u64, VirtualFile> files;
std::string name;
};
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index 11d1978ea..429d7bc8b 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -42,14 +42,14 @@ VirtualDir ExtractZIP(VirtualFile file) {
continue;
if (name.back() != '/') {
- std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file{
+ std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file2{
zip_fopen_index(zip.get(), i, 0), zip_fclose};
std::vector<u8> buf(stat.size);
- if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size())
+ if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size()))
return nullptr;
- const auto parts = FileUtil::SplitPathComponents(stat.name);
+ const auto parts = Common::FS::SplitPathComponents(stat.name);
const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
std::shared_ptr<VectorVfsDirectory> dtrv = out;
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index c96f88488..7714d3de5 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -58,10 +58,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
}
std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
- if (r_offset < size)
- return file->ReadByte(offset + r_offset);
+ if (r_offset >= size) {
+ return std::nullopt;
+ }
- return {};
+ return file->ReadByte(offset + r_offset);
}
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index e21300a7c..488687ba9 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -14,24 +14,28 @@
namespace FileSys {
+namespace FS = Common::FS;
+
static std::string ModeFlagsToString(Mode mode) {
std::string mode_str;
// Calculate the correct open mode for the file.
- if (mode & Mode::Read && mode & Mode::Write) {
- if (mode & Mode::Append)
+ if (True(mode & Mode::Read) && True(mode & Mode::Write)) {
+ if (True(mode & Mode::Append)) {
mode_str = "a+";
- else
+ } else {
mode_str = "r+";
+ }
} else {
- if (mode & Mode::Read)
+ if (True(mode & Mode::Read)) {
mode_str = "r";
- else if (mode & Mode::Append)
+ } else if (True(mode & Mode::Append)) {
mode_str = "a";
- else if (mode & Mode::Write)
+ } else if (True(mode & Mode::Write)) {
mode_str = "w";
- else
+ } else {
UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode));
+ }
}
mode_str += "b";
@@ -55,102 +59,118 @@ bool RealVfsFilesystem::IsWritable() const {
}
VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::Exists(path)) {
return VfsEntryType::None;
- if (FileUtil::IsDirectory(path))
+ }
+ if (FS::IsDirectory(path)) {
return VfsEntryType::Directory;
+ }
return VfsEntryType::File;
}
VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (cache.find(path) != cache.end()) {
- auto weak = cache[path];
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+
+ if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) {
+ const auto& weak = weak_iter->second;
+
if (!weak.expired()) {
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
}
}
- if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0)
- FileUtil::CreateEmptyFile(path);
+ if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
+ FS::CreateEmptyFile(path);
+ }
- auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str());
- cache[path] = backing;
+ auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str());
+ cache.insert_or_assign(path, backing);
// Cannot use make_shared as RealVfsFile constructor is private
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
}
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path_fwd);
- if (!FileUtil::CreateEmptyFile(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
+ if (!FS::Exists(path)) {
+ FS::CreateFullPath(path_fwd);
+ if (!FS::CreateEmptyFile(path)) {
return nullptr;
+ }
}
return OpenFile(path, perms);
}
VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path))
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Copy(old_path, new_path)) {
return nullptr;
+ }
return OpenFile(new_path, Mode::ReadWrite);
}
VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto cached_file_iter = cache.find(old_path);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
- return nullptr;
+ if (cached_file_iter != cache.cend()) {
+ auto file = cached_file_iter->second.lock();
- if (cache.find(old_path) != cache.end()) {
- auto cached = cache[old_path];
- if (!cached.expired()) {
- auto file = cached.lock();
- file->Open(new_path, "r+b");
- cache.erase(old_path);
- cache[new_path] = file;
+ if (!cached_file_iter->second.expired()) {
+ file->Close();
}
+
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Rename(old_path, new_path)) {
+ return nullptr;
+ }
+
+ cache.erase(old_path);
+ file->Open(new_path, "r+b");
+ cache.insert_or_assign(new_path, std::move(file));
+ } else {
+ UNREACHABLE();
+ return nullptr;
}
+
return OpenFile(new_path, Mode::ReadWrite);
}
bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (cache.find(path) != cache.end()) {
- if (!cache[path].expired())
- cache[path].lock()->Close();
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto cached_iter = cache.find(path);
+
+ if (cached_iter != cache.cend()) {
+ if (!cached_iter->second.expired()) {
+ cached_iter->second.lock()->Close();
+ }
cache.erase(path);
}
- return FileUtil::Delete(path);
+
+ return FS::Delete(path);
}
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
}
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path_fwd);
- if (!FileUtil::CreateDir(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
+ if (!FS::Exists(path)) {
+ FS::CreateFullPath(path_fwd);
+ if (!FS::CreateDir(path)) {
return nullptr;
+ }
}
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
@@ -158,67 +178,75 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- !FileUtil::IsDirectory(old_path))
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) {
return nullptr;
- FileUtil::CopyDir(old_path, new_path);
+ }
+ FS::CopyDir(old_path, new_path);
return OpenDirectory(new_path, Mode::ReadWrite);
}
VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Rename(old_path, new_path)) {
return nullptr;
+ }
for (auto& kv : cache) {
- // Path in cache starts with old_path
- if (kv.first.rfind(old_path, 0) == 0) {
- const auto file_old_path =
- FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault);
- const auto file_new_path =
- FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
- FileUtil::DirectorySeparator::PlatformDefault);
- auto cached = cache[file_old_path];
- if (!cached.expired()) {
- auto file = cached.lock();
- file->Open(file_new_path, "r+b");
- cache.erase(file_old_path);
- cache[file_new_path] = file;
- }
+ // If the path in the cache doesn't start with old_path, then bail on this file.
+ if (kv.first.rfind(old_path, 0) != 0) {
+ continue;
+ }
+
+ const auto file_old_path =
+ FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault);
+ auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
+ FS::DirectorySeparator::PlatformDefault);
+ const auto& cached = cache[file_old_path];
+
+ if (cached.expired()) {
+ continue;
}
+
+ auto file = cached.lock();
+ file->Open(file_new_path, "r+b");
+ cache.erase(file_old_path);
+ cache.insert_or_assign(std::move(file_new_path), std::move(file));
}
return OpenDirectory(new_path, Mode::ReadWrite);
}
bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+
for (auto& kv : cache) {
- // Path in cache starts with old_path
- if (kv.first.rfind(path, 0) == 0) {
- if (!cache[kv.first].expired())
- cache[kv.first].lock()->Close();
- cache.erase(kv.first);
+ // If the path in the cache doesn't start with path, then bail on this file.
+ if (kv.first.rfind(path, 0) != 0) {
+ continue;
+ }
+
+ const auto& entry = cache[kv.first];
+ if (!entry.expired()) {
+ entry.lock()->Close();
}
+
+ cache.erase(kv.first);
}
- return FileUtil::DeleteDirRecursively(path);
+
+ return FS::DeleteDirRecursively(path);
}
-RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_,
+RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_,
const std::string& path_, Mode perms_)
- : base(base_), backing(std::move(backing_)), path(path_),
- parent_path(FileUtil::GetParentPath(path_)),
- path_components(FileUtil::SplitPathComponents(path_)),
- parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
+ : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)),
+ path_components(FS::SplitPathComponents(path_)),
+ parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {}
RealVfsFile::~RealVfsFile() = default;
@@ -240,22 +268,24 @@ std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
}
bool RealVfsFile::IsWritable() const {
- return (perms & Mode::WriteAppend) != 0;
+ return True(perms & Mode::WriteAppend);
}
bool RealVfsFile::IsReadable() const {
- return (perms & Mode::ReadWrite) != 0;
+ return True(perms & Mode::ReadWrite);
}
std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
- if (!backing->Seek(offset, SEEK_SET))
+ if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
return 0;
+ }
return backing->ReadBytes(data, length);
}
std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
- if (!backing->Seek(offset, SEEK_SET))
+ if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
return 0;
+ }
return backing->WriteBytes(data, length);
}
@@ -272,16 +302,18 @@ bool RealVfsFile::Close() {
template <>
std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::vector<VirtualFile> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- if (!FileUtil::IsDirectory(full_path))
+ if (!FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenFile(full_path, perms));
+ }
return true;
});
@@ -290,16 +322,18 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
template <>
std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::vector<VirtualDir> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- if (FileUtil::IsDirectory(full_path))
+ if (FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenDirectory(full_path, perms));
+ }
return true;
});
@@ -307,28 +341,30 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
}
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
- : base(base_), path(FileUtil::RemoveTrailingSlash(path_)),
- parent_path(FileUtil::GetParentPath(path)),
- path_components(FileUtil::SplitPathComponents(path)),
- parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
+ : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
+ path_components(FS::SplitPathComponents(path)),
+ parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {
- if (!FileUtil::Exists(path) && perms & Mode::WriteAppend)
- FileUtil::CreateDir(path);
+ if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
+ FS::CreateDir(path);
+ }
}
RealVfsDirectory::~RealVfsDirectory() = default;
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
- if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
+ if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
return nullptr;
+ }
return base.OpenFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
- if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
+ if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
return nullptr;
+ }
return base.OpenDirectory(full_path, perms);
}
@@ -341,17 +377,17 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view
}
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateDirectory(full_path, perms);
}
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
- auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name));
return base.DeleteDirectory(full_path);
}
@@ -364,11 +400,11 @@ std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories()
}
bool RealVfsDirectory::IsWritable() const {
- return (perms & Mode::WriteAppend) != 0;
+ return True(perms & Mode::WriteAppend);
}
bool RealVfsDirectory::IsReadable() const {
- return (perms & Mode::ReadWrite) != 0;
+ return True(perms & Mode::ReadWrite);
}
std::string RealVfsDirectory::GetName() const {
@@ -376,8 +412,9 @@ std::string RealVfsDirectory::GetName() const {
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
- if (path_components.size() <= 1)
+ if (path_components.size() <= 1) {
return nullptr;
+ }
return base.OpenDirectory(parent_path, perms);
}
@@ -414,16 +451,17 @@ std::string RealVfsDirectory::GetFullPath() const {
}
std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::map<std::string, VfsEntryType, std::less<>> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
- : VfsEntryType::File);
+ out.emplace(filename,
+ FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
return true;
});
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index a0a857a31..0b537b22c 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -9,7 +9,7 @@
#include "core/file_sys/mode.h"
#include "core/file_sys/vfs.h"
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
@@ -36,7 +36,7 @@ public:
bool DeleteDirectory(std::string_view path) override;
private:
- boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache;
+ boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache;
};
// An implmentation of VfsFile that represents a file on the user's computer.
@@ -58,13 +58,13 @@ public:
bool Rename(std::string_view name) override;
private:
- RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
+ RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing,
const std::string& path, Mode perms = Mode::Read);
bool Close();
RealVfsFilesystem& base;
- std::shared_ptr<FileUtil::IOFile> backing;
+ std::shared_ptr<Common::FS::IOFile> backing;
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 9f5a90b1b..8b27c30fa 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -54,9 +54,11 @@ public:
}
std::optional<u8> ReadByte(std::size_t offset) const override {
- if (offset < size)
- return value;
- return {};
+ if (offset >= size) {
+ return std::nullopt;
+ }
+
+ return value;
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index ac36cb2ee..95d3da2f2 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -4,7 +4,11 @@
#pragma once
+#include <array>
#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -13,7 +17,8 @@ namespace FileSys {
template <std::size_t size>
class ArrayVfsFile : public VfsFile {
public:
- ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr)
+ explicit ArrayVfsFile(const std::array<u8, size>& data, std::string name = "",
+ VirtualDir parent = nullptr)
: data(data), name(std::move(name)), parent(std::move(parent)) {}
std::string GetName() const override {
@@ -61,6 +66,12 @@ private:
VirtualDir parent;
};
+template <std::size_t Size, typename... Args>
+std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data,
+ Args&&... args) {
+ return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...);
+}
+
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index 86e06ccb9..24c58e7ae 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -15,8 +15,9 @@
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
#include "core/crypto/xts_encryption_layer.h"
-#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
@@ -43,8 +44,10 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t
return true;
}
-NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
- std::string path = FileUtil::SanitizePath(file->GetFullPath());
+NAX::NAX(VirtualFile file_)
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
+ std::string path = Common::FS::SanitizePath(file->GetFullPath());
static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
std::regex_constants::ECMAScript |
std::regex_constants::icase);
@@ -60,7 +63,8 @@ NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::m
}
NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
- : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
@@ -70,14 +74,18 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
NAX::~NAX() = default;
Loader::ResultStatus NAX::Parse(std::string_view path) {
- if (file->ReadObject(header.get()) != sizeof(NAXHeader))
+ if (file == nullptr) {
+ return Loader::ResultStatus::ErrorNullFile;
+ }
+ if (file->ReadObject(header.get()) != sizeof(NAXHeader)) {
return Loader::ResultStatus::ErrorBadNAXHeader;
-
- if (header->magic != Common::MakeMagic('N', 'A', 'X', '0'))
+ }
+ if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) {
return Loader::ResultStatus::ErrorBadNAXHeader;
-
- if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size)
+ }
+ if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) {
return Loader::ResultStatus::ErrorIncorrectNAXFileSize;
+ }
keys.DeriveSDSeedLazy();
std::array<Core::Crypto::Key256, 2> sd_keys{};
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 7704dee90..c472e226e 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -9,12 +9,16 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/crypto/key_manager.h"
-#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
+class NCA;
+
struct NAXHeader {
std::array<u8, 0x20> hmac;
u64_le magic;
@@ -62,6 +66,6 @@ private:
VirtualFile dec_file;
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
new file mode 100644
index 000000000..03bbedf8b
--- /dev/null
+++ b/src/core/frontend/applets/controller.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Core::Frontend {
+
+ControllerApplet::~ControllerApplet() = default;
+
+DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
+ : service_manager{service_manager_} {}
+
+DefaultControllerApplet::~DefaultControllerApplet() = default;
+
+void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const {
+ LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
+
+ auto& npad =
+ service_manager.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ auto& players = Settings::values.players.GetValue();
+
+ const std::size_t min_supported_players =
+ parameters.enable_single_mode ? 1 : parameters.min_players;
+
+ // Disconnect Handheld first.
+ npad.DisconnectNpadAtIndex(8);
+
+ // Deduce the best configuration based on the input parameters.
+ for (std::size_t index = 0; index < players.size() - 2; ++index) {
+ // First, disconnect all controllers regardless of the value of keep_controllers_connected.
+ // This makes it easy to connect the desired controllers.
+ npad.DisconnectNpadAtIndex(index);
+
+ // Only connect the minimum number of required players.
+ if (index >= min_supported_players) {
+ continue;
+ }
+
+ // Connect controllers based on the following priority list from highest to lowest priority:
+ // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
+ if (parameters.allow_pro_controller) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
+ } else if (parameters.allow_dual_joycons) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
+ } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
+ // Assign left joycons to even player indices and right joycons to odd player indices.
+ // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
+ // a right Joycon for Player 2 in 2 Player Assist mode.
+ if (index % 2 == 0) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
+ } else {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
+ }
+ } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
+ !Settings::values.use_docked_mode.GetValue()) {
+ // We should *never* reach here under any normal circumstances.
+ npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
+ index);
+ } else {
+ UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
+ }
+ }
+
+ callback();
+}
+
+} // 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..dff71d8d9
--- /dev/null
+++ b/src/core/frontend/applets/controller.h
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+#include "common/common_types.h"
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Core::Frontend {
+
+using BorderColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+struct ControllerParameters {
+ s8 min_players{};
+ s8 max_players{};
+ bool keep_controllers_connected{};
+ bool enable_single_mode{};
+ bool enable_border_color{};
+ std::vector<BorderColor> border_colors{};
+ bool enable_explain_text{};
+ std::vector<ExplainText> explain_text{};
+ bool allow_pro_controller{};
+ bool allow_handheld{};
+ bool allow_dual_joycons{};
+ bool allow_left_joycon{};
+ bool allow_right_joycon{};
+};
+
+class ControllerApplet {
+public:
+ virtual ~ControllerApplet();
+
+ virtual void ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const = 0;
+};
+
+class DefaultControllerApplet final : public ControllerApplet {
+public:
+ explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
+ ~DefaultControllerApplet() override;
+
+ void ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const override;
+
+private:
+ Service::SM::ServiceManager& service_manager;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index eda466a5d..8c1193894 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -46,7 +46,7 @@ private:
EmuWindow::EmuWindow() {
// TODO: Find a better place to set this.
config.min_client_area_size =
- std::make_pair(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
active_config = config;
touch_state = std::make_shared<TouchState>();
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
@@ -84,10 +84,12 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
return;
std::lock_guard guard{touch_state->mutex};
- touch_state->touch_x = static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
- (framebuffer_layout.screen.right - framebuffer_layout.screen.left);
- touch_state->touch_y = static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
- (framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
+ touch_state->touch_x =
+ static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
+ static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
+ touch_state->touch_y =
+ static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
+ static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
touch_state->touch_pressed = true;
}
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 13aa14934..3e8780243 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -39,7 +39,7 @@ public:
class Scoped {
public:
- explicit Scoped(GraphicsContext& context_) : context(context_) {
+ [[nodiscard]] explicit Scoped(GraphicsContext& context_) : context(context_) {
context.MakeCurrent();
}
~Scoped() {
@@ -52,7 +52,7 @@ public:
/// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
/// ends
- Scoped Acquire() {
+ [[nodiscard]] Scoped Acquire() {
return Scoped{*this};
}
};
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 68a0e0906..b9a270a55 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -14,8 +14,8 @@ namespace Layout {
template <class T>
static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
float screen_aspect_ratio) {
- float scale = std::min(static_cast<float>(window_area.GetWidth()),
- window_area.GetHeight() / screen_aspect_ratio);
+ const float scale = std::min(static_cast<float>(window_area.GetWidth()),
+ static_cast<float>(window_area.GetHeight()) / screen_aspect_ratio);
return Common::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
static_cast<T>(std::round(scale * screen_aspect_ratio))};
}
@@ -25,11 +25,11 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
ASSERT(height > 0);
// The drawing code needs at least somewhat valid values for both screens
// so just calculate them both even if the other isn't showing.
- FramebufferLayout res{width, height};
+ FramebufferLayout res{width, height, false, {}};
- const float window_aspect_ratio = static_cast<float>(height) / width;
+ const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
const float emulation_aspect_ratio = EmulationAspectRatio(
- static_cast<AspectRatio>(Settings::values.aspect_ratio), window_aspect_ratio);
+ static_cast<AspectRatio>(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio);
const Common::Rectangle<u32> screen_window_area{0, 0, width, height};
Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
@@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
u32 width, height;
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
width = ScreenDocked::Width * res_scale;
height = ScreenDocked::Height * res_scale;
} else {
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 15ecfb13d..e2e3bbbb3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -4,10 +4,16 @@
#pragma once
+#include "common/common_types.h"
#include "common/math_util.h"
namespace Layout {
+namespace MinimumSize {
+constexpr u32 Width = 640;
+constexpr u32 Height = 360;
+} // namespace MinimumSize
+
namespace ScreenUndocked {
constexpr u32 Width = 1280;
constexpr u32 Height = 720;
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 2b098b7c6..11c2e96ca 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -30,7 +30,12 @@ public:
virtual StatusType GetStatus() const {
return {};
}
- virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
+ virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
+ return {};
+ }
+ virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
+ [[maybe_unused]] f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const {
return {};
}
};
@@ -119,11 +124,18 @@ using ButtonDevice = InputDevice<bool>;
using AnalogDevice = InputDevice<std::tuple<float, float>>;
/**
- * A motion device is an input device that returns a tuple of accelerometer state vector and
- * gyroscope state vector.
+ * A vibration device is an input device that returns an unsigned byte as status.
+ * It represents whether the vibration device supports vibration or not.
+ * If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
+ */
+using VibrationDevice = InputDevice<u8>;
+
+/**
+ * A motion status is an object that returns a tuple of accelerometer state vector,
+ * gyroscope state vector, rotation state vector and orientation state matrix.
*
* For both vectors:
- * x+ is the same direction as LEFT on D-pad.
+ * x+ is the same direction as RIGHT on D-pad.
* y+ is normal to the touch screen, pointing outward.
* z+ is the same direction as UP on D-pad.
*
@@ -133,8 +145,22 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
* For gyroscope state vector:
* Orientation is determined by right-hand rule.
* Units: deg/sec
+ *
+ * For rotation state vector
+ * Units: rotations
+ *
+ * For orientation state matrix
+ * x vector
+ * y vector
+ * z vector
+ */
+using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
+ std::array<Common::Vec3f, 3>>;
+
+/**
+ * A motion device is an input device that returns a motion status object
*/
-using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>;
+using MotionDevice = InputDevice<MotionStatus>;
/**
* 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/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 6d15aeed9..97ee65464 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -35,11 +35,10 @@
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@@ -292,11 +291,11 @@ static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr)
*/
static u8 HexCharToValue(u8 hex) {
if (hex >= '0' && hex <= '9') {
- return hex - '0';
+ return static_cast<u8>(hex - '0');
} else if (hex >= 'a' && hex <= 'f') {
- return hex - 'a' + 0xA;
+ return static_cast<u8>(hex - 'a' + 0xA);
} else if (hex >= 'A' && hex <= 'F') {
- return hex - 'A' + 0xA;
+ return static_cast<u8>(hex - 'A' + 0xA);
}
LOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex);
@@ -311,9 +310,9 @@ static u8 HexCharToValue(u8 hex) {
static u8 NibbleToHex(u8 n) {
n &= 0xF;
if (n < 0xA) {
- return '0' + n;
+ return static_cast<u8>('0' + n);
} else {
- return 'a' + n - 0xA;
+ return static_cast<u8>('a' + n - 0xA);
}
}
@@ -356,8 +355,8 @@ static u64 HexToLong(const u8* src, std::size_t len) {
*/
static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) {
while (len-- > 0) {
- u8 tmp = *src++;
- *dest++ = NibbleToHex(tmp >> 4);
+ const u8 tmp = *src++;
+ *dest++ = NibbleToHex(static_cast<u8>(tmp >> 4));
*dest++ = NibbleToHex(tmp);
}
}
@@ -371,7 +370,7 @@ static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) {
*/
static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) {
while (len-- > 0) {
- *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]);
+ *dest++ = static_cast<u8>((HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]));
src += 2;
}
}
@@ -603,22 +602,22 @@ static void SendReply(const char* reply) {
memcpy(command_buffer + 1, reply, command_length);
- u8 checksum = CalculateChecksum(command_buffer, command_length + 1);
+ const u8 checksum = CalculateChecksum(command_buffer, command_length + 1);
command_buffer[0] = GDB_STUB_START;
command_buffer[command_length + 1] = GDB_STUB_END;
- command_buffer[command_length + 2] = NibbleToHex(checksum >> 4);
+ command_buffer[command_length + 2] = NibbleToHex(static_cast<u8>(checksum >> 4));
command_buffer[command_length + 3] = NibbleToHex(checksum);
u8* ptr = command_buffer;
u32 left = command_length + 4;
while (left > 0) {
- int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0);
+ const auto sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0);
if (sent_size < 0) {
LOG_ERROR(Debug_GDBStub, "gdb: send failed");
return Shutdown();
}
- left -= sent_size;
+ left -= static_cast<u32>(sent_size);
ptr += sent_size;
}
}
@@ -643,7 +642,7 @@ static void HandleQuery() {
SendReply(target_xml);
} else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
const VAddr base_address =
- Core::System::GetInstance().CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
+ Core::System::GetInstance().CurrentProcess()->PageTable().GetCodeRegionStart();
std::string buffer = fmt::format("TextSeg={:0x}", base_address);
SendReply(buffer.c_str());
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
@@ -778,10 +777,10 @@ static void ReadCommand() {
command_buffer[command_length++] = c;
}
- u8 checksum_received = HexCharToValue(ReadByte()) << 4;
- checksum_received |= HexCharToValue(ReadByte());
+ auto checksum_received = static_cast<u32>(HexCharToValue(ReadByte()) << 4);
+ checksum_received |= static_cast<u32>(HexCharToValue(ReadByte()));
- u8 checksum_calculated = CalculateChecksum(command_buffer, command_length);
+ const u32 checksum_calculated = CalculateChecksum(command_buffer, command_length);
if (checksum_received != checksum_calculated) {
LOG_ERROR(Debug_GDBStub,
@@ -1389,10 +1388,9 @@ void SendTrap(Kernel::Thread* thread, int trap) {
return;
}
- if (!halt_loop || current_thread == thread) {
- current_thread = thread;
- SendSignal(thread, trap);
- }
+ current_thread = thread;
+ SendSignal(thread, trap);
+
halt_loop = true;
send_trap = false;
}
diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp
index c629d9fa1..645f26e91 100644
--- a/src/core/hardware_interrupt_manager.cpp
+++ b/src/core/hardware_interrupt_manager.cpp
@@ -11,19 +11,20 @@
namespace Core::Hardware {
InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
- gpu_interrupt_event = Core::Timing::CreateEvent("GPUInterrupt", [this](u64 message, s64) {
- auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
- const u32 syncpt = static_cast<u32>(message >> 32);
- const u32 value = static_cast<u32>(message);
- nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
- });
+ gpu_interrupt_event = Core::Timing::CreateEvent(
+ "GPUInterrupt", [this](std::uintptr_t message, std::chrono::nanoseconds) {
+ auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
+ const u32 syncpt = static_cast<u32>(message >> 32);
+ const u32 value = static_cast<u32>(message);
+ nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
+ });
}
InterruptManager::~InterruptManager() = default;
void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
- system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg);
+ system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
}
} // namespace Core::Hardware
diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h
index b04e046ed..456b41e1b 100644
--- a/src/core/hardware_properties.h
+++ b/src/core/hardware_properties.h
@@ -42,6 +42,10 @@ struct EmuThreadHandle {
constexpr u32 invalid_handle = 0xFFFFFFFF;
return {invalid_handle, invalid_handle};
}
+
+ bool IsInvalid() const {
+ return (*this) == InvalidHandle();
+ }
};
} // namespace Core
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 0dc6a4a43..d57776ce9 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -12,7 +12,6 @@
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
-#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@@ -38,10 +37,11 @@ public:
explicit RequestHelperBase(Kernel::HLERequestContext& context)
: context(&context), cmdbuf(context.CommandBuffer()) {}
- void Skip(unsigned size_in_words, bool set_to_null) {
- if (set_to_null)
+ void Skip(u32 size_in_words, bool set_to_null) {
+ if (set_to_null) {
memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
- index += size_in_words;
+ }
+ index += static_cast<ptrdiff_t>(size_in_words);
}
/**
@@ -49,15 +49,15 @@ public:
*/
void AlignWithPadding() {
if (index & 3) {
- Skip(4 - (index & 3), true);
+ Skip(static_cast<u32>(4 - (index & 3)), true);
}
}
- unsigned GetCurrentOffset() const {
- return static_cast<unsigned>(index);
+ u32 GetCurrentOffset() const {
+ return static_cast<u32>(index);
}
- void SetCurrentOffset(unsigned offset) {
+ void SetCurrentOffset(u32 offset) {
index = static_cast<ptrdiff_t>(offset);
}
};
@@ -72,14 +72,12 @@ public:
AlwaysMoveHandles = 1,
};
- explicit ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {}
-
explicit ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size,
u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0,
Flags flags = Flags::None)
-
: RequestHelperBase(context), normal_params_size(normal_params_size),
- num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) {
+ num_handles_to_copy(num_handles_to_copy),
+ num_objects_to_move(num_objects_to_move), kernel{context.kernel} {
memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
@@ -89,7 +87,7 @@ public:
// The entire size of the raw data section in u32 units, including the 16 bytes of mandatory
// padding.
- u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
+ u64 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
u32 num_handles_to_move{};
u32 num_domain_objects{};
@@ -105,7 +103,7 @@ public:
raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects;
}
- header.data_size.Assign(raw_data_size);
+ header.data_size.Assign(static_cast<u32>(raw_data_size));
if (num_handles_to_copy || num_handles_to_move) {
header.enable_handle_descriptor.Assign(1);
}
@@ -139,7 +137,6 @@ public:
if (context->Session()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
- auto& kernel = Core::System::GetInstance().Kernel();
auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName());
context->AddMoveObject(std::move(client));
iface->ClientConnected(std::move(server));
@@ -213,6 +210,7 @@ private:
u32 num_handles_to_copy{};
u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent
std::ptrdiff_t datapayload_index{};
+ Kernel::KernelCore& kernel;
};
/// Push ///
@@ -229,6 +227,8 @@ inline void ResponseBuilder::Push(u32 value) {
template <typename T>
void ResponseBuilder::PushRaw(const T& value) {
+ static_assert(std::is_trivially_copyable_v<T>,
+ "It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(cmdbuf + index, &value, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
@@ -384,6 +384,8 @@ inline s32 RequestParser::Pop() {
template <typename T>
void RequestParser::PopRaw(T& value) {
+ static_assert(std::is_trivially_copyable_v<T>,
+ "It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, cmdbuf + index, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 8475b698c..b882eaa0f 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -7,11 +7,15 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -29,12 +33,10 @@ void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& wai
// Signal the waiting threads.
for (std::size_t i = 0; i < last; i++) {
- ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
- waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ waiting_threads[i]->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
RemoveThread(waiting_threads[i]);
- waiting_threads[i]->SetArbiterWaitAddress(0);
+ waiting_threads[i]->WaitForArbitration(false);
waiting_threads[i]->ResumeFromWait();
- system.PrepareReschedule(waiting_threads[i]->GetProcessorID());
}
}
@@ -56,6 +58,7 @@ ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 v
}
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
+ SchedulerLock lock(system.Kernel());
const std::vector<std::shared_ptr<Thread>> waiting_threads =
GetThreadsWaitingOnAddress(address);
WakeThreads(waiting_threads, num_to_wake);
@@ -64,6 +67,7 @@ ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) {
+ SchedulerLock lock(system.Kernel());
auto& memory = system.Memory();
// Ensure that we can write to the address.
@@ -71,16 +75,24 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
return ERR_INVALID_ADDRESS_STATE;
}
- if (static_cast<s32>(memory.Read32(address)) != value) {
- return ERR_INVALID_STATE;
- }
+ const std::size_t current_core = system.CurrentCoreIndex();
+ auto& monitor = system.Monitor();
+ u32 current_value;
+ do {
+ current_value = monitor.ExclusiveRead32(current_core, address);
+
+ if (current_value != static_cast<u32>(value)) {
+ return ERR_INVALID_STATE;
+ }
+ current_value++;
+ } while (!monitor.ExclusiveWrite32(current_core, address, current_value));
- memory.Write32(address, static_cast<u32>(value + 1));
return SignalToAddressOnly(address, num_to_wake);
}
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) {
+ SchedulerLock lock(system.Kernel());
auto& memory = system.Memory();
// Ensure that we can write to the address.
@@ -92,29 +104,33 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
const std::vector<std::shared_ptr<Thread>> waiting_threads =
GetThreadsWaitingOnAddress(address);
- // Determine the modified value depending on the waiting count.
+ const std::size_t current_core = system.CurrentCoreIndex();
+ auto& monitor = system.Monitor();
s32 updated_value;
- if (num_to_wake <= 0) {
- if (waiting_threads.empty()) {
- updated_value = value + 1;
- } else {
- updated_value = value - 1;
+ do {
+ updated_value = monitor.ExclusiveRead32(current_core, address);
+
+ if (updated_value != value) {
+ return ERR_INVALID_STATE;
}
- } else {
- if (waiting_threads.empty()) {
- updated_value = value + 1;
- } else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
- updated_value = value - 1;
+ // Determine the modified value depending on the waiting count.
+ if (num_to_wake <= 0) {
+ if (waiting_threads.empty()) {
+ updated_value = value + 1;
+ } else {
+ updated_value = value - 1;
+ }
} else {
- updated_value = value;
+ if (waiting_threads.empty()) {
+ updated_value = value + 1;
+ } else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
+ updated_value = value - 1;
+ } else {
+ updated_value = value;
+ }
}
- }
+ } while (!monitor.ExclusiveWrite32(current_core, address, updated_value));
- if (static_cast<s32>(memory.Read32(address)) != value) {
- return ERR_INVALID_STATE;
- }
-
- memory.Write32(address, static_cast<u32>(updated_value));
WakeThreads(waiting_threads, num_to_wake);
return RESULT_SUCCESS;
}
@@ -136,60 +152,127 @@ ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s
ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
bool should_decrement) {
auto& memory = system.Memory();
+ auto& kernel = system.Kernel();
+ Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
- // Ensure that we can read the address.
- if (!memory.IsValidVirtualAddress(address)) {
- return ERR_INVALID_ADDRESS_STATE;
- }
+ Handle event_handle = InvalidHandle;
+ {
+ SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
+
+ if (current_thread->IsPendingTermination()) {
+ lock.CancelSleep();
+ return ERR_THREAD_TERMINATING;
+ }
+
+ // Ensure that we can read the address.
+ if (!memory.IsValidVirtualAddress(address)) {
+ lock.CancelSleep();
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ s32 current_value = static_cast<s32>(memory.Read32(address));
+ if (current_value >= value) {
+ lock.CancelSleep();
+ return ERR_INVALID_STATE;
+ }
+
+ current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
+
+ s32 decrement_value;
+
+ const std::size_t current_core = system.CurrentCoreIndex();
+ auto& monitor = system.Monitor();
+ do {
+ current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address));
+ if (should_decrement) {
+ decrement_value = current_value - 1;
+ } else {
+ decrement_value = current_value;
+ }
+ } while (
+ !monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value)));
+
+ // Short-circuit without rescheduling, if timeout is zero.
+ if (timeout == 0) {
+ lock.CancelSleep();
+ return RESULT_TIMEOUT;
+ }
- const s32 cur_value = static_cast<s32>(memory.Read32(address));
- if (cur_value >= value) {
- return ERR_INVALID_STATE;
+ current_thread->SetArbiterWaitAddress(address);
+ InsertThread(SharedFrom(current_thread));
+ current_thread->SetStatus(ThreadStatus::WaitArb);
+ current_thread->WaitForArbitration(true);
}
- if (should_decrement) {
- memory.Write32(address, static_cast<u32>(cur_value - 1));
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = kernel.TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
}
- // Short-circuit without rescheduling, if timeout is zero.
- if (timeout == 0) {
- return RESULT_TIMEOUT;
+ {
+ SchedulerLock lock(kernel);
+ if (current_thread->IsWaitingForArbitration()) {
+ RemoveThread(SharedFrom(current_thread));
+ current_thread->WaitForArbitration(false);
+ }
}
- return WaitForAddressImpl(address, timeout);
+ return current_thread->GetSignalingResult();
}
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
auto& memory = system.Memory();
+ auto& kernel = system.Kernel();
+ Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
- // Ensure that we can read the address.
- if (!memory.IsValidVirtualAddress(address)) {
- return ERR_INVALID_ADDRESS_STATE;
- }
+ Handle event_handle = InvalidHandle;
+ {
+ SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
+
+ if (current_thread->IsPendingTermination()) {
+ lock.CancelSleep();
+ return ERR_THREAD_TERMINATING;
+ }
+
+ // Ensure that we can read the address.
+ if (!memory.IsValidVirtualAddress(address)) {
+ lock.CancelSleep();
+ return ERR_INVALID_ADDRESS_STATE;
+ }
- // Only wait for the address if equal.
- if (static_cast<s32>(memory.Read32(address)) != value) {
- return ERR_INVALID_STATE;
+ s32 current_value = static_cast<s32>(memory.Read32(address));
+ if (current_value != value) {
+ lock.CancelSleep();
+ return ERR_INVALID_STATE;
+ }
+
+ // Short-circuit without rescheduling, if timeout is zero.
+ if (timeout == 0) {
+ lock.CancelSleep();
+ return RESULT_TIMEOUT;
+ }
+
+ current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
+ current_thread->SetArbiterWaitAddress(address);
+ InsertThread(SharedFrom(current_thread));
+ current_thread->SetStatus(ThreadStatus::WaitArb);
+ current_thread->WaitForArbitration(true);
}
- // Short-circuit without rescheduling if timeout is zero.
- if (timeout == 0) {
- return RESULT_TIMEOUT;
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = kernel.TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
}
- return WaitForAddressImpl(address, timeout);
-}
+ {
+ SchedulerLock lock(kernel);
+ if (current_thread->IsWaitingForArbitration()) {
+ RemoveThread(SharedFrom(current_thread));
+ current_thread->WaitForArbitration(false);
+ }
+ }
-ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
- Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
- current_thread->SetArbiterWaitAddress(address);
- InsertThread(SharedFrom(current_thread));
- current_thread->SetStatus(ThreadStatus::WaitArb);
- current_thread->InvalidateWakeupCallback();
- current_thread->WakeAfterDelay(timeout);
-
- system.PrepareReschedule(current_thread->GetProcessorID());
- return RESULT_TIMEOUT;
+ return current_thread->GetSignalingResult();
}
void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) {
@@ -221,9 +304,9 @@ void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) {
const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(),
[&thread](const auto& entry) { return thread == entry; });
- ASSERT(iter != thread_list.cend());
-
- thread_list.erase(iter);
+ if (iter != thread_list.cend()) {
+ thread_list.erase(iter);
+ }
}
std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
index f958eee5a..0b05d533c 100644
--- a/src/core/hle/kernel/address_arbiter.h
+++ b/src/core/hle/kernel/address_arbiter.h
@@ -73,9 +73,6 @@ private:
/// Waits on an address if the value passed is equal to the argument value.
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
- // Waits on the given address with a timeout in nanoseconds
- ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
-
/// Wake up num_to_wake (or all) threads in a vector.
void WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, s32 num_to_wake);
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 5498fd313..8aff2227a 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -34,7 +34,7 @@ ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
}
// Wake the threads waiting on the ServerPort
- server_port->WakeupAllWaitingThreads();
+ server_port->Signal();
return MakeResult(std::move(client));
}
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 6d66276bc..be9eba519 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -47,14 +47,16 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern
return MakeResult(std::move(client_session));
}
-ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
+ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread,
+ Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
// Keep ServerSession alive until we're done working with it.
if (!parent->Server()) {
return ERR_SESSION_CLOSED_BY_REMOTE;
}
// Signal the server session that new data is available
- return parent->Server()->HandleSyncRequest(std::move(thread), memory);
+ return parent->Server()->HandleSyncRequest(std::move(thread), memory, core_timing);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index d15b09554..e5e0690c2 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -12,10 +12,14 @@
union ResultCode;
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
+namespace Core::Timing {
+class CoreTiming;
+}
+
namespace Kernel {
class KernelCore;
@@ -42,7 +46,8 @@ public:
return HANDLE_TYPE;
}
- ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
+ ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
bool ShouldWait(const Thread* thread) const override;
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index 8097b3863..d4e5d88cf 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -12,8 +12,10 @@ namespace Kernel {
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
+constexpr ResultCode ERR_THREAD_TERMINATING{ErrorModule::Kernel, 59};
constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
+constexpr ResultCode ERR_OUT_OF_RESOURCES{ErrorModule::Kernel, 103};
constexpr ResultCode ERR_OUT_OF_MEMORY{ErrorModule::Kernel, 104};
constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106};
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index e441a27fc..3e745c18b 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -8,7 +8,9 @@
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -22,7 +24,7 @@ constexpr u16 GetGeneration(Handle handle) {
}
} // Anonymous namespace
-HandleTable::HandleTable() {
+HandleTable::HandleTable(KernelCore& kernel) : kernel{kernel} {
Clear();
}
@@ -30,6 +32,7 @@ HandleTable::~HandleTable() = default;
ResultCode HandleTable::SetSize(s32 handle_table_size) {
if (static_cast<u32>(handle_table_size) > MAX_COUNT) {
+ LOG_ERROR(Kernel, "Handle table size {} is greater than {}", handle_table_size, MAX_COUNT);
return ERR_OUT_OF_MEMORY;
}
@@ -80,6 +83,7 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
ResultCode HandleTable::Close(Handle handle) {
if (!IsValid(handle)) {
+ LOG_ERROR(Kernel, "Handle is not valid! handle={:08X}", handle);
return ERR_INVALID_HANDLE;
}
@@ -101,9 +105,9 @@ bool HandleTable::IsValid(Handle handle) const {
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
- return SharedFrom(GetCurrentThread());
+ return SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
} else if (handle == CurrentProcess) {
- return SharedFrom(Core::System::GetInstance().CurrentProcess());
+ return SharedFrom(kernel.CurrentProcess());
}
if (!IsValid(handle)) {
@@ -114,7 +118,7 @@ std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
void HandleTable::Clear() {
for (u16 i = 0; i < table_size; ++i) {
- generations[i] = i + 1;
+ generations[i] = static_cast<u16>(i + 1);
objects[i] = nullptr;
}
next_free_slot = 0;
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index 8029660ed..c9dab8cdd 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -14,6 +14,8 @@
namespace Kernel {
+class KernelCore;
+
enum KernelHandle : Handle {
InvalidHandle = 0,
CurrentThread = 0xFFFF8000,
@@ -48,7 +50,7 @@ public:
/// This is the maximum limit of handles allowed per process in Horizon
static constexpr std::size_t MAX_COUNT = 1024;
- HandleTable();
+ explicit HandleTable(KernelCore& kernel);
~HandleTable();
/**
@@ -134,6 +136,9 @@ private:
/// Head of the free slots linked list.
u16 next_free_slot = 0;
+
+ /// Underlying kernel instance that this handle table operates under.
+ KernelCore& kernel;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index c558a2f33..81f85643b 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -13,16 +13,18 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/time_manager.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
@@ -47,31 +49,33 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
const std::string& reason, u64 timeout, WakeupCallback&& callback,
std::shared_ptr<WritableEvent> writable_event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
- thread->SetWakeupCallback(
- [context = *this, callback](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
- std::shared_ptr<SynchronizationObject> object,
- std::size_t index) mutable -> bool {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
- callback(thread, context, reason);
- context.WriteToOutgoingCommandBuffer(*thread);
- return true;
- });
-
- auto& kernel = Core::System::GetInstance().Kernel();
+
if (!writable_event) {
// Create event if not provided
const auto pair = WritableEvent::CreateEventPair(kernel, "HLE Pause Event: " + reason);
writable_event = pair.writable;
}
- const auto readable_event{writable_event->GetReadableEvent()};
- writable_event->Clear();
- thread->SetStatus(ThreadStatus::WaitHLEEvent);
- thread->SetSynchronizationObjects({readable_event});
- readable_event->AddWaitingThread(thread);
-
- if (timeout > 0) {
- thread->WakeAfterDelay(timeout);
+ {
+ Handle event_handle = InvalidHandle;
+ SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
+ thread->SetHLECallback(
+ [context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
+ ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
+ ? ThreadWakeupReason::Timeout
+ : ThreadWakeupReason::Signal;
+ callback(thread, context, reason);
+ context.WriteToOutgoingCommandBuffer(*thread);
+ return true;
+ });
+ const auto readable_event{writable_event->GetReadableEvent()};
+ writable_event->Clear();
+ thread->SetHLESyncObject(readable_event.get());
+ thread->SetStatus(ThreadStatus::WaitHLEEvent);
+ thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
+ readable_event->AddWaitingThread(thread);
+ lock.Release();
+ thread->SetHLETimeEvent(event_handle);
}
is_thread_waiting = true;
@@ -79,9 +83,11 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
return writable_event;
}
-HLERequestContext::HLERequestContext(std::shared_ptr<Kernel::ServerSession> server_session,
+HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory,
+ std::shared_ptr<ServerSession> server_session,
std::shared_ptr<Thread> thread)
- : server_session(std::move(server_session)), thread(std::move(thread)) {
+ : server_session(std::move(server_session)),
+ thread(std::move(thread)), kernel{kernel}, memory{memory} {
cmd_buf[0] = 0;
}
@@ -216,7 +222,6 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const HandleTabl
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
auto& owner_process = *thread.GetOwnerProcess();
auto& handle_table = owner_process.GetHandleTable();
- auto& memory = Core::System::GetInstance().Memory();
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
memory.ReadBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
@@ -282,20 +287,21 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
return RESULT_SUCCESS;
}
-std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {
- std::vector<u8> buffer;
+std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
+ std::vector<u8> buffer{};
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()};
- auto& memory = Core::System::GetInstance().Memory();
if (is_buffer_a) {
- ASSERT_MSG(BufferDescriptorA().size() > buffer_index,
- "BufferDescriptorA invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorA().size() > buffer_index, { return buffer; },
+ "BufferDescriptorA invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorA()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
} else {
- ASSERT_MSG(BufferDescriptorX().size() > buffer_index,
- "BufferDescriptorX invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorX().size() > buffer_index, { return buffer; },
+ "BufferDescriptorX invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorX()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
}
@@ -304,7 +310,7 @@ std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {
}
std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
- int buffer_index) const {
+ std::size_t buffer_index) const {
if (size == 0) {
LOG_WARNING(Core, "skip empty buffer write");
return 0;
@@ -319,54 +325,54 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
- auto& memory = Core::System::GetInstance().Memory();
if (is_buffer_b) {
- ASSERT_MSG(BufferDescriptorB().size() > buffer_index,
- "BufferDescriptorB invalid buffer_index {}", buffer_index);
- ASSERT_MSG(BufferDescriptorB()[buffer_index].Size() >= size,
- "BufferDescriptorB buffer_index {} is not large enough", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorB().size() > buffer_index &&
+ BufferDescriptorB()[buffer_index].Size() >= size,
+ { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size);
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
} else {
- ASSERT_MSG(BufferDescriptorC().size() > buffer_index,
- "BufferDescriptorC invalid buffer_index {}", buffer_index);
- ASSERT_MSG(BufferDescriptorC()[buffer_index].Size() >= size,
- "BufferDescriptorC buffer_index {} is not large enough", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorC().size() > buffer_index &&
+ BufferDescriptorC()[buffer_index].Size() >= size,
+ { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size);
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
}
return size;
}
-std::size_t HLERequestContext::GetReadBufferSize(int buffer_index) const {
+std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const {
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) {
- ASSERT_MSG(BufferDescriptorA().size() > buffer_index,
- "BufferDescriptorA invalid buffer_index {}", buffer_index);
- ASSERT_MSG(BufferDescriptorA()[buffer_index].Size() > 0,
- "BufferDescriptorA buffer_index {} is empty", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorA().size() > buffer_index, { return 0; },
+ "BufferDescriptorA invalid buffer_index {}", buffer_index);
return BufferDescriptorA()[buffer_index].Size();
} else {
- ASSERT_MSG(BufferDescriptorX().size() > buffer_index,
- "BufferDescriptorX invalid buffer_index {}", buffer_index);
- ASSERT_MSG(BufferDescriptorX()[buffer_index].Size() > 0,
- "BufferDescriptorX buffer_index {} is empty", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorX().size() > buffer_index, { return 0; },
+ "BufferDescriptorX invalid buffer_index {}", buffer_index);
return BufferDescriptorX()[buffer_index].Size();
}
}
-std::size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const {
+std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) const {
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size()};
if (is_buffer_b) {
- ASSERT_MSG(BufferDescriptorB().size() > buffer_index,
- "BufferDescriptorB invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorB().size() > buffer_index, { return 0; },
+ "BufferDescriptorB invalid buffer_index {}", buffer_index);
return BufferDescriptorB()[buffer_index].Size();
} else {
- ASSERT_MSG(BufferDescriptorC().size() > buffer_index,
- "BufferDescriptorC invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorC().size() > buffer_index, { return 0; },
+ "BufferDescriptorC invalid buffer_index {}", buffer_index);
return BufferDescriptorC()[buffer_index].Size();
}
+ return 0;
}
std::string HLERequestContext::Description() const {
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 050ad8fd7..c31a65476 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -13,12 +13,21 @@
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
+#include "common/concepts.h"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
union ResultCode;
+namespace Core::Memory {
+class Memory;
+}
+
+namespace IPC {
+class ResponseBuilder;
+}
+
namespace Service {
class ServiceFrameworkBase;
}
@@ -28,6 +37,7 @@ namespace Kernel {
class Domain;
class HandleTable;
class HLERequestContext;
+class KernelCore;
class Process;
class ServerSession;
class Thread;
@@ -98,7 +108,8 @@ protected:
*/
class HLERequestContext {
public:
- explicit HLERequestContext(std::shared_ptr<ServerSession> session,
+ explicit HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory,
+ std::shared_ptr<ServerSession> session,
std::shared_ptr<Thread> thread);
~HLERequestContext();
@@ -179,36 +190,39 @@ public:
}
/// Helper function to read a buffer using the appropriate buffer descriptor
- std::vector<u8> ReadBuffer(int buffer_index = 0) const;
+ std::vector<u8> ReadBuffer(std::size_t buffer_index = 0) const;
/// Helper function to write a buffer using the appropriate buffer descriptor
- std::size_t WriteBuffer(const void* buffer, std::size_t size, int buffer_index = 0) const;
+ std::size_t WriteBuffer(const void* buffer, std::size_t size,
+ std::size_t buffer_index = 0) const;
/* Helper function to write a buffer using the appropriate buffer descriptor
*
- * @tparam ContiguousContainer an arbitrary container that satisfies the
- * ContiguousContainer concept in the C++ standard library.
+ * @tparam T an arbitrary container that satisfies the
+ * ContiguousContainer concept in the C++ standard library or a trivially copyable type.
*
- * @param container The container to write the data of into a buffer.
+ * @param data The container/data to write into a buffer.
* @param buffer_index The buffer in particular to write to.
*/
- template <typename ContiguousContainer,
- typename = std::enable_if_t<!std::is_pointer_v<ContiguousContainer>>>
- std::size_t WriteBuffer(const ContiguousContainer& container, int buffer_index = 0) const {
- using ContiguousType = typename ContiguousContainer::value_type;
-
- static_assert(std::is_trivially_copyable_v<ContiguousType>,
- "Container to WriteBuffer must contain trivially copyable objects");
-
- return WriteBuffer(std::data(container), std::size(container) * sizeof(ContiguousType),
- buffer_index);
+ template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
+ std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const {
+ if constexpr (Common::IsSTLContainer<T>) {
+ using ContiguousType = typename T::value_type;
+ static_assert(std::is_trivially_copyable_v<ContiguousType>,
+ "Container to WriteBuffer must contain trivially copyable objects");
+ return WriteBuffer(std::data(data), std::size(data) * sizeof(ContiguousType),
+ buffer_index);
+ } else {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
+ return WriteBuffer(&data, sizeof(T), buffer_index);
+ }
}
/// Helper function to get the size of the input buffer
- std::size_t GetReadBufferSize(int buffer_index = 0) const;
+ std::size_t GetReadBufferSize(std::size_t buffer_index = 0) const;
/// Helper function to get the size of the output buffer
- std::size_t GetWriteBufferSize(int buffer_index = 0) const;
+ std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const;
template <typename T>
std::shared_ptr<T> GetCopyObject(std::size_t index) {
@@ -277,6 +291,8 @@ public:
}
private:
+ friend class IPC::ResponseBuilder;
+
void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming);
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
@@ -303,6 +319,9 @@ private:
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
bool is_thread_waiting{};
+
+ KernelCore& kernel;
+ Core::Memory::Memory& memory;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 014d647cf..bb3e312a7 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -2,31 +2,40 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <atomic>
#include <bitset>
#include <functional>
#include <memory>
-#include <mutex>
#include <thread>
#include <unordered_map>
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/arm/arm_interface.h"
+#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
+#include "core/cpu_manager.h"
+#include "core/device_memory.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/memory_layout.h"
+#include "core/hle/kernel/memory/memory_manager.h"
+#include "core/hle/kernel/memory/slab_heap.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
@@ -34,84 +43,29 @@
#include "core/hle/result.h"
#include "core/memory.h"
-namespace Kernel {
-
-/**
- * Callback that will wake up the thread it was scheduled for
- * @param thread_handle The handle of the thread that's been awoken
- * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
- */
-static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
- const auto proper_handle = static_cast<Handle>(thread_handle);
- const auto& system = Core::System::GetInstance();
-
- // Lock the global kernel mutex when we enter the kernel HLE.
- std::lock_guard lock{HLE::g_hle_lock};
-
- std::shared_ptr<Thread> thread =
- system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
- if (thread == nullptr) {
- LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
- return;
- }
-
- bool resume = true;
-
- if (thread->GetStatus() == ThreadStatus::WaitSynch ||
- thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
- // Remove the thread from each of its waiting objects' waitlists
- for (const auto& object : thread->GetSynchronizationObjects()) {
- object->RemoveWaitingThread(thread);
- }
- thread->ClearSynchronizationObjects();
-
- // Invoke the wakeup callback before clearing the wait objects
- if (thread->HasWakeupCallback()) {
- resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
- }
- } else if (thread->GetStatus() == ThreadStatus::WaitMutex ||
- thread->GetStatus() == ThreadStatus::WaitCondVar) {
- thread->SetMutexWaitAddress(0);
- thread->SetWaitHandle(0);
- if (thread->GetStatus() == ThreadStatus::WaitCondVar) {
- thread->GetOwnerProcess()->RemoveConditionVariableThread(thread);
- thread->SetCondVarWaitAddress(0);
- }
-
- auto* const lock_owner = thread->GetLockOwner();
- // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
- // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
- // wasn't awakened due to the mutex already being acquired.
- if (lock_owner != nullptr) {
- lock_owner->RemoveMutexWaiter(thread);
- }
- }
-
- if (thread->GetStatus() == ThreadStatus::WaitArb) {
- auto& address_arbiter = thread->GetOwnerProcess()->GetAddressArbiter();
- address_arbiter.HandleWakeupThread(thread);
- }
+MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
- if (resume) {
- if (thread->GetStatus() == ThreadStatus::WaitCondVar ||
- thread->GetStatus() == ThreadStatus::WaitArb) {
- thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
- }
- thread->ResumeFromWait();
- }
-}
+namespace Kernel {
struct KernelCore::Impl {
explicit Impl(Core::System& system, KernelCore& kernel)
- : global_scheduler{kernel}, synchronization{system}, time_manager{system}, system{system} {}
+ : global_scheduler{kernel}, synchronization{system}, time_manager{system},
+ global_handle_table{kernel}, system{system} {}
+
+ void SetMulticore(bool is_multicore) {
+ this->is_multicore = is_multicore;
+ }
void Initialize(KernelCore& kernel) {
Shutdown();
+ RegisterHostThread();
InitializePhysicalCores();
InitializeSystemResourceLimit(kernel);
- InitializeThreads();
- InitializePreemption();
+ InitializeMemoryLayout();
+ InitializePreemption(kernel);
+ InitializeSchedulers();
+ InitializeSuspendThreads();
}
void Shutdown() {
@@ -120,13 +74,24 @@ struct KernelCore::Impl {
next_user_process_id = Process::ProcessIDMin;
next_thread_id = 1;
+ for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ if (suspend_threads[i]) {
+ suspend_threads[i].reset();
+ }
+ }
+
+ for (std::size_t i = 0; i < cores.size(); i++) {
+ cores[i].Shutdown();
+ schedulers[i].reset();
+ }
+ cores.clear();
+
process_list.clear();
current_process = nullptr;
system_resource_limit = nullptr;
global_handle_table.Clear();
- thread_wakeup_event_type = nullptr;
preemption_event = nullptr;
global_scheduler.Shutdown();
@@ -139,13 +104,25 @@ struct KernelCore::Impl {
cores.clear();
exclusive_monitor.reset();
+
+ num_host_threads = 0;
+ std::fill(register_host_thread_keys.begin(), register_host_thread_keys.end(),
+ std::thread::id{});
+ std::fill(register_host_thread_values.begin(), register_host_thread_values.end(), 0);
}
void InitializePhysicalCores() {
exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- cores.emplace_back(system, i, *exclusive_monitor);
+ schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i);
+ cores.emplace_back(system, i, *schedulers[i], interrupts[i]);
+ }
+ }
+
+ void InitializeSchedulers() {
+ for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ cores[i].Scheduler().Initialize();
}
}
@@ -154,71 +131,105 @@ struct KernelCore::Impl {
system_resource_limit = ResourceLimit::Create(kernel);
// If setting the default system values fails, then something seriously wrong has occurred.
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000)
+ ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x100000000)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
- }
- void InitializeThreads() {
- thread_wakeup_event_type =
- Core::Timing::CreateEvent("ThreadWakeupCallback", ThreadWakeupCallback);
+ if (!system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0) ||
+ !system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0x60000)) {
+ UNREACHABLE();
+ }
}
- void InitializePreemption() {
- preemption_event =
- Core::Timing::CreateEvent("PreemptionCallback", [this](u64 userdata, s64 cycles_late) {
- global_scheduler.PreemptThreads();
- s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
+ void InitializePreemption(KernelCore& kernel) {
+ preemption_event = Core::Timing::CreateEvent(
+ "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
+ {
+ SchedulerLock lock(kernel);
+ global_scheduler.PreemptThreads();
+ }
+ const auto time_interval = std::chrono::nanoseconds{
+ Core::Timing::msToCycles(std::chrono::milliseconds(10))};
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
});
- s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
+ const auto time_interval =
+ std::chrono::nanoseconds{Core::Timing::msToCycles(std::chrono::milliseconds(10))};
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
}
+ void InitializeSuspendThreads() {
+ for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ std::string name = "Suspend Thread Id:" + std::to_string(i);
+ std::function<void(void*)> init_func = Core::CpuManager::GetSuspendThreadStartFunc();
+ void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
+ const auto type =
+ static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND);
+ auto thread_res =
+ Thread::Create(system, type, std::move(name), 0, 0, 0, static_cast<u32>(i), 0,
+ nullptr, std::move(init_func), init_func_parameter);
+
+ suspend_threads[i] = std::move(thread_res).Unwrap();
+ }
+ }
+
void MakeCurrentProcess(Process* process) {
current_process = process;
-
if (process == nullptr) {
return;
}
-
- for (auto& core : cores) {
- core.SetIs64Bit(process->Is64BitProcess());
+ const u32 core_id = GetCurrentHostThreadID();
+ if (core_id < Core::Hardware::NUM_CPU_CORES) {
+ system.Memory().SetCurrentPageTable(*process, core_id);
}
-
- system.Memory().SetCurrentPageTable(*process);
}
void RegisterCoreThread(std::size_t core_id) {
- std::unique_lock lock{register_thread_mutex};
const std::thread::id this_id = std::this_thread::get_id();
- const auto it = host_thread_ids.find(this_id);
+ if (!is_multicore) {
+ single_core_thread_id = this_id;
+ }
+ const auto end =
+ register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
+ const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
- ASSERT(it == host_thread_ids.end());
- ASSERT(!registered_core_threads[core_id]);
- host_thread_ids[this_id] = static_cast<u32>(core_id);
- registered_core_threads.set(core_id);
+ ASSERT(it == end);
+ InsertHostThread(static_cast<u32>(core_id));
}
void RegisterHostThread() {
- std::unique_lock lock{register_thread_mutex};
const std::thread::id this_id = std::this_thread::get_id();
- const auto it = host_thread_ids.find(this_id);
- ASSERT(it == host_thread_ids.end());
- host_thread_ids[this_id] = registered_thread_ids++;
+ const auto end =
+ register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
+ const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
+ if (it == end) {
+ InsertHostThread(registered_thread_ids++);
+ }
+ }
+
+ void InsertHostThread(u32 value) {
+ const size_t index = num_host_threads++;
+ ASSERT_MSG(index < NUM_REGISTRABLE_HOST_THREADS, "Too many host threads");
+ register_host_thread_values[index] = value;
+ register_host_thread_keys[index] = std::this_thread::get_id();
}
- u32 GetCurrentHostThreadID() const {
+ [[nodiscard]] u32 GetCurrentHostThreadID() const {
const std::thread::id this_id = std::this_thread::get_id();
- const auto it = host_thread_ids.find(this_id);
- if (it == host_thread_ids.end()) {
+ if (!is_multicore && single_core_thread_id == this_id) {
+ return static_cast<u32>(system.GetCpuManager().CurrentCore());
+ }
+ const auto end =
+ register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
+ const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
+ if (it == end) {
return Core::INVALID_HOST_THREAD_ID;
}
- return it->second;
+ return register_host_thread_values[static_cast<size_t>(
+ std::distance(register_host_thread_keys.begin(), it))];
}
Core::EmuThreadHandle GetCurrentEmuThreadID() const {
@@ -229,7 +240,7 @@ struct KernelCore::Impl {
}
const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler();
const Kernel::Thread* current = sched.GetCurrentThread();
- if (current != nullptr) {
+ if (current != nullptr && !current->IsPhantomMode()) {
result.guest_handle = current->GetGlobalHandle();
} else {
result.guest_handle = InvalidHandle;
@@ -237,6 +248,57 @@ struct KernelCore::Impl {
return result;
}
+ void InitializeMemoryLayout() {
+ // Initialize memory layout
+ constexpr Memory::MemoryLayout layout{Memory::MemoryLayout::GetDefaultLayout()};
+ constexpr std::size_t hid_size{0x40000};
+ constexpr std::size_t font_size{0x1100000};
+ constexpr std::size_t irs_size{0x8000};
+ constexpr std::size_t time_size{0x1000};
+ constexpr PAddr hid_addr{layout.System().StartAddress()};
+ constexpr PAddr font_pa{layout.System().StartAddress() + hid_size};
+ constexpr PAddr irs_addr{layout.System().StartAddress() + hid_size + font_size};
+ constexpr PAddr time_addr{layout.System().StartAddress() + hid_size + font_size + irs_size};
+
+ // Initialize memory manager
+ memory_manager = std::make_unique<Memory::MemoryManager>();
+ memory_manager->InitializeManager(Memory::MemoryManager::Pool::Application,
+ layout.Application().StartAddress(),
+ layout.Application().EndAddress());
+ memory_manager->InitializeManager(Memory::MemoryManager::Pool::Applet,
+ layout.Applet().StartAddress(),
+ layout.Applet().EndAddress());
+ memory_manager->InitializeManager(Memory::MemoryManager::Pool::System,
+ layout.System().StartAddress(),
+ layout.System().EndAddress());
+
+ hid_shared_mem = Kernel::SharedMemory::Create(
+ system.Kernel(), system.DeviceMemory(), nullptr,
+ {hid_addr, hid_size / Memory::PageSize}, Memory::MemoryPermission::None,
+ Memory::MemoryPermission::Read, hid_addr, hid_size, "HID:SharedMemory");
+ font_shared_mem = Kernel::SharedMemory::Create(
+ system.Kernel(), system.DeviceMemory(), nullptr,
+ {font_pa, font_size / Memory::PageSize}, Memory::MemoryPermission::None,
+ Memory::MemoryPermission::Read, font_pa, font_size, "Font:SharedMemory");
+ irs_shared_mem = Kernel::SharedMemory::Create(
+ system.Kernel(), system.DeviceMemory(), nullptr,
+ {irs_addr, irs_size / Memory::PageSize}, Memory::MemoryPermission::None,
+ Memory::MemoryPermission::Read, irs_addr, irs_size, "IRS:SharedMemory");
+ time_shared_mem = Kernel::SharedMemory::Create(
+ system.Kernel(), system.DeviceMemory(), nullptr,
+ {time_addr, time_size / Memory::PageSize}, Memory::MemoryPermission::None,
+ Memory::MemoryPermission::Read, time_addr, time_size, "Time:SharedMemory");
+
+ // Allocate slab heaps
+ user_slab_heap_pages = std::make_unique<Memory::SlabHeap<Memory::Page>>();
+
+ // Initialize slab heaps
+ constexpr u64 user_slab_heap_size{0x3de000};
+ user_slab_heap_pages->Initialize(
+ system.DeviceMemory().GetPointer(Core::DramMemoryMap::SlabHeapBase),
+ user_slab_heap_size);
+ }
+
std::atomic<u32> next_object_id{0};
std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin};
std::atomic<u64> next_user_process_id{Process::ProcessIDMin};
@@ -251,12 +313,11 @@ struct KernelCore::Impl {
std::shared_ptr<ResourceLimit> system_resource_limit;
- std::shared_ptr<Core::Timing::EventType> thread_wakeup_event_type;
std::shared_ptr<Core::Timing::EventType> preemption_event;
// This is the kernel's handle table or supervisor handle table which
// stores all the objects in place.
- Kernel::HandleTable global_handle_table;
+ HandleTable global_handle_table;
/// Map of named ports managed by the kernel, which can be retrieved using
/// the ConnectToPort SVC.
@@ -266,10 +327,33 @@ struct KernelCore::Impl {
std::vector<Kernel::PhysicalCore> cores;
// 0-3 IDs represent core threads, >3 represent others
- std::unordered_map<std::thread::id, u32> host_thread_ids;
- u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES};
- std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads;
- std::mutex register_thread_mutex;
+ std::atomic<u32> registered_thread_ids{Core::Hardware::NUM_CPU_CORES};
+
+ // Number of host threads is a relatively high number to avoid overflowing
+ static constexpr size_t NUM_REGISTRABLE_HOST_THREADS = 64;
+ std::atomic<size_t> num_host_threads{0};
+ std::array<std::atomic<std::thread::id>, NUM_REGISTRABLE_HOST_THREADS>
+ register_host_thread_keys{};
+ std::array<std::atomic<u32>, NUM_REGISTRABLE_HOST_THREADS> register_host_thread_values{};
+
+ // Kernel memory management
+ std::unique_ptr<Memory::MemoryManager> memory_manager;
+ std::unique_ptr<Memory::SlabHeap<Memory::Page>> user_slab_heap_pages;
+
+ // Shared memory for services
+ std::shared_ptr<Kernel::SharedMemory> hid_shared_mem;
+ std::shared_ptr<Kernel::SharedMemory> font_shared_mem;
+ std::shared_ptr<Kernel::SharedMemory> irs_shared_mem;
+ std::shared_ptr<Kernel::SharedMemory> time_shared_mem;
+
+ std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
+ std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
+ std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
+
+ bool is_multicore{};
+ std::thread::id single_core_thread_id{};
+
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
// System context
Core::System& system;
@@ -280,6 +364,10 @@ KernelCore::~KernelCore() {
Shutdown();
}
+void KernelCore::SetMulticore(bool is_multicore) {
+ impl->SetMulticore(is_multicore);
+}
+
void KernelCore::Initialize() {
impl->Initialize(*this);
}
@@ -325,11 +413,11 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
}
Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) {
- return impl->cores[id].Scheduler();
+ return *impl->schedulers[id];
}
const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const {
- return impl->cores[id].Scheduler();
+ return *impl->schedulers[id];
}
Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
@@ -340,6 +428,39 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
return impl->cores[id];
}
+Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
+ u32 core_id = impl->GetCurrentHostThreadID();
+ ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
+ return impl->cores[core_id];
+}
+
+const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
+ u32 core_id = impl->GetCurrentHostThreadID();
+ ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
+ return impl->cores[core_id];
+}
+
+Kernel::Scheduler& KernelCore::CurrentScheduler() {
+ u32 core_id = impl->GetCurrentHostThreadID();
+ ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
+ return *impl->schedulers[core_id];
+}
+
+const Kernel::Scheduler& KernelCore::CurrentScheduler() const {
+ u32 core_id = impl->GetCurrentHostThreadID();
+ ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
+ return *impl->schedulers[core_id];
+}
+
+std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
+ return impl->interrupts;
+}
+
+const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts()
+ const {
+ return impl->interrupts;
+}
+
Kernel::Synchronization& KernelCore::Synchronization() {
return impl->synchronization;
}
@@ -365,15 +486,17 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
}
void KernelCore::InvalidateAllInstructionCaches() {
- for (std::size_t i = 0; i < impl->global_scheduler.CpuCoresCount(); i++) {
- PhysicalCore(i).ArmInterface().ClearInstructionCache();
+ auto& threads = GlobalScheduler().GetThreadList();
+ for (auto& thread : threads) {
+ if (!thread->IsHLEThread()) {
+ auto& arm_interface = thread->ArmInterface();
+ arm_interface.ClearInstructionCache();
+ }
}
}
void KernelCore::PrepareReschedule(std::size_t id) {
- if (id < impl->global_scheduler.CpuCoresCount()) {
- impl->cores[id].Stop();
- }
+ // TODO: Reimplement, this
}
void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
@@ -409,10 +532,6 @@ u64 KernelCore::CreateNewUserProcessID() {
return impl->next_user_process_id++;
}
-const std::shared_ptr<Core::Timing::EventType>& KernelCore::ThreadWakeupCallbackEventType() const {
- return impl->thread_wakeup_event_type;
-}
-
Kernel::HandleTable& KernelCore::GlobalHandleTable() {
return impl->global_handle_table;
}
@@ -437,4 +556,82 @@ Core::EmuThreadHandle KernelCore::GetCurrentEmuThreadID() const {
return impl->GetCurrentEmuThreadID();
}
+Memory::MemoryManager& KernelCore::MemoryManager() {
+ return *impl->memory_manager;
+}
+
+const Memory::MemoryManager& KernelCore::MemoryManager() const {
+ return *impl->memory_manager;
+}
+
+Memory::SlabHeap<Memory::Page>& KernelCore::GetUserSlabHeapPages() {
+ return *impl->user_slab_heap_pages;
+}
+
+const Memory::SlabHeap<Memory::Page>& KernelCore::GetUserSlabHeapPages() const {
+ return *impl->user_slab_heap_pages;
+}
+
+Kernel::SharedMemory& KernelCore::GetHidSharedMem() {
+ return *impl->hid_shared_mem;
+}
+
+const Kernel::SharedMemory& KernelCore::GetHidSharedMem() const {
+ return *impl->hid_shared_mem;
+}
+
+Kernel::SharedMemory& KernelCore::GetFontSharedMem() {
+ return *impl->font_shared_mem;
+}
+
+const Kernel::SharedMemory& KernelCore::GetFontSharedMem() const {
+ return *impl->font_shared_mem;
+}
+
+Kernel::SharedMemory& KernelCore::GetIrsSharedMem() {
+ return *impl->irs_shared_mem;
+}
+
+const Kernel::SharedMemory& KernelCore::GetIrsSharedMem() const {
+ return *impl->irs_shared_mem;
+}
+
+Kernel::SharedMemory& KernelCore::GetTimeSharedMem() {
+ return *impl->time_shared_mem;
+}
+
+const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const {
+ return *impl->time_shared_mem;
+}
+
+void KernelCore::Suspend(bool in_suspention) {
+ const bool should_suspend = exception_exited || in_suspention;
+ {
+ SchedulerLock lock(*this);
+ ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep;
+ for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ impl->suspend_threads[i]->SetStatus(status);
+ }
+ }
+}
+
+bool KernelCore::IsMulticore() const {
+ return impl->is_multicore;
+}
+
+void KernelCore::ExceptionalExit() {
+ exception_exited = true;
+ Suspend(true);
+}
+
+void KernelCore::EnterSVCProfile() {
+ std::size_t core = impl->GetCurrentHostThreadID();
+ impl->svc_ticks[core] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
+}
+
+void KernelCore::ExitSVCProfile() {
+ std::size_t core = impl->GetCurrentHostThreadID();
+ MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index c4f78ab71..16285c3f0 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -4,14 +4,18 @@
#pragma once
+#include <array>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
+#include "core/arm/cpu_interrupt_handler.h"
+#include "core/hardware_properties.h"
+#include "core/hle/kernel/memory/memory_types.h"
#include "core/hle/kernel/object.h"
namespace Core {
-struct EmuThreadHandle;
+class CPUInterruptHandler;
class ExclusiveMonitor;
class System;
} // namespace Core
@@ -23,6 +27,12 @@ struct EventType;
namespace Kernel {
+namespace Memory {
+class MemoryManager;
+template <typename T>
+class SlabHeap;
+} // namespace Memory
+
class AddressArbiter;
class ClientPort;
class GlobalScheduler;
@@ -31,6 +41,7 @@ class PhysicalCore;
class Process;
class ResourceLimit;
class Scheduler;
+class SharedMemory;
class Synchronization;
class Thread;
class TimeManager;
@@ -57,6 +68,9 @@ public:
KernelCore(KernelCore&&) = delete;
KernelCore& operator=(KernelCore&&) = delete;
+ /// Sets if emulation is multicore or single core, must be set before Initialize
+ void SetMulticore(bool is_multicore);
+
/// Resets the kernel to a clean slate for use.
void Initialize();
@@ -102,6 +116,18 @@ public:
/// Gets the an instance of the respective physical CPU core.
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
+ /// Gets the sole instance of the Scheduler at the current running core.
+ Kernel::Scheduler& CurrentScheduler();
+
+ /// Gets the sole instance of the Scheduler at the current running core.
+ const Kernel::Scheduler& CurrentScheduler() const;
+
+ /// Gets the an instance of the current physical CPU core.
+ Kernel::PhysicalCore& CurrentPhysicalCore();
+
+ /// Gets the an instance of the current physical CPU core.
+ const Kernel::PhysicalCore& CurrentPhysicalCore() const;
+
/// Gets the an instance of the Synchronization Interface.
Kernel::Synchronization& Synchronization();
@@ -121,6 +147,10 @@ public:
const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
+ std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts();
+
+ const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
+
void InvalidateAllInstructionCaches();
/// Adds a port to the named port table
@@ -147,6 +177,54 @@ public:
/// Register the current thread as a non CPU core thread.
void RegisterHostThread();
+ /// Gets the virtual memory manager for the kernel.
+ Memory::MemoryManager& MemoryManager();
+
+ /// Gets the virtual memory manager for the kernel.
+ const Memory::MemoryManager& MemoryManager() const;
+
+ /// Gets the slab heap allocated for user space pages.
+ Memory::SlabHeap<Memory::Page>& GetUserSlabHeapPages();
+
+ /// Gets the slab heap allocated for user space pages.
+ const Memory::SlabHeap<Memory::Page>& GetUserSlabHeapPages() const;
+
+ /// Gets the shared memory object for HID services.
+ Kernel::SharedMemory& GetHidSharedMem();
+
+ /// Gets the shared memory object for HID services.
+ const Kernel::SharedMemory& GetHidSharedMem() const;
+
+ /// Gets the shared memory object for font services.
+ Kernel::SharedMemory& GetFontSharedMem();
+
+ /// Gets the shared memory object for font services.
+ const Kernel::SharedMemory& GetFontSharedMem() const;
+
+ /// Gets the shared memory object for IRS services.
+ Kernel::SharedMemory& GetIrsSharedMem();
+
+ /// Gets the shared memory object for IRS services.
+ const Kernel::SharedMemory& GetIrsSharedMem() const;
+
+ /// Gets the shared memory object for Time services.
+ Kernel::SharedMemory& GetTimeSharedMem();
+
+ /// Gets the shared memory object for Time services.
+ const Kernel::SharedMemory& GetTimeSharedMem() const;
+
+ /// Suspend/unsuspend the OS.
+ void Suspend(bool in_suspention);
+
+ /// Exceptional exit the OS.
+ void ExceptionalExit();
+
+ bool IsMulticore() const;
+
+ void EnterSVCProfile();
+
+ void ExitSVCProfile();
+
private:
friend class Object;
friend class Process;
@@ -164,9 +242,6 @@ private:
/// Creates a new thread ID, incrementing the internal thread ID counter.
u64 CreateNewThreadID();
- /// Retrieves the event type used for thread wakeup callbacks.
- const std::shared_ptr<Core::Timing::EventType>& ThreadWakeupCallbackEventType() const;
-
/// Provides a reference to the global handle table.
Kernel::HandleTable& GlobalHandleTable();
@@ -175,6 +250,7 @@ private:
struct Impl;
std::unique_ptr<Impl> impl;
+ bool exception_exited{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/memory/address_space_info.cpp b/src/core/hle/kernel/memory/address_space_info.cpp
new file mode 100644
index 000000000..e4288cab4
--- /dev/null
+++ b/src/core/hle/kernel/memory/address_space_info.cpp
@@ -0,0 +1,117 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#include <array>
+
+#include "common/assert.h"
+#include "core/hle/kernel/memory/address_space_info.h"
+
+namespace Kernel::Memory {
+
+namespace {
+
+enum : u64 {
+ Size_1_MB = 0x100000,
+ Size_2_MB = 2 * Size_1_MB,
+ Size_128_MB = 128 * Size_1_MB,
+ Size_1_GB = 0x40000000,
+ Size_2_GB = 2 * Size_1_GB,
+ Size_4_GB = 4 * Size_1_GB,
+ Size_6_GB = 6 * Size_1_GB,
+ Size_64_GB = 64 * Size_1_GB,
+ Size_512_GB = 512 * Size_1_GB,
+ Invalid = std::numeric_limits<u64>::max(),
+};
+
+// clang-format off
+constexpr std::array<AddressSpaceInfo, 13> AddressSpaceInfos{{
+ { .bit_width = 32, .address = Size_2_MB , .size = Size_1_GB - Size_2_MB , .type = AddressSpaceInfo::Type::Is32Bit, },
+ { .bit_width = 32, .address = Size_1_GB , .size = Size_4_GB - Size_1_GB , .type = AddressSpaceInfo::Type::Small64Bit, },
+ { .bit_width = 32, .address = Invalid , .size = Size_1_GB , .type = AddressSpaceInfo::Type::Heap, },
+ { .bit_width = 32, .address = Invalid , .size = Size_1_GB , .type = AddressSpaceInfo::Type::Alias, },
+ { .bit_width = 36, .address = Size_128_MB, .size = Size_2_GB - Size_128_MB, .type = AddressSpaceInfo::Type::Is32Bit, },
+ { .bit_width = 36, .address = Size_2_GB , .size = Size_64_GB - Size_2_GB , .type = AddressSpaceInfo::Type::Small64Bit, },
+ { .bit_width = 36, .address = Invalid , .size = Size_6_GB , .type = AddressSpaceInfo::Type::Heap, },
+ { .bit_width = 36, .address = Invalid , .size = Size_6_GB , .type = AddressSpaceInfo::Type::Alias, },
+ { .bit_width = 39, .address = Size_128_MB, .size = Size_512_GB - Size_128_MB, .type = AddressSpaceInfo::Type::Large64Bit, },
+ { .bit_width = 39, .address = Invalid , .size = Size_64_GB , .type = AddressSpaceInfo::Type::Is32Bit },
+ { .bit_width = 39, .address = Invalid , .size = Size_6_GB , .type = AddressSpaceInfo::Type::Heap, },
+ { .bit_width = 39, .address = Invalid , .size = Size_64_GB , .type = AddressSpaceInfo::Type::Alias, },
+ { .bit_width = 39, .address = Invalid , .size = Size_2_GB , .type = AddressSpaceInfo::Type::Stack, },
+}};
+// clang-format on
+
+constexpr bool IsAllowedIndexForAddress(std::size_t index) {
+ return index < AddressSpaceInfos.size() && AddressSpaceInfos[index].address != Invalid;
+}
+
+using IndexArray = std::array<std::size_t, static_cast<std::size_t>(AddressSpaceInfo::Type::Count)>;
+
+constexpr IndexArray AddressSpaceIndices32Bit{
+ 0, 1, 0, 2, 0, 3,
+};
+
+constexpr IndexArray AddressSpaceIndices36Bit{
+ 4, 5, 4, 6, 4, 7,
+};
+
+constexpr IndexArray AddressSpaceIndices39Bit{
+ 9, 8, 8, 10, 12, 11,
+};
+
+constexpr bool IsAllowed32BitType(AddressSpaceInfo::Type type) {
+ return type < AddressSpaceInfo::Type::Count && type != AddressSpaceInfo::Type::Large64Bit &&
+ type != AddressSpaceInfo::Type::Stack;
+}
+
+constexpr bool IsAllowed36BitType(AddressSpaceInfo::Type type) {
+ return type < AddressSpaceInfo::Type::Count && type != AddressSpaceInfo::Type::Large64Bit &&
+ type != AddressSpaceInfo::Type::Stack;
+}
+
+constexpr bool IsAllowed39BitType(AddressSpaceInfo::Type type) {
+ return type < AddressSpaceInfo::Type::Count && type != AddressSpaceInfo::Type::Small64Bit;
+}
+
+} // namespace
+
+u64 AddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) {
+ const std::size_t index{static_cast<std::size_t>(type)};
+ switch (width) {
+ case 32:
+ ASSERT(IsAllowed32BitType(type));
+ ASSERT(IsAllowedIndexForAddress(AddressSpaceIndices32Bit[index]));
+ return AddressSpaceInfos[AddressSpaceIndices32Bit[index]].address;
+ case 36:
+ ASSERT(IsAllowed36BitType(type));
+ ASSERT(IsAllowedIndexForAddress(AddressSpaceIndices36Bit[index]));
+ return AddressSpaceInfos[AddressSpaceIndices36Bit[index]].address;
+ case 39:
+ ASSERT(IsAllowed39BitType(type));
+ ASSERT(IsAllowedIndexForAddress(AddressSpaceIndices39Bit[index]));
+ return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address;
+ }
+ UNREACHABLE();
+}
+
+std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) {
+ const std::size_t index{static_cast<std::size_t>(type)};
+ switch (width) {
+ case 32:
+ ASSERT(IsAllowed32BitType(type));
+ return AddressSpaceInfos[AddressSpaceIndices32Bit[index]].size;
+ case 36:
+ ASSERT(IsAllowed36BitType(type));
+ return AddressSpaceInfos[AddressSpaceIndices36Bit[index]].size;
+ case 39:
+ ASSERT(IsAllowed39BitType(type));
+ return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size;
+ }
+ UNREACHABLE();
+}
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/address_space_info.h b/src/core/hle/kernel/memory/address_space_info.h
new file mode 100644
index 000000000..a4e6e91e5
--- /dev/null
+++ b/src/core/hle/kernel/memory/address_space_info.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel::Memory {
+
+struct AddressSpaceInfo final {
+ enum class Type : u32 {
+ Is32Bit = 0,
+ Small64Bit = 1,
+ Large64Bit = 2,
+ Heap = 3,
+ Stack = 4,
+ Alias = 5,
+ Count,
+ };
+
+ static u64 GetAddressSpaceStart(std::size_t width, Type type);
+ static std::size_t GetAddressSpaceSize(std::size_t width, Type type);
+
+ const std::size_t bit_width{};
+ const std::size_t address{};
+ const std::size_t size{};
+ const Type type{};
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_block.h b/src/core/hle/kernel/memory/memory_block.h
new file mode 100644
index 000000000..9d7839d08
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_block.h
@@ -0,0 +1,335 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/memory/memory_types.h"
+#include "core/hle/kernel/svc_types.h"
+
+namespace Kernel::Memory {
+
+enum class MemoryState : u32 {
+ None = 0,
+ Mask = 0xFF,
+ All = ~None,
+
+ FlagCanReprotect = (1 << 8),
+ FlagCanDebug = (1 << 9),
+ FlagCanUseIpc = (1 << 10),
+ FlagCanUseNonDeviceIpc = (1 << 11),
+ FlagCanUseNonSecureIpc = (1 << 12),
+ FlagMapped = (1 << 13),
+ FlagCode = (1 << 14),
+ FlagCanAlias = (1 << 15),
+ FlagCanCodeAlias = (1 << 16),
+ FlagCanTransfer = (1 << 17),
+ FlagCanQueryPhysical = (1 << 18),
+ FlagCanDeviceMap = (1 << 19),
+ FlagCanAlignedDeviceMap = (1 << 20),
+ FlagCanIpcUserBuffer = (1 << 21),
+ FlagReferenceCounted = (1 << 22),
+ FlagCanMapProcess = (1 << 23),
+ FlagCanChangeAttribute = (1 << 24),
+ FlagCanCodeMemory = (1 << 25),
+
+ FlagsData = FlagCanReprotect | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
+ FlagMapped | FlagCanAlias | FlagCanTransfer | FlagCanQueryPhysical |
+ FlagCanDeviceMap | FlagCanAlignedDeviceMap | FlagCanIpcUserBuffer |
+ FlagReferenceCounted | FlagCanChangeAttribute,
+
+ FlagsCode = FlagCanDebug | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
+ FlagMapped | FlagCode | FlagCanQueryPhysical | FlagCanDeviceMap |
+ FlagCanAlignedDeviceMap | FlagReferenceCounted,
+
+ FlagsMisc = FlagMapped | FlagReferenceCounted | FlagCanQueryPhysical | FlagCanDeviceMap,
+
+ Free = static_cast<u32>(Svc::MemoryState::Free),
+ Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped,
+ Static = static_cast<u32>(Svc::MemoryState::Static) | FlagMapped | FlagCanQueryPhysical,
+ Code = static_cast<u32>(Svc::MemoryState::Code) | FlagsCode | FlagCanMapProcess,
+ CodeData = static_cast<u32>(Svc::MemoryState::CodeData) | FlagsData | FlagCanMapProcess |
+ FlagCanCodeMemory,
+ Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted,
+ Normal = static_cast<u32>(Svc::MemoryState::Normal) | FlagsData | FlagCanCodeMemory,
+
+ AliasCode = static_cast<u32>(Svc::MemoryState::AliasCode) | FlagsCode | FlagCanMapProcess |
+ FlagCanCodeAlias,
+ AliasCodeData = static_cast<u32>(Svc::MemoryState::AliasCodeData) | FlagsData |
+ FlagCanMapProcess | FlagCanCodeAlias | FlagCanCodeMemory,
+
+ Ipc = static_cast<u32>(Svc::MemoryState::Ipc) | FlagsMisc | FlagCanAlignedDeviceMap |
+ FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ Stack = static_cast<u32>(Svc::MemoryState::Stack) | FlagsMisc | FlagCanAlignedDeviceMap |
+ FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ ThreadLocal =
+ static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted,
+
+ Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc |
+ FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
+ FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransfered) | FlagsMisc |
+ FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped |
+ FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ Inaccessible = static_cast<u32>(Svc::MemoryState::Inaccessible),
+
+ NonSecureIpc = static_cast<u32>(Svc::MemoryState::NonSecureIpc) | FlagsMisc |
+ FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+
+ NonDeviceIpc =
+ static_cast<u32>(Svc::MemoryState::NonDeviceIpc) | FlagsMisc | FlagCanUseNonDeviceIpc,
+
+ Kernel = static_cast<u32>(Svc::MemoryState::Kernel) | FlagMapped,
+
+ GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped |
+ FlagReferenceCounted | FlagCanDebug,
+ CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted,
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryState);
+
+static_assert(static_cast<u32>(MemoryState::Free) == 0x00000000);
+static_assert(static_cast<u32>(MemoryState::Io) == 0x00002001);
+static_assert(static_cast<u32>(MemoryState::Static) == 0x00042002);
+static_assert(static_cast<u32>(MemoryState::Code) == 0x00DC7E03);
+static_assert(static_cast<u32>(MemoryState::CodeData) == 0x03FEBD04);
+static_assert(static_cast<u32>(MemoryState::Normal) == 0x037EBD05);
+static_assert(static_cast<u32>(MemoryState::Shared) == 0x00402006);
+static_assert(static_cast<u32>(MemoryState::AliasCode) == 0x00DD7E08);
+static_assert(static_cast<u32>(MemoryState::AliasCodeData) == 0x03FFBD09);
+static_assert(static_cast<u32>(MemoryState::Ipc) == 0x005C3C0A);
+static_assert(static_cast<u32>(MemoryState::Stack) == 0x005C3C0B);
+static_assert(static_cast<u32>(MemoryState::ThreadLocal) == 0x0040200C);
+static_assert(static_cast<u32>(MemoryState::Transfered) == 0x015C3C0D);
+static_assert(static_cast<u32>(MemoryState::SharedTransfered) == 0x005C380E);
+static_assert(static_cast<u32>(MemoryState::SharedCode) == 0x0040380F);
+static_assert(static_cast<u32>(MemoryState::Inaccessible) == 0x00000010);
+static_assert(static_cast<u32>(MemoryState::NonSecureIpc) == 0x005C3811);
+static_assert(static_cast<u32>(MemoryState::NonDeviceIpc) == 0x004C2812);
+static_assert(static_cast<u32>(MemoryState::Kernel) == 0x00002013);
+static_assert(static_cast<u32>(MemoryState::GeneratedCode) == 0x00402214);
+static_assert(static_cast<u32>(MemoryState::CodeOut) == 0x00402015);
+
+enum class MemoryPermission : u8 {
+ None = 0,
+ Mask = static_cast<u8>(~None),
+
+ Read = 1 << 0,
+ Write = 1 << 1,
+ Execute = 1 << 2,
+
+ ReadAndWrite = Read | Write,
+ ReadAndExecute = Read | Execute,
+
+ UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
+ Svc::MemoryPermission::Execute),
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission);
+
+enum class MemoryAttribute : u8 {
+ None = 0x00,
+ Mask = 0x7F,
+ All = Mask,
+ DontCareMask = 0x80,
+
+ Locked = static_cast<u8>(Svc::MemoryAttribute::Locked),
+ IpcLocked = static_cast<u8>(Svc::MemoryAttribute::IpcLocked),
+ DeviceShared = static_cast<u8>(Svc::MemoryAttribute::DeviceShared),
+ Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached),
+
+ IpcAndDeviceMapped = IpcLocked | DeviceShared,
+ LockedAndIpcLocked = Locked | IpcLocked,
+ DeviceSharedAndUncached = DeviceShared | Uncached
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute);
+
+static_assert((static_cast<u8>(MemoryAttribute::Mask) &
+ static_cast<u8>(MemoryAttribute::DontCareMask)) == 0);
+
+struct MemoryInfo {
+ VAddr addr{};
+ std::size_t size{};
+ MemoryState state{};
+ MemoryPermission perm{};
+ MemoryAttribute attribute{};
+ MemoryPermission original_perm{};
+ u16 ipc_lock_count{};
+ u16 device_use_count{};
+
+ constexpr Svc::MemoryInfo GetSvcMemoryInfo() const {
+ return {
+ addr,
+ size,
+ static_cast<Svc::MemoryState>(state & MemoryState::Mask),
+ static_cast<Svc::MemoryAttribute>(attribute & MemoryAttribute::Mask),
+ static_cast<Svc::MemoryPermission>(perm & MemoryPermission::UserMask),
+ ipc_lock_count,
+ device_use_count,
+ };
+ }
+
+ constexpr VAddr GetAddress() const {
+ return addr;
+ }
+ constexpr std::size_t GetSize() const {
+ return size;
+ }
+ constexpr std::size_t GetNumPages() const {
+ return GetSize() / PageSize;
+ }
+ constexpr VAddr GetEndAddress() const {
+ return GetAddress() + GetSize();
+ }
+ constexpr VAddr GetLastAddress() const {
+ return GetEndAddress() - 1;
+ }
+};
+
+class MemoryBlock final {
+ friend class MemoryBlockManager;
+
+private:
+ VAddr addr{};
+ std::size_t num_pages{};
+ MemoryState state{MemoryState::None};
+ u16 ipc_lock_count{};
+ u16 device_use_count{};
+ MemoryPermission perm{MemoryPermission::None};
+ MemoryPermission original_perm{MemoryPermission::None};
+ MemoryAttribute attribute{MemoryAttribute::None};
+
+public:
+ static constexpr int Compare(const MemoryBlock& lhs, const MemoryBlock& rhs) {
+ if (lhs.GetAddress() < rhs.GetAddress()) {
+ return -1;
+ } else if (lhs.GetAddress() <= rhs.GetLastAddress()) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+public:
+ constexpr MemoryBlock() = default;
+ constexpr MemoryBlock(VAddr addr, std::size_t num_pages, MemoryState state,
+ MemoryPermission perm, MemoryAttribute attribute)
+ : addr{addr}, num_pages(num_pages), state{state}, perm{perm}, attribute{attribute} {}
+
+ constexpr VAddr GetAddress() const {
+ return addr;
+ }
+
+ constexpr std::size_t GetNumPages() const {
+ return num_pages;
+ }
+
+ constexpr std::size_t GetSize() const {
+ return GetNumPages() * PageSize;
+ }
+
+ constexpr VAddr GetEndAddress() const {
+ return GetAddress() + GetSize();
+ }
+
+ constexpr VAddr GetLastAddress() const {
+ return GetEndAddress() - 1;
+ }
+
+ constexpr MemoryInfo GetMemoryInfo() const {
+ return {
+ GetAddress(), GetSize(), state, perm,
+ attribute, original_perm, ipc_lock_count, device_use_count,
+ };
+ }
+
+ void ShareToDevice(MemoryPermission /*new_perm*/) {
+ ASSERT((attribute & MemoryAttribute::DeviceShared) == MemoryAttribute::DeviceShared ||
+ device_use_count == 0);
+ attribute |= MemoryAttribute::DeviceShared;
+ const u16 new_use_count{++device_use_count};
+ ASSERT(new_use_count > 0);
+ }
+
+ void UnshareToDevice(MemoryPermission /*new_perm*/) {
+ ASSERT((attribute & MemoryAttribute::DeviceShared) == MemoryAttribute::DeviceShared);
+ const u16 prev_use_count{device_use_count--};
+ ASSERT(prev_use_count > 0);
+ if (prev_use_count == 1) {
+ attribute &= ~MemoryAttribute::DeviceShared;
+ }
+ }
+
+private:
+ constexpr bool HasProperties(MemoryState s, MemoryPermission p, MemoryAttribute a) const {
+ constexpr MemoryAttribute AttributeIgnoreMask{MemoryAttribute::DontCareMask |
+ MemoryAttribute::IpcLocked |
+ MemoryAttribute::DeviceShared};
+ return state == s && perm == p &&
+ (attribute | AttributeIgnoreMask) == (a | AttributeIgnoreMask);
+ }
+
+ constexpr bool HasSameProperties(const MemoryBlock& rhs) const {
+ return state == rhs.state && perm == rhs.perm && original_perm == rhs.original_perm &&
+ attribute == rhs.attribute && ipc_lock_count == rhs.ipc_lock_count &&
+ device_use_count == rhs.device_use_count;
+ }
+
+ constexpr bool Contains(VAddr start) const {
+ return GetAddress() <= start && start <= GetEndAddress();
+ }
+
+ constexpr void Add(std::size_t count) {
+ ASSERT(count > 0);
+ ASSERT(GetAddress() + count * PageSize - 1 < GetEndAddress() + count * PageSize - 1);
+
+ num_pages += count;
+ }
+
+ constexpr void Update(MemoryState new_state, MemoryPermission new_perm,
+ MemoryAttribute new_attribute) {
+ ASSERT(original_perm == MemoryPermission::None);
+ ASSERT((attribute & MemoryAttribute::IpcLocked) == MemoryAttribute::None);
+
+ state = new_state;
+ perm = new_perm;
+
+ attribute = static_cast<MemoryAttribute>(
+ new_attribute |
+ (attribute & (MemoryAttribute::IpcLocked | MemoryAttribute::DeviceShared)));
+ }
+
+ constexpr MemoryBlock Split(VAddr split_addr) {
+ ASSERT(GetAddress() < split_addr);
+ ASSERT(Contains(split_addr));
+ ASSERT(Common::IsAligned(split_addr, PageSize));
+
+ MemoryBlock block;
+ block.addr = addr;
+ block.num_pages = (split_addr - GetAddress()) / PageSize;
+ block.state = state;
+ block.ipc_lock_count = ipc_lock_count;
+ block.device_use_count = device_use_count;
+ block.perm = perm;
+ block.original_perm = original_perm;
+ block.attribute = attribute;
+
+ addr = split_addr;
+ num_pages -= block.num_pages;
+
+ return block;
+ }
+};
+static_assert(std::is_trivially_destructible<MemoryBlock>::value);
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_block_manager.cpp b/src/core/hle/kernel/memory/memory_block_manager.cpp
new file mode 100644
index 000000000..0732fa5a1
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_block_manager.cpp
@@ -0,0 +1,223 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/memory/memory_block_manager.h"
+#include "core/hle/kernel/memory/memory_types.h"
+
+namespace Kernel::Memory {
+
+MemoryBlockManager::MemoryBlockManager(VAddr start_addr, VAddr end_addr)
+ : start_addr{start_addr}, end_addr{end_addr} {
+ const u64 num_pages{(end_addr - start_addr) / PageSize};
+ memory_block_tree.emplace_back(start_addr, num_pages, MemoryState::Free, MemoryPermission::None,
+ MemoryAttribute::None);
+}
+
+MemoryBlockManager::iterator MemoryBlockManager::FindIterator(VAddr addr) {
+ auto node{memory_block_tree.begin()};
+ while (node != end()) {
+ const VAddr end_addr{node->GetNumPages() * PageSize + node->GetAddress()};
+ if (node->GetAddress() <= addr && end_addr - 1 >= addr) {
+ return node;
+ }
+ node = std::next(node);
+ }
+ return end();
+}
+
+VAddr MemoryBlockManager::FindFreeArea(VAddr region_start, std::size_t region_num_pages,
+ std::size_t num_pages, std::size_t align, std::size_t offset,
+ std::size_t guard_pages) {
+ if (num_pages == 0) {
+ return {};
+ }
+
+ const VAddr region_end{region_start + region_num_pages * PageSize};
+ const VAddr region_last{region_end - 1};
+ for (auto it{FindIterator(region_start)}; it != memory_block_tree.cend(); it++) {
+ const auto info{it->GetMemoryInfo()};
+ if (region_last < info.GetAddress()) {
+ break;
+ }
+
+ if (info.state != MemoryState::Free) {
+ continue;
+ }
+
+ VAddr area{(info.GetAddress() <= region_start) ? region_start : info.GetAddress()};
+ area += guard_pages * PageSize;
+
+ const VAddr offset_area{Common::AlignDown(area, align) + offset};
+ area = (area <= offset_area) ? offset_area : offset_area + align;
+
+ const VAddr area_end{area + num_pages * PageSize + guard_pages * PageSize};
+ const VAddr area_last{area_end - 1};
+
+ if (info.GetAddress() <= area && area < area_last && area_last <= region_last &&
+ area_last <= info.GetLastAddress()) {
+ return area;
+ }
+ }
+
+ return {};
+}
+
+void MemoryBlockManager::Update(VAddr addr, std::size_t num_pages, MemoryState prev_state,
+ MemoryPermission prev_perm, MemoryAttribute prev_attribute,
+ MemoryState state, MemoryPermission perm,
+ MemoryAttribute attribute) {
+ const VAddr end_addr{addr + num_pages * PageSize};
+ iterator node{memory_block_tree.begin()};
+
+ prev_attribute |= MemoryAttribute::IpcAndDeviceMapped;
+
+ while (node != memory_block_tree.end()) {
+ MemoryBlock* block{&(*node)};
+ iterator next_node{std::next(node)};
+ const VAddr cur_addr{block->GetAddress()};
+ const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr};
+
+ if (addr < cur_end_addr && cur_addr < end_addr) {
+ if (!block->HasProperties(prev_state, prev_perm, prev_attribute)) {
+ node = next_node;
+ continue;
+ }
+
+ iterator new_node{node};
+ if (addr > cur_addr) {
+ memory_block_tree.insert(node, block->Split(addr));
+ }
+
+ if (end_addr < cur_end_addr) {
+ new_node = memory_block_tree.insert(node, block->Split(end_addr));
+ }
+
+ new_node->Update(state, perm, attribute);
+
+ MergeAdjacent(new_node, next_node);
+ }
+
+ if (cur_end_addr - 1 >= end_addr - 1) {
+ break;
+ }
+
+ node = next_node;
+ }
+}
+
+void MemoryBlockManager::Update(VAddr addr, std::size_t num_pages, MemoryState state,
+ MemoryPermission perm, MemoryAttribute attribute) {
+ const VAddr end_addr{addr + num_pages * PageSize};
+ iterator node{memory_block_tree.begin()};
+
+ while (node != memory_block_tree.end()) {
+ MemoryBlock* block{&(*node)};
+ iterator next_node{std::next(node)};
+ const VAddr cur_addr{block->GetAddress()};
+ const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr};
+
+ if (addr < cur_end_addr && cur_addr < end_addr) {
+ iterator new_node{node};
+
+ if (addr > cur_addr) {
+ memory_block_tree.insert(node, block->Split(addr));
+ }
+
+ if (end_addr < cur_end_addr) {
+ new_node = memory_block_tree.insert(node, block->Split(end_addr));
+ }
+
+ new_node->Update(state, perm, attribute);
+
+ MergeAdjacent(new_node, next_node);
+ }
+
+ if (cur_end_addr - 1 >= end_addr - 1) {
+ break;
+ }
+
+ node = next_node;
+ }
+}
+
+void MemoryBlockManager::UpdateLock(VAddr addr, std::size_t num_pages, LockFunc&& lock_func,
+ MemoryPermission perm) {
+ const VAddr end_addr{addr + num_pages * PageSize};
+ iterator node{memory_block_tree.begin()};
+
+ while (node != memory_block_tree.end()) {
+ MemoryBlock* block{&(*node)};
+ iterator next_node{std::next(node)};
+ const VAddr cur_addr{block->GetAddress()};
+ const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr};
+
+ if (addr < cur_end_addr && cur_addr < end_addr) {
+ iterator new_node{node};
+
+ if (addr > cur_addr) {
+ memory_block_tree.insert(node, block->Split(addr));
+ }
+
+ if (end_addr < cur_end_addr) {
+ new_node = memory_block_tree.insert(node, block->Split(end_addr));
+ }
+
+ lock_func(new_node, perm);
+
+ MergeAdjacent(new_node, next_node);
+ }
+
+ if (cur_end_addr - 1 >= end_addr - 1) {
+ break;
+ }
+
+ node = next_node;
+ }
+}
+
+void MemoryBlockManager::IterateForRange(VAddr start, VAddr end, IterateFunc&& func) {
+ const_iterator it{FindIterator(start)};
+ MemoryInfo info{};
+ do {
+ info = it->GetMemoryInfo();
+ func(info);
+ it = std::next(it);
+ } while (info.addr + info.size - 1 < end - 1 && it != cend());
+}
+
+void MemoryBlockManager::MergeAdjacent(iterator it, iterator& next_it) {
+ MemoryBlock* block{&(*it)};
+
+ auto EraseIt = [&](const iterator it_to_erase) {
+ if (next_it == it_to_erase) {
+ next_it = std::next(next_it);
+ }
+ memory_block_tree.erase(it_to_erase);
+ };
+
+ if (it != memory_block_tree.begin()) {
+ MemoryBlock* prev{&(*std::prev(it))};
+
+ if (block->HasSameProperties(*prev)) {
+ const iterator prev_it{std::prev(it)};
+
+ prev->Add(block->GetNumPages());
+ EraseIt(it);
+
+ it = prev_it;
+ block = prev;
+ }
+ }
+
+ if (it != cend()) {
+ const MemoryBlock* const next{&(*std::next(it))};
+
+ if (block->HasSameProperties(*next)) {
+ block->Add(next->GetNumPages());
+ EraseIt(std::next(it));
+ }
+ }
+}
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_block_manager.h b/src/core/hle/kernel/memory/memory_block_manager.h
new file mode 100644
index 000000000..6e1d41075
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_block_manager.h
@@ -0,0 +1,66 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <list>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/memory/memory_block.h"
+
+namespace Kernel::Memory {
+
+class MemoryBlockManager final {
+public:
+ using MemoryBlockTree = std::list<MemoryBlock>;
+ using iterator = MemoryBlockTree::iterator;
+ using const_iterator = MemoryBlockTree::const_iterator;
+
+public:
+ MemoryBlockManager(VAddr start_addr, VAddr end_addr);
+
+ iterator end() {
+ return memory_block_tree.end();
+ }
+ const_iterator end() const {
+ return memory_block_tree.end();
+ }
+ const_iterator cend() const {
+ return memory_block_tree.cend();
+ }
+
+ iterator FindIterator(VAddr addr);
+
+ VAddr FindFreeArea(VAddr region_start, std::size_t region_num_pages, std::size_t num_pages,
+ std::size_t align, std::size_t offset, std::size_t guard_pages);
+
+ void Update(VAddr addr, std::size_t num_pages, MemoryState prev_state,
+ MemoryPermission prev_perm, MemoryAttribute prev_attribute, MemoryState state,
+ MemoryPermission perm, MemoryAttribute attribute);
+
+ void Update(VAddr addr, std::size_t num_pages, MemoryState state,
+ MemoryPermission perm = MemoryPermission::None,
+ MemoryAttribute attribute = MemoryAttribute::None);
+
+ using LockFunc = std::function<void(iterator, MemoryPermission)>;
+ void UpdateLock(VAddr addr, std::size_t num_pages, LockFunc&& lock_func, MemoryPermission perm);
+
+ using IterateFunc = std::function<void(const MemoryInfo&)>;
+ void IterateForRange(VAddr start, VAddr end, IterateFunc&& func);
+
+ MemoryBlock& FindBlock(VAddr addr) {
+ return *FindIterator(addr);
+ }
+
+private:
+ void MergeAdjacent(iterator it, iterator& next_it);
+
+ const VAddr start_addr;
+ const VAddr end_addr;
+
+ MemoryBlockTree memory_block_tree;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_layout.h b/src/core/hle/kernel/memory/memory_layout.h
new file mode 100644
index 000000000..9b3d6267a
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_layout.h
@@ -0,0 +1,71 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel::Memory {
+
+class MemoryRegion final {
+ friend class MemoryLayout;
+
+public:
+ constexpr PAddr StartAddress() const {
+ return start_address;
+ }
+
+ constexpr PAddr EndAddress() const {
+ return end_address;
+ }
+
+private:
+ constexpr MemoryRegion() = default;
+ constexpr MemoryRegion(PAddr start_address, PAddr end_address)
+ : start_address{start_address}, end_address{end_address} {}
+
+ const PAddr start_address{};
+ const PAddr end_address{};
+};
+
+class MemoryLayout final {
+public:
+ constexpr const MemoryRegion& Application() const {
+ return application;
+ }
+
+ constexpr const MemoryRegion& Applet() const {
+ return applet;
+ }
+
+ constexpr const MemoryRegion& System() const {
+ return system;
+ }
+
+ static constexpr MemoryLayout GetDefaultLayout() {
+ constexpr std::size_t application_size{0xcd500000};
+ constexpr std::size_t applet_size{0x1fb00000};
+ constexpr PAddr application_start_address{Core::DramMemoryMap::End - application_size};
+ constexpr PAddr application_end_address{Core::DramMemoryMap::End};
+ constexpr PAddr applet_start_address{application_start_address - applet_size};
+ constexpr PAddr applet_end_address{applet_start_address + applet_size};
+ constexpr PAddr system_start_address{Core::DramMemoryMap::SlabHeapEnd};
+ constexpr PAddr system_end_address{applet_start_address};
+ return {application_start_address, application_end_address, applet_start_address,
+ applet_end_address, system_start_address, system_end_address};
+ }
+
+private:
+ constexpr MemoryLayout(PAddr application_start_address, std::size_t application_size,
+ PAddr applet_start_address, std::size_t applet_size,
+ PAddr system_start_address, std::size_t system_size)
+ : application{application_start_address, application_size},
+ applet{applet_start_address, applet_size}, system{system_start_address, system_size} {}
+
+ const MemoryRegion application;
+ const MemoryRegion applet;
+ const MemoryRegion system;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_manager.cpp b/src/core/hle/kernel/memory/memory_manager.cpp
new file mode 100644
index 000000000..acf13585c
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_manager.cpp
@@ -0,0 +1,175 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/scope_exit.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/memory/memory_manager.h"
+#include "core/hle/kernel/memory/page_linked_list.h"
+
+namespace Kernel::Memory {
+
+std::size_t MemoryManager::Impl::Initialize(Pool new_pool, u64 start_address, u64 end_address) {
+ const auto size{end_address - start_address};
+
+ // Calculate metadata sizes
+ const auto ref_count_size{(size / PageSize) * sizeof(u16)};
+ const auto optimize_map_size{(Common::AlignUp((size / PageSize), 64) / 64) * sizeof(u64)};
+ const auto manager_size{Common::AlignUp(optimize_map_size + ref_count_size, PageSize)};
+ const auto page_heap_size{PageHeap::CalculateMetadataOverheadSize(size)};
+ const auto total_metadata_size{manager_size + page_heap_size};
+ ASSERT(manager_size <= total_metadata_size);
+ ASSERT(Common::IsAligned(total_metadata_size, PageSize));
+
+ // Setup region
+ pool = new_pool;
+
+ // Initialize the manager's KPageHeap
+ heap.Initialize(start_address, size, page_heap_size);
+
+ // Free the memory to the heap
+ heap.Free(start_address, size / PageSize);
+
+ // Update the heap's used size
+ heap.UpdateUsedSize();
+
+ return total_metadata_size;
+}
+
+void MemoryManager::InitializeManager(Pool pool, u64 start_address, u64 end_address) {
+ ASSERT(pool < Pool::Count);
+ managers[static_cast<std::size_t>(pool)].Initialize(pool, start_address, end_address);
+}
+
+VAddr MemoryManager::AllocateContinuous(std::size_t num_pages, std::size_t align_pages, Pool pool,
+ Direction dir) {
+ // Early return if we're allocating no pages
+ if (num_pages == 0) {
+ return {};
+ }
+
+ // Lock the pool that we're allocating from
+ const auto pool_index{static_cast<std::size_t>(pool)};
+ std::lock_guard lock{pool_locks[pool_index]};
+
+ // Choose a heap based on our page size request
+ const s32 heap_index{PageHeap::GetAlignedBlockIndex(num_pages, align_pages)};
+
+ // Loop, trying to iterate from each block
+ // TODO (bunnei): Support multiple managers
+ Impl& chosen_manager{managers[pool_index]};
+ VAddr allocated_block{chosen_manager.AllocateBlock(heap_index)};
+
+ // If we failed to allocate, quit now
+ if (!allocated_block) {
+ return {};
+ }
+
+ // If we allocated more than we need, free some
+ const auto allocated_pages{PageHeap::GetBlockNumPages(heap_index)};
+ if (allocated_pages > num_pages) {
+ chosen_manager.Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages);
+ }
+
+ return allocated_block;
+}
+
+ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pages, Pool pool,
+ Direction dir) {
+ ASSERT(page_list.GetNumPages() == 0);
+
+ // Early return if we're allocating no pages
+ if (num_pages == 0) {
+ return RESULT_SUCCESS;
+ }
+
+ // Lock the pool that we're allocating from
+ const auto pool_index{static_cast<std::size_t>(pool)};
+ std::lock_guard lock{pool_locks[pool_index]};
+
+ // Choose a heap based on our page size request
+ const s32 heap_index{PageHeap::GetBlockIndex(num_pages)};
+ if (heap_index < 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ // TODO (bunnei): Support multiple managers
+ Impl& chosen_manager{managers[pool_index]};
+
+ // Ensure that we don't leave anything un-freed
+ auto group_guard = detail::ScopeExit([&] {
+ for (const auto& it : page_list.Nodes()) {
+ const auto min_num_pages{std::min<size_t>(
+ it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)};
+ chosen_manager.Free(it.GetAddress(), min_num_pages);
+ }
+ });
+
+ // Keep allocating until we've allocated all our pages
+ for (s32 index{heap_index}; index >= 0 && num_pages > 0; index--) {
+ const auto pages_per_alloc{PageHeap::GetBlockNumPages(index)};
+
+ while (num_pages >= pages_per_alloc) {
+ // Allocate a block
+ VAddr allocated_block{chosen_manager.AllocateBlock(index)};
+ if (!allocated_block) {
+ break;
+ }
+
+ // Safely add it to our group
+ {
+ auto block_guard = detail::ScopeExit(
+ [&] { chosen_manager.Free(allocated_block, pages_per_alloc); });
+
+ if (const ResultCode result{page_list.AddBlock(allocated_block, pages_per_alloc)};
+ result.IsError()) {
+ return result;
+ }
+
+ block_guard.Cancel();
+ }
+
+ num_pages -= pages_per_alloc;
+ }
+ }
+
+ // Only succeed if we allocated as many pages as we wanted
+ if (num_pages) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ // We succeeded!
+ group_guard.Cancel();
+ return RESULT_SUCCESS;
+}
+
+ResultCode MemoryManager::Free(PageLinkedList& page_list, std::size_t num_pages, Pool pool,
+ Direction dir) {
+ // Early return if we're freeing no pages
+ if (!num_pages) {
+ return RESULT_SUCCESS;
+ }
+
+ // Lock the pool that we're freeing from
+ const auto pool_index{static_cast<std::size_t>(pool)};
+ std::lock_guard lock{pool_locks[pool_index]};
+
+ // TODO (bunnei): Support multiple managers
+ Impl& chosen_manager{managers[pool_index]};
+
+ // Free all of the pages
+ for (const auto& it : page_list.Nodes()) {
+ const auto min_num_pages{std::min<size_t>(
+ it.GetNumPages(), (chosen_manager.GetEndAddress() - it.GetAddress()) / PageSize)};
+ chosen_manager.Free(it.GetAddress(), min_num_pages);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_manager.h b/src/core/hle/kernel/memory/memory_manager.h
new file mode 100644
index 000000000..3cf444857
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_manager.h
@@ -0,0 +1,96 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/memory/page_heap.h"
+#include "core/hle/result.h"
+
+namespace Kernel::Memory {
+
+class PageLinkedList;
+
+class MemoryManager final : NonCopyable {
+public:
+ enum class Pool : u32 {
+ Application = 0,
+ Applet = 1,
+ System = 2,
+ SystemNonSecure = 3,
+
+ Count,
+
+ Shift = 4,
+ Mask = (0xF << Shift),
+ };
+
+ enum class Direction : u32 {
+ FromFront = 0,
+ FromBack = 1,
+
+ Shift = 0,
+ Mask = (0xF << Shift),
+ };
+
+ MemoryManager() = default;
+
+ constexpr std::size_t GetSize(Pool pool) const {
+ return managers[static_cast<std::size_t>(pool)].GetSize();
+ }
+
+ void InitializeManager(Pool pool, u64 start_address, u64 end_address);
+ VAddr AllocateContinuous(std::size_t num_pages, std::size_t align_pages, Pool pool,
+ Direction dir = Direction::FromFront);
+ ResultCode Allocate(PageLinkedList& page_list, std::size_t num_pages, Pool pool,
+ Direction dir = Direction::FromFront);
+ ResultCode Free(PageLinkedList& page_list, std::size_t num_pages, Pool pool,
+ Direction dir = Direction::FromFront);
+
+ static constexpr std::size_t MaxManagerCount = 10;
+
+private:
+ class Impl final : NonCopyable {
+ private:
+ using RefCount = u16;
+
+ private:
+ PageHeap heap;
+ Pool pool{};
+
+ public:
+ Impl() = default;
+
+ std::size_t Initialize(Pool new_pool, u64 start_address, u64 end_address);
+
+ VAddr AllocateBlock(s32 index) {
+ return heap.AllocateBlock(index);
+ }
+
+ void Free(VAddr addr, std::size_t num_pages) {
+ heap.Free(addr, num_pages);
+ }
+
+ constexpr std::size_t GetSize() const {
+ return heap.GetSize();
+ }
+
+ constexpr VAddr GetAddress() const {
+ return heap.GetAddress();
+ }
+
+ constexpr VAddr GetEndAddress() const {
+ return heap.GetEndAddress();
+ }
+ };
+
+private:
+ std::array<std::mutex, static_cast<std::size_t>(Pool::Count)> pool_locks;
+ std::array<Impl, MaxManagerCount> managers;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_types.h b/src/core/hle/kernel/memory/memory_types.h
new file mode 100644
index 000000000..a75bf77c0
--- /dev/null
+++ b/src/core/hle/kernel/memory/memory_types.h
@@ -0,0 +1,18 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace Kernel::Memory {
+
+constexpr std::size_t PageBits{12};
+constexpr std::size_t PageSize{1 << PageBits};
+
+using Page = std::array<u8, PageSize>;
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/page_heap.cpp b/src/core/hle/kernel/memory/page_heap.cpp
new file mode 100644
index 000000000..0ab1f7205
--- /dev/null
+++ b/src/core/hle/kernel/memory/page_heap.cpp
@@ -0,0 +1,119 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#include "core/core.h"
+#include "core/hle/kernel/memory/page_heap.h"
+#include "core/memory.h"
+
+namespace Kernel::Memory {
+
+void PageHeap::Initialize(VAddr address, std::size_t size, std::size_t metadata_size) {
+ // Check our assumptions
+ ASSERT(Common::IsAligned((address), PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Set our members
+ heap_address = address;
+ heap_size = size;
+
+ // Setup bitmaps
+ metadata.resize(metadata_size / sizeof(u64));
+ u64* cur_bitmap_storage{metadata.data()};
+ for (std::size_t i = 0; i < MemoryBlockPageShifts.size(); i++) {
+ const std::size_t cur_block_shift{MemoryBlockPageShifts[i]};
+ const std::size_t next_block_shift{
+ (i != MemoryBlockPageShifts.size() - 1) ? MemoryBlockPageShifts[i + 1] : 0};
+ cur_bitmap_storage = blocks[i].Initialize(heap_address, heap_size, cur_block_shift,
+ next_block_shift, cur_bitmap_storage);
+ }
+}
+
+VAddr PageHeap::AllocateBlock(s32 index) {
+ const std::size_t needed_size{blocks[index].GetSize()};
+
+ for (s32 i{index}; i < static_cast<s32>(MemoryBlockPageShifts.size()); i++) {
+ if (const VAddr addr{blocks[i].PopBlock()}; addr) {
+ if (const std::size_t allocated_size{blocks[i].GetSize()};
+ allocated_size > needed_size) {
+ Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
+ }
+ return addr;
+ }
+ }
+
+ return 0;
+}
+
+void PageHeap::FreeBlock(VAddr block, s32 index) {
+ do {
+ block = blocks[index++].PushBlock(block);
+ } while (block != 0);
+}
+
+void PageHeap::Free(VAddr addr, std::size_t num_pages) {
+ // Freeing no pages is a no-op
+ if (num_pages == 0) {
+ return;
+ }
+
+ // Find the largest block size that we can free, and free as many as possible
+ s32 big_index{static_cast<s32>(MemoryBlockPageShifts.size()) - 1};
+ const VAddr start{addr};
+ const VAddr end{(num_pages * PageSize) + addr};
+ VAddr before_start{start};
+ VAddr before_end{start};
+ VAddr after_start{end};
+ VAddr after_end{end};
+ while (big_index >= 0) {
+ const std::size_t block_size{blocks[big_index].GetSize()};
+ const VAddr big_start{Common::AlignUp((start), block_size)};
+ const VAddr big_end{Common::AlignDown((end), block_size)};
+ if (big_start < big_end) {
+ // Free as many big blocks as we can
+ for (auto block{big_start}; block < big_end; block += block_size) {
+ FreeBlock(block, big_index);
+ }
+ before_end = big_start;
+ after_start = big_end;
+ break;
+ }
+ big_index--;
+ }
+ ASSERT(big_index >= 0);
+
+ // Free space before the big blocks
+ for (s32 i{big_index - 1}; i >= 0; i--) {
+ const std::size_t block_size{blocks[i].GetSize()};
+ while (before_start + block_size <= before_end) {
+ before_end -= block_size;
+ FreeBlock(before_end, i);
+ }
+ }
+
+ // Free space after the big blocks
+ for (s32 i{big_index - 1}; i >= 0; i--) {
+ const std::size_t block_size{blocks[i].GetSize()};
+ while (after_start + block_size <= after_end) {
+ FreeBlock(after_start, i);
+ after_start += block_size;
+ }
+ }
+}
+
+std::size_t PageHeap::CalculateMetadataOverheadSize(std::size_t region_size) {
+ std::size_t overhead_size = 0;
+ for (std::size_t i = 0; i < MemoryBlockPageShifts.size(); i++) {
+ const std::size_t cur_block_shift{MemoryBlockPageShifts[i]};
+ const std::size_t next_block_shift{
+ (i != MemoryBlockPageShifts.size() - 1) ? MemoryBlockPageShifts[i + 1] : 0};
+ overhead_size += PageHeap::Block::CalculateMetadataOverheadSize(
+ region_size, cur_block_shift, next_block_shift);
+ }
+ return Common::AlignUp(overhead_size, PageSize);
+}
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/page_heap.h b/src/core/hle/kernel/memory/page_heap.h
new file mode 100644
index 000000000..22b0de860
--- /dev/null
+++ b/src/core/hle/kernel/memory/page_heap.h
@@ -0,0 +1,370 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/bit_util.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/memory/memory_types.h"
+
+namespace Kernel::Memory {
+
+class PageHeap final : NonCopyable {
+public:
+ static constexpr s32 GetAlignedBlockIndex(std::size_t num_pages, std::size_t align_pages) {
+ const auto target_pages{std::max(num_pages, align_pages)};
+ for (std::size_t i = 0; i < NumMemoryBlockPageShifts; i++) {
+ if (target_pages <=
+ (static_cast<std::size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ return static_cast<s32>(i);
+ }
+ }
+ return -1;
+ }
+
+ static constexpr s32 GetBlockIndex(std::size_t num_pages) {
+ for (s32 i{static_cast<s32>(NumMemoryBlockPageShifts) - 1}; i >= 0; i--) {
+ if (num_pages >= (static_cast<std::size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ static constexpr std::size_t GetBlockSize(std::size_t index) {
+ return static_cast<std::size_t>(1) << MemoryBlockPageShifts[index];
+ }
+
+ static constexpr std::size_t GetBlockNumPages(std::size_t index) {
+ return GetBlockSize(index) / PageSize;
+ }
+
+private:
+ static constexpr std::size_t NumMemoryBlockPageShifts{7};
+ static constexpr std::array<std::size_t, NumMemoryBlockPageShifts> MemoryBlockPageShifts{
+ 0xC, 0x10, 0x15, 0x16, 0x19, 0x1D, 0x1E,
+ };
+
+ class Block final : NonCopyable {
+ private:
+ class Bitmap final : NonCopyable {
+ public:
+ static constexpr std::size_t MaxDepth{4};
+
+ private:
+ std::array<u64*, MaxDepth> bit_storages{};
+ std::size_t num_bits{};
+ std::size_t used_depths{};
+
+ public:
+ constexpr Bitmap() = default;
+
+ constexpr std::size_t GetNumBits() const {
+ return num_bits;
+ }
+ constexpr s32 GetHighestDepthIndex() const {
+ return static_cast<s32>(used_depths) - 1;
+ }
+
+ constexpr u64* Initialize(u64* storage, std::size_t size) {
+ //* Initially, everything is un-set
+ num_bits = 0;
+
+ // Calculate the needed bitmap depth
+ used_depths = static_cast<std::size_t>(GetRequiredDepth(size));
+ ASSERT(used_depths <= MaxDepth);
+
+ // Set the bitmap pointers
+ for (s32 depth{GetHighestDepthIndex()}; depth >= 0; depth--) {
+ bit_storages[depth] = storage;
+ size = Common::AlignUp(size, 64) / 64;
+ storage += size;
+ }
+
+ return storage;
+ }
+
+ s64 FindFreeBlock() const {
+ uintptr_t offset{};
+ s32 depth{};
+
+ do {
+ const u64 v{bit_storages[depth][offset]};
+ if (v == 0) {
+ // Non-zero depth indicates that a previous level had a free block
+ ASSERT(depth == 0);
+ return -1;
+ }
+ offset = offset * 64 + Common::CountTrailingZeroes64(v);
+ ++depth;
+ } while (depth < static_cast<s32>(used_depths));
+
+ return static_cast<s64>(offset);
+ }
+
+ constexpr void SetBit(std::size_t offset) {
+ SetBit(GetHighestDepthIndex(), offset);
+ num_bits++;
+ }
+
+ constexpr void ClearBit(std::size_t offset) {
+ ClearBit(GetHighestDepthIndex(), offset);
+ num_bits--;
+ }
+
+ constexpr bool ClearRange(std::size_t offset, std::size_t count) {
+ const s32 depth{GetHighestDepthIndex()};
+ const auto bit_ind{offset / 64};
+ u64* bits{bit_storages[depth]};
+ if (count < 64) {
+ const auto shift{offset % 64};
+ ASSERT(shift + count <= 64);
+ // Check that all the bits are set
+ const u64 mask{((1ULL << count) - 1) << shift};
+ u64 v{bits[bit_ind]};
+ if ((v & mask) != mask) {
+ return false;
+ }
+
+ // Clear the bits
+ v &= ~mask;
+ bits[bit_ind] = v;
+ if (v == 0) {
+ ClearBit(depth - 1, bit_ind);
+ }
+ } else {
+ ASSERT(offset % 64 == 0);
+ ASSERT(count % 64 == 0);
+ // Check that all the bits are set
+ std::size_t remaining{count};
+ std::size_t i = 0;
+ do {
+ if (bits[bit_ind + i++] != ~u64(0)) {
+ return false;
+ }
+ remaining -= 64;
+ } while (remaining > 0);
+
+ // Clear the bits
+ remaining = count;
+ i = 0;
+ do {
+ bits[bit_ind + i] = 0;
+ ClearBit(depth - 1, bit_ind + i);
+ i++;
+ remaining -= 64;
+ } while (remaining > 0);
+ }
+
+ num_bits -= count;
+ return true;
+ }
+
+ private:
+ constexpr void SetBit(s32 depth, std::size_t offset) {
+ while (depth >= 0) {
+ const auto ind{offset / 64};
+ const auto which{offset % 64};
+ const u64 mask{1ULL << which};
+
+ u64* bit{std::addressof(bit_storages[depth][ind])};
+ const u64 v{*bit};
+ ASSERT((v & mask) == 0);
+ *bit = v | mask;
+ if (v) {
+ break;
+ }
+ offset = ind;
+ depth--;
+ }
+ }
+
+ constexpr void ClearBit(s32 depth, std::size_t offset) {
+ while (depth >= 0) {
+ const auto ind{offset / 64};
+ const auto which{offset % 64};
+ const u64 mask{1ULL << which};
+
+ u64* bit{std::addressof(bit_storages[depth][ind])};
+ u64 v{*bit};
+ ASSERT((v & mask) != 0);
+ v &= ~mask;
+ *bit = v;
+ if (v) {
+ break;
+ }
+ offset = ind;
+ depth--;
+ }
+ }
+
+ private:
+ static constexpr s32 GetRequiredDepth(std::size_t region_size) {
+ s32 depth = 0;
+ while (true) {
+ region_size /= 64;
+ depth++;
+ if (region_size == 0) {
+ return depth;
+ }
+ }
+ }
+
+ public:
+ static constexpr std::size_t CalculateMetadataOverheadSize(std::size_t region_size) {
+ std::size_t overhead_bits = 0;
+ for (s32 depth{GetRequiredDepth(region_size) - 1}; depth >= 0; depth--) {
+ region_size = Common::AlignUp(region_size, 64) / 64;
+ overhead_bits += region_size;
+ }
+ return overhead_bits * sizeof(u64);
+ }
+ };
+
+ private:
+ Bitmap bitmap;
+ VAddr heap_address{};
+ uintptr_t end_offset{};
+ std::size_t block_shift{};
+ std::size_t next_block_shift{};
+
+ public:
+ constexpr Block() = default;
+
+ constexpr std::size_t GetShift() const {
+ return block_shift;
+ }
+ constexpr std::size_t GetNextShift() const {
+ return next_block_shift;
+ }
+ constexpr std::size_t GetSize() const {
+ return static_cast<std::size_t>(1) << GetShift();
+ }
+ constexpr std::size_t GetNumPages() const {
+ return GetSize() / PageSize;
+ }
+ constexpr std::size_t GetNumFreeBlocks() const {
+ return bitmap.GetNumBits();
+ }
+ constexpr std::size_t GetNumFreePages() const {
+ return GetNumFreeBlocks() * GetNumPages();
+ }
+
+ constexpr u64* Initialize(VAddr addr, std::size_t size, std::size_t bs, std::size_t nbs,
+ u64* bit_storage) {
+ // Set shifts
+ block_shift = bs;
+ next_block_shift = nbs;
+
+ // Align up the address
+ VAddr end{addr + size};
+ const auto align{(next_block_shift != 0) ? (1ULL << next_block_shift)
+ : (1ULL << block_shift)};
+ addr = Common::AlignDown((addr), align);
+ end = Common::AlignUp((end), align);
+
+ heap_address = addr;
+ end_offset = (end - addr) / (1ULL << block_shift);
+ return bitmap.Initialize(bit_storage, end_offset);
+ }
+
+ constexpr VAddr PushBlock(VAddr address) {
+ // Set the bit for the free block
+ std::size_t offset{(address - heap_address) >> GetShift()};
+ bitmap.SetBit(offset);
+
+ // If we have a next shift, try to clear the blocks below and return the address
+ if (GetNextShift()) {
+ const auto diff{1ULL << (GetNextShift() - GetShift())};
+ offset = Common::AlignDown(offset, diff);
+ if (bitmap.ClearRange(offset, diff)) {
+ return heap_address + (offset << GetShift());
+ }
+ }
+
+ // We couldn't coalesce, or we're already as big as possible
+ return 0;
+ }
+
+ VAddr PopBlock() {
+ // Find a free block
+ const s64 soffset{bitmap.FindFreeBlock()};
+ if (soffset < 0) {
+ return 0;
+ }
+ const auto offset{static_cast<std::size_t>(soffset)};
+
+ // Update our tracking and return it
+ bitmap.ClearBit(offset);
+ return heap_address + (offset << GetShift());
+ }
+
+ public:
+ static constexpr std::size_t CalculateMetadataOverheadSize(std::size_t region_size,
+ std::size_t cur_block_shift,
+ std::size_t next_block_shift) {
+ const auto cur_block_size{(1ULL << cur_block_shift)};
+ const auto next_block_size{(1ULL << next_block_shift)};
+ const auto align{(next_block_shift != 0) ? next_block_size : cur_block_size};
+ return Bitmap::CalculateMetadataOverheadSize(
+ (align * 2 + Common::AlignUp(region_size, align)) / cur_block_size);
+ }
+ };
+
+public:
+ PageHeap() = default;
+
+ constexpr VAddr GetAddress() const {
+ return heap_address;
+ }
+ constexpr std::size_t GetSize() const {
+ return heap_size;
+ }
+ constexpr VAddr GetEndAddress() const {
+ return GetAddress() + GetSize();
+ }
+ constexpr std::size_t GetPageOffset(VAddr block) const {
+ return (block - GetAddress()) / PageSize;
+ }
+
+ void Initialize(VAddr heap_address, std::size_t heap_size, std::size_t metadata_size);
+ VAddr AllocateBlock(s32 index);
+ void Free(VAddr addr, std::size_t num_pages);
+
+ void UpdateUsedSize() {
+ used_size = heap_size - (GetNumFreePages() * PageSize);
+ }
+
+ static std::size_t CalculateMetadataOverheadSize(std::size_t region_size);
+
+private:
+ constexpr std::size_t GetNumFreePages() const {
+ std::size_t num_free{};
+
+ for (const auto& block : blocks) {
+ num_free += block.GetNumFreePages();
+ }
+
+ return num_free;
+ }
+
+ void FreeBlock(VAddr block, s32 index);
+
+ VAddr heap_address{};
+ std::size_t heap_size{};
+ std::size_t used_size{};
+ std::array<Block, NumMemoryBlockPageShifts> blocks{};
+ std::vector<u64> metadata;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/page_linked_list.h b/src/core/hle/kernel/memory/page_linked_list.h
new file mode 100644
index 000000000..45dc13eaf
--- /dev/null
+++ b/src/core/hle/kernel/memory/page_linked_list.h
@@ -0,0 +1,92 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <list>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/memory/memory_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel::Memory {
+
+class PageLinkedList final {
+public:
+ class Node final {
+ public:
+ constexpr Node(u64 addr, std::size_t num_pages) : addr{addr}, num_pages{num_pages} {}
+
+ constexpr u64 GetAddress() const {
+ return addr;
+ }
+
+ constexpr std::size_t GetNumPages() const {
+ return num_pages;
+ }
+
+ private:
+ u64 addr{};
+ std::size_t num_pages{};
+ };
+
+public:
+ PageLinkedList() = default;
+ PageLinkedList(u64 address, u64 num_pages) {
+ ASSERT(AddBlock(address, num_pages).IsSuccess());
+ }
+
+ constexpr std::list<Node>& Nodes() {
+ return nodes;
+ }
+
+ constexpr const std::list<Node>& Nodes() const {
+ return nodes;
+ }
+
+ std::size_t GetNumPages() const {
+ std::size_t num_pages = 0;
+ for (const Node& node : nodes) {
+ num_pages += node.GetNumPages();
+ }
+ return num_pages;
+ }
+
+ bool IsEqual(PageLinkedList& other) const {
+ auto this_node = nodes.begin();
+ auto other_node = other.nodes.begin();
+ while (this_node != nodes.end() && other_node != other.nodes.end()) {
+ if (this_node->GetAddress() != other_node->GetAddress() ||
+ this_node->GetNumPages() != other_node->GetNumPages()) {
+ return false;
+ }
+ this_node = std::next(this_node);
+ other_node = std::next(other_node);
+ }
+
+ return this_node == nodes.end() && other_node == other.nodes.end();
+ }
+
+ ResultCode AddBlock(u64 address, u64 num_pages) {
+ if (!num_pages) {
+ return RESULT_SUCCESS;
+ }
+ if (!nodes.empty()) {
+ const auto node = nodes.back();
+ if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
+ address = node.GetAddress();
+ num_pages += node.GetNumPages();
+ nodes.pop_back();
+ }
+ }
+ nodes.push_back({address, num_pages});
+ return RESULT_SUCCESS;
+ }
+
+private:
+ std::list<Node> nodes;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp
new file mode 100644
index 000000000..a3fadb533
--- /dev/null
+++ b/src/core/hle/kernel/memory/page_table.cpp
@@ -0,0 +1,1174 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/address_space_info.h"
+#include "core/hle/kernel/memory/memory_block.h"
+#include "core/hle/kernel/memory/memory_block_manager.h"
+#include "core/hle/kernel/memory/page_linked_list.h"
+#include "core/hle/kernel/memory/page_table.h"
+#include "core/hle/kernel/memory/system_control.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
+#include "core/memory.h"
+
+namespace Kernel::Memory {
+
+namespace {
+
+constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {
+ switch (as_type) {
+ case FileSys::ProgramAddressSpaceType::Is32Bit:
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
+ return 32;
+ case FileSys::ProgramAddressSpaceType::Is36Bit:
+ return 36;
+ case FileSys::ProgramAddressSpaceType::Is39Bit:
+ return 39;
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+constexpr u64 GetAddressInRange(const MemoryInfo& info, VAddr addr) {
+ if (info.GetAddress() < addr) {
+ return addr;
+ }
+ return info.GetAddress();
+}
+
+constexpr std::size_t GetSizeInRange(const MemoryInfo& info, VAddr start, VAddr end) {
+ std::size_t size{info.GetSize()};
+ if (info.GetAddress() < start) {
+ size -= start - info.GetAddress();
+ }
+ if (info.GetEndAddress() > end) {
+ size -= info.GetEndAddress() - end;
+ }
+ return size;
+}
+
+} // namespace
+
+PageTable::PageTable(Core::System& system) : system{system} {}
+
+ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type,
+ bool enable_aslr, VAddr code_addr, std::size_t code_size,
+ Memory::MemoryManager::Pool pool) {
+
+ const auto GetSpaceStart = [this](AddressSpaceInfo::Type type) {
+ return AddressSpaceInfo::GetAddressSpaceStart(address_space_width, type);
+ };
+ const auto GetSpaceSize = [this](AddressSpaceInfo::Type type) {
+ return AddressSpaceInfo::GetAddressSpaceSize(address_space_width, type);
+ };
+
+ // Set our width and heap/alias sizes
+ address_space_width = GetAddressSpaceWidthFromType(as_type);
+ const VAddr start = 0;
+ const VAddr end{1ULL << address_space_width};
+ std::size_t alias_region_size{GetSpaceSize(AddressSpaceInfo::Type::Alias)};
+ std::size_t heap_region_size{GetSpaceSize(AddressSpaceInfo::Type::Heap)};
+
+ ASSERT(start <= code_addr);
+ ASSERT(code_addr < code_addr + code_size);
+ ASSERT(code_addr + code_size - 1 <= end - 1);
+
+ // Adjust heap/alias size if we don't have an alias region
+ if (as_type == FileSys::ProgramAddressSpaceType::Is32BitNoMap) {
+ heap_region_size += alias_region_size;
+ alias_region_size = 0;
+ }
+
+ // Set code regions and determine remaining
+ constexpr std::size_t RegionAlignment{2 * 1024 * 1024};
+ VAddr process_code_start{};
+ VAddr process_code_end{};
+ std::size_t stack_region_size{};
+ std::size_t kernel_map_region_size{};
+
+ if (address_space_width == 39) {
+ alias_region_size = GetSpaceSize(AddressSpaceInfo::Type::Alias);
+ heap_region_size = GetSpaceSize(AddressSpaceInfo::Type::Heap);
+ stack_region_size = GetSpaceSize(AddressSpaceInfo::Type::Stack);
+ kernel_map_region_size = GetSpaceSize(AddressSpaceInfo::Type::Is32Bit);
+ code_region_start = GetSpaceStart(AddressSpaceInfo::Type::Large64Bit);
+ code_region_end = code_region_start + GetSpaceSize(AddressSpaceInfo::Type::Large64Bit);
+ alias_code_region_start = code_region_start;
+ alias_code_region_end = code_region_end;
+ process_code_start = Common::AlignDown(code_addr, RegionAlignment);
+ process_code_end = Common::AlignUp(code_addr + code_size, RegionAlignment);
+ } else {
+ stack_region_size = 0;
+ kernel_map_region_size = 0;
+ code_region_start = GetSpaceStart(AddressSpaceInfo::Type::Is32Bit);
+ code_region_end = code_region_start + GetSpaceSize(AddressSpaceInfo::Type::Is32Bit);
+ stack_region_start = code_region_start;
+ alias_code_region_start = code_region_start;
+ alias_code_region_end = GetSpaceStart(AddressSpaceInfo::Type::Small64Bit) +
+ GetSpaceSize(AddressSpaceInfo::Type::Small64Bit);
+ stack_region_end = code_region_end;
+ kernel_map_region_start = code_region_start;
+ kernel_map_region_end = code_region_end;
+ process_code_start = code_region_start;
+ process_code_end = code_region_end;
+ }
+
+ // Set other basic fields
+ is_aslr_enabled = enable_aslr;
+ address_space_start = start;
+ address_space_end = end;
+ is_kernel = false;
+
+ // Determine the region we can place our undetermineds in
+ VAddr alloc_start{};
+ std::size_t alloc_size{};
+ if ((process_code_start - code_region_start) >= (end - process_code_end)) {
+ alloc_start = code_region_start;
+ alloc_size = process_code_start - code_region_start;
+ } else {
+ alloc_start = process_code_end;
+ alloc_size = end - process_code_end;
+ }
+ const std::size_t needed_size{
+ (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)};
+ if (alloc_size < needed_size) {
+ UNREACHABLE();
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ const std::size_t remaining_size{alloc_size - needed_size};
+
+ // Determine random placements for each region
+ std::size_t alias_rnd{}, heap_rnd{}, stack_rnd{}, kmap_rnd{};
+ if (enable_aslr) {
+ alias_rnd = SystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ heap_rnd = SystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ stack_rnd = SystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ kmap_rnd = SystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ }
+
+ // Setup heap and alias regions
+ alias_region_start = alloc_start + alias_rnd;
+ alias_region_end = alias_region_start + alias_region_size;
+ heap_region_start = alloc_start + heap_rnd;
+ heap_region_end = heap_region_start + heap_region_size;
+
+ if (alias_rnd <= heap_rnd) {
+ heap_region_start += alias_region_size;
+ heap_region_end += alias_region_size;
+ } else {
+ alias_region_start += heap_region_size;
+ alias_region_end += heap_region_size;
+ }
+
+ // Setup stack region
+ if (stack_region_size) {
+ stack_region_start = alloc_start + stack_rnd;
+ stack_region_end = stack_region_start + stack_region_size;
+
+ if (alias_rnd < stack_rnd) {
+ stack_region_start += alias_region_size;
+ stack_region_end += alias_region_size;
+ } else {
+ alias_region_start += stack_region_size;
+ alias_region_end += stack_region_size;
+ }
+
+ if (heap_rnd < stack_rnd) {
+ stack_region_start += heap_region_size;
+ stack_region_end += heap_region_size;
+ } else {
+ heap_region_start += stack_region_size;
+ heap_region_end += stack_region_size;
+ }
+ }
+
+ // Setup kernel map region
+ if (kernel_map_region_size) {
+ kernel_map_region_start = alloc_start + kmap_rnd;
+ kernel_map_region_end = kernel_map_region_start + kernel_map_region_size;
+
+ if (alias_rnd < kmap_rnd) {
+ kernel_map_region_start += alias_region_size;
+ kernel_map_region_end += alias_region_size;
+ } else {
+ alias_region_start += kernel_map_region_size;
+ alias_region_end += kernel_map_region_size;
+ }
+
+ if (heap_rnd < kmap_rnd) {
+ kernel_map_region_start += heap_region_size;
+ kernel_map_region_end += heap_region_size;
+ } else {
+ heap_region_start += kernel_map_region_size;
+ heap_region_end += kernel_map_region_size;
+ }
+
+ if (stack_region_size) {
+ if (stack_rnd < kmap_rnd) {
+ kernel_map_region_start += stack_region_size;
+ kernel_map_region_end += stack_region_size;
+ } else {
+ stack_region_start += kernel_map_region_size;
+ stack_region_end += kernel_map_region_size;
+ }
+ }
+ }
+
+ // Set heap members
+ current_heap_end = heap_region_start;
+ max_heap_size = 0;
+ max_physical_memory_size = 0;
+
+ // Ensure that we regions inside our address space
+ auto IsInAddressSpace = [&](VAddr addr) {
+ return address_space_start <= addr && addr <= address_space_end;
+ };
+ ASSERT(IsInAddressSpace(alias_region_start));
+ ASSERT(IsInAddressSpace(alias_region_end));
+ ASSERT(IsInAddressSpace(heap_region_start));
+ ASSERT(IsInAddressSpace(heap_region_end));
+ ASSERT(IsInAddressSpace(stack_region_start));
+ ASSERT(IsInAddressSpace(stack_region_end));
+ ASSERT(IsInAddressSpace(kernel_map_region_start));
+ ASSERT(IsInAddressSpace(kernel_map_region_end));
+
+ // Ensure that we selected regions that don't overlap
+ const VAddr alias_start{alias_region_start};
+ const VAddr alias_last{alias_region_end - 1};
+ const VAddr heap_start{heap_region_start};
+ const VAddr heap_last{heap_region_end - 1};
+ const VAddr stack_start{stack_region_start};
+ const VAddr stack_last{stack_region_end - 1};
+ const VAddr kmap_start{kernel_map_region_start};
+ const VAddr kmap_last{kernel_map_region_end - 1};
+ ASSERT(alias_last < heap_start || heap_last < alias_start);
+ ASSERT(alias_last < stack_start || stack_last < alias_start);
+ ASSERT(alias_last < kmap_start || kmap_last < alias_start);
+ ASSERT(heap_last < stack_start || stack_last < heap_start);
+ ASSERT(heap_last < kmap_start || kmap_last < heap_start);
+
+ current_heap_addr = heap_region_start;
+ heap_capacity = 0;
+ physical_memory_usage = 0;
+ memory_pool = pool;
+
+ page_table_impl.Resize(address_space_width, PageBits, true);
+
+ return InitializeMemoryLayout(start, end);
+}
+
+ResultCode PageTable::MapProcessCode(VAddr addr, std::size_t num_pages, MemoryState state,
+ MemoryPermission perm) {
+ std::lock_guard lock{page_table_lock};
+
+ const u64 size{num_pages * PageSize};
+
+ if (!CanContain(addr, size, state)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (IsRegionMapped(addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ PageLinkedList page_linked_list;
+ CASCADE_CODE(
+ system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool));
+ CASCADE_CODE(Operate(addr, num_pages, page_linked_list, OperationType::MapGroup));
+
+ block_manager->Update(addr, num_pages, state, perm);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ const std::size_t num_pages{size / PageSize};
+
+ MemoryState state{};
+ MemoryPermission perm{};
+ CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, src_addr, size, MemoryState::All,
+ MemoryState::Normal, MemoryPermission::Mask,
+ MemoryPermission::ReadAndWrite, MemoryAttribute::Mask,
+ MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
+
+ if (IsRegionMapped(dst_addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ PageLinkedList page_linked_list;
+ AddRegionToPages(src_addr, num_pages, page_linked_list);
+
+ {
+ auto block_guard = detail::ScopeExit(
+ [&] { Operate(src_addr, num_pages, perm, OperationType::ChangePermissions); });
+
+ CASCADE_CODE(
+ Operate(src_addr, num_pages, MemoryPermission::None, OperationType::ChangePermissions));
+ CASCADE_CODE(MapPages(dst_addr, page_linked_list, MemoryPermission::None));
+
+ block_guard.Cancel();
+ }
+
+ block_manager->Update(src_addr, num_pages, state, MemoryPermission::None,
+ MemoryAttribute::Locked);
+ block_manager->Update(dst_addr, num_pages, MemoryState::AliasCode);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ if (!size) {
+ return RESULT_SUCCESS;
+ }
+
+ const std::size_t num_pages{size / PageSize};
+
+ CASCADE_CODE(CheckMemoryState(nullptr, nullptr, nullptr, src_addr, size, MemoryState::All,
+ MemoryState::Normal, MemoryPermission::None,
+ MemoryPermission::None, MemoryAttribute::Mask,
+ MemoryAttribute::Locked, MemoryAttribute::IpcAndDeviceMapped));
+
+ MemoryState state{};
+ CASCADE_CODE(CheckMemoryState(
+ &state, nullptr, nullptr, dst_addr, PageSize, MemoryState::FlagCanCodeAlias,
+ MemoryState::FlagCanCodeAlias, MemoryPermission::None, MemoryPermission::None,
+ MemoryAttribute::Mask, MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
+ CASCADE_CODE(CheckMemoryState(dst_addr, size, MemoryState::All, state, MemoryPermission::None,
+ MemoryPermission::None, MemoryAttribute::Mask,
+ MemoryAttribute::None));
+ CASCADE_CODE(Operate(dst_addr, num_pages, MemoryPermission::None, OperationType::Unmap));
+
+ block_manager->Update(dst_addr, num_pages, MemoryState::Free);
+ block_manager->Update(src_addr, num_pages, MemoryState::Normal, MemoryPermission::ReadAndWrite);
+
+ return RESULT_SUCCESS;
+}
+
+void PageTable::MapPhysicalMemory(PageLinkedList& page_linked_list, VAddr start, VAddr end) {
+ auto node{page_linked_list.Nodes().begin()};
+ PAddr map_addr{node->GetAddress()};
+ std::size_t src_num_pages{node->GetNumPages()};
+
+ block_manager->IterateForRange(start, end, [&](const MemoryInfo& info) {
+ if (info.state != MemoryState::Free) {
+ return;
+ }
+
+ std::size_t dst_num_pages{GetSizeInRange(info, start, end) / PageSize};
+ VAddr dst_addr{GetAddressInRange(info, start)};
+
+ while (dst_num_pages) {
+ if (!src_num_pages) {
+ node = std::next(node);
+ map_addr = node->GetAddress();
+ src_num_pages = node->GetNumPages();
+ }
+
+ const std::size_t num_pages{std::min(src_num_pages, dst_num_pages)};
+ Operate(dst_addr, num_pages, MemoryPermission::ReadAndWrite, OperationType::Map,
+ map_addr);
+
+ dst_addr += num_pages * PageSize;
+ map_addr += num_pages * PageSize;
+ src_num_pages -= num_pages;
+ dst_num_pages -= num_pages;
+ }
+ });
+}
+
+ResultCode PageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ std::size_t mapped_size{};
+ const VAddr end_addr{addr + size};
+
+ block_manager->IterateForRange(addr, end_addr, [&](const MemoryInfo& info) {
+ if (info.state != MemoryState::Free) {
+ mapped_size += GetSizeInRange(info, addr, end_addr);
+ }
+ });
+
+ if (mapped_size == size) {
+ return RESULT_SUCCESS;
+ }
+
+ auto process{system.Kernel().CurrentProcess()};
+ const std::size_t remaining_size{size - mapped_size};
+ const std::size_t remaining_pages{remaining_size / PageSize};
+
+ if (process->GetResourceLimit() &&
+ !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, remaining_size)) {
+ return ERR_RESOURCE_LIMIT_EXCEEDED;
+ }
+
+ PageLinkedList page_linked_list;
+ {
+ auto block_guard = detail::ScopeExit([&] {
+ system.Kernel().MemoryManager().Free(page_linked_list, remaining_pages, memory_pool);
+ process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, remaining_size);
+ });
+
+ CASCADE_CODE(system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages,
+ memory_pool));
+
+ block_guard.Cancel();
+ }
+
+ MapPhysicalMemory(page_linked_list, addr, end_addr);
+
+ physical_memory_usage += remaining_size;
+
+ const std::size_t num_pages{size / PageSize};
+ block_manager->Update(addr, num_pages, MemoryState::Free, MemoryPermission::None,
+ MemoryAttribute::None, MemoryState::Normal,
+ MemoryPermission::ReadAndWrite, MemoryAttribute::None);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ const VAddr end_addr{addr + size};
+ ResultCode result{RESULT_SUCCESS};
+ std::size_t mapped_size{};
+
+ // Verify that the region can be unmapped
+ block_manager->IterateForRange(addr, end_addr, [&](const MemoryInfo& info) {
+ if (info.state == MemoryState::Normal) {
+ if (info.attribute != MemoryAttribute::None) {
+ result = ERR_INVALID_ADDRESS_STATE;
+ return;
+ }
+ mapped_size += GetSizeInRange(info, addr, end_addr);
+ } else if (info.state != MemoryState::Free) {
+ result = ERR_INVALID_ADDRESS_STATE;
+ }
+ });
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (!mapped_size) {
+ return RESULT_SUCCESS;
+ }
+
+ CASCADE_CODE(UnmapMemory(addr, size));
+
+ auto process{system.Kernel().CurrentProcess()};
+ process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, mapped_size);
+ physical_memory_usage -= mapped_size;
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::UnmapMemory(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ const VAddr end_addr{addr + size};
+ ResultCode result{RESULT_SUCCESS};
+ PageLinkedList page_linked_list;
+
+ // Unmap each region within the range
+ block_manager->IterateForRange(addr, end_addr, [&](const MemoryInfo& info) {
+ if (info.state == MemoryState::Normal) {
+ const std::size_t block_size{GetSizeInRange(info, addr, end_addr)};
+ const std::size_t block_num_pages{block_size / PageSize};
+ const VAddr block_addr{GetAddressInRange(info, addr)};
+
+ AddRegionToPages(block_addr, block_size / PageSize, page_linked_list);
+
+ if (result = Operate(block_addr, block_num_pages, MemoryPermission::None,
+ OperationType::Unmap);
+ result.IsError()) {
+ return;
+ }
+ }
+ });
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ const std::size_t num_pages{size / PageSize};
+ system.Kernel().MemoryManager().Free(page_linked_list, num_pages, memory_pool);
+
+ block_manager->Update(addr, num_pages, MemoryState::Free);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState src_state{};
+ CASCADE_CODE(CheckMemoryState(
+ &src_state, nullptr, nullptr, src_addr, size, MemoryState::FlagCanAlias,
+ MemoryState::FlagCanAlias, MemoryPermission::Mask, MemoryPermission::ReadAndWrite,
+ MemoryAttribute::Mask, MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
+
+ if (IsRegionMapped(dst_addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ PageLinkedList page_linked_list;
+ const std::size_t num_pages{size / PageSize};
+
+ AddRegionToPages(src_addr, num_pages, page_linked_list);
+
+ {
+ auto block_guard = detail::ScopeExit([&] {
+ Operate(src_addr, num_pages, MemoryPermission::ReadAndWrite,
+ OperationType::ChangePermissions);
+ });
+
+ CASCADE_CODE(
+ Operate(src_addr, num_pages, MemoryPermission::None, OperationType::ChangePermissions));
+ CASCADE_CODE(MapPages(dst_addr, page_linked_list, MemoryPermission::ReadAndWrite));
+
+ block_guard.Cancel();
+ }
+
+ block_manager->Update(src_addr, num_pages, src_state, MemoryPermission::None,
+ MemoryAttribute::Locked);
+ block_manager->Update(dst_addr, num_pages, MemoryState::Stack, MemoryPermission::ReadAndWrite);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState src_state{};
+ CASCADE_CODE(CheckMemoryState(
+ &src_state, nullptr, nullptr, src_addr, size, MemoryState::FlagCanAlias,
+ MemoryState::FlagCanAlias, MemoryPermission::Mask, MemoryPermission::None,
+ MemoryAttribute::Mask, MemoryAttribute::Locked, MemoryAttribute::IpcAndDeviceMapped));
+
+ MemoryPermission dst_perm{};
+ CASCADE_CODE(CheckMemoryState(nullptr, &dst_perm, nullptr, dst_addr, size, MemoryState::All,
+ MemoryState::Stack, MemoryPermission::None,
+ MemoryPermission::None, MemoryAttribute::Mask,
+ MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
+
+ PageLinkedList src_pages;
+ PageLinkedList dst_pages;
+ const std::size_t num_pages{size / PageSize};
+
+ AddRegionToPages(src_addr, num_pages, src_pages);
+ AddRegionToPages(dst_addr, num_pages, dst_pages);
+
+ if (!dst_pages.IsEqual(src_pages)) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ {
+ auto block_guard = detail::ScopeExit([&] { MapPages(dst_addr, dst_pages, dst_perm); });
+
+ CASCADE_CODE(Operate(dst_addr, num_pages, MemoryPermission::None, OperationType::Unmap));
+ CASCADE_CODE(Operate(src_addr, num_pages, MemoryPermission::ReadAndWrite,
+ OperationType::ChangePermissions));
+
+ block_guard.Cancel();
+ }
+
+ block_manager->Update(src_addr, num_pages, src_state, MemoryPermission::ReadAndWrite);
+ block_manager->Update(dst_addr, num_pages, MemoryState::Free);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::MapPages(VAddr addr, const PageLinkedList& page_linked_list,
+ MemoryPermission perm) {
+ VAddr cur_addr{addr};
+
+ for (const auto& node : page_linked_list.Nodes()) {
+ if (const auto result{
+ Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())};
+ result.IsError()) {
+ const std::size_t num_pages{(addr - cur_addr) / PageSize};
+
+ ASSERT(
+ Operate(addr, num_pages, MemoryPermission::None, OperationType::Unmap).IsSuccess());
+
+ return result;
+ }
+
+ cur_addr += node.GetNumPages() * PageSize;
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::MapPages(VAddr addr, PageLinkedList& page_linked_list, MemoryState state,
+ MemoryPermission perm) {
+ std::lock_guard lock{page_table_lock};
+
+ const std::size_t num_pages{page_linked_list.GetNumPages()};
+ const std::size_t size{num_pages * PageSize};
+
+ if (!CanContain(addr, size, state)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (IsRegionMapped(addr, num_pages * PageSize)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ CASCADE_CODE(MapPages(addr, page_linked_list, perm));
+
+ block_manager->Update(addr, num_pages, state, perm);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size, MemoryPermission perm) {
+
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState prev_state{};
+ MemoryPermission prev_perm{};
+
+ CASCADE_CODE(CheckMemoryState(
+ &prev_state, &prev_perm, nullptr, addr, size, MemoryState::FlagCode, MemoryState::FlagCode,
+ MemoryPermission::None, MemoryPermission::None, MemoryAttribute::Mask,
+ MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
+
+ MemoryState state{prev_state};
+
+ // Ensure state is mutable if permission allows write
+ if ((perm & MemoryPermission::Write) != MemoryPermission::None) {
+ if (prev_state == MemoryState::Code) {
+ state = MemoryState::CodeData;
+ } else if (prev_state == MemoryState::AliasCode) {
+ state = MemoryState::AliasCodeData;
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ // Return early if there is nothing to change
+ if (state == prev_state && perm == prev_perm) {
+ return RESULT_SUCCESS;
+ }
+
+ const std::size_t num_pages{size / PageSize};
+ const OperationType operation{(perm & MemoryPermission::Execute) != MemoryPermission::None
+ ? OperationType::ChangePermissionsAndRefresh
+ : OperationType::ChangePermissions};
+
+ CASCADE_CODE(Operate(addr, num_pages, perm, operation));
+
+ block_manager->Update(addr, num_pages, state, perm);
+
+ return RESULT_SUCCESS;
+}
+
+MemoryInfo PageTable::QueryInfoImpl(VAddr addr) {
+ std::lock_guard lock{page_table_lock};
+
+ return block_manager->FindBlock(addr).GetMemoryInfo();
+}
+
+MemoryInfo PageTable::QueryInfo(VAddr addr) {
+ if (!Contains(addr, 1)) {
+ return {address_space_end, 0 - address_space_end, MemoryState::Inaccessible,
+ MemoryPermission::None, MemoryAttribute::None, MemoryPermission::None};
+ }
+
+ return QueryInfoImpl(addr);
+}
+
+ResultCode PageTable::ReserveTransferMemory(VAddr addr, std::size_t size, MemoryPermission perm) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState state{};
+ MemoryAttribute attribute{};
+
+ CASCADE_CODE(CheckMemoryState(&state, nullptr, &attribute, addr, size,
+ MemoryState::FlagCanTransfer | MemoryState::FlagReferenceCounted,
+ MemoryState::FlagCanTransfer | MemoryState::FlagReferenceCounted,
+ MemoryPermission::Mask, MemoryPermission::ReadAndWrite,
+ MemoryAttribute::Mask, MemoryAttribute::None,
+ MemoryAttribute::IpcAndDeviceMapped));
+
+ block_manager->Update(addr, size / PageSize, state, perm, attribute | MemoryAttribute::Locked);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::ResetTransferMemory(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState state{};
+
+ CASCADE_CODE(CheckMemoryState(&state, nullptr, nullptr, addr, size,
+ MemoryState::FlagCanTransfer | MemoryState::FlagReferenceCounted,
+ MemoryState::FlagCanTransfer | MemoryState::FlagReferenceCounted,
+ MemoryPermission::None, MemoryPermission::None,
+ MemoryAttribute::Mask, MemoryAttribute::Locked,
+ MemoryAttribute::IpcAndDeviceMapped));
+
+ block_manager->Update(addr, size / PageSize, state, MemoryPermission::ReadAndWrite);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::SetMemoryAttribute(VAddr addr, std::size_t size, MemoryAttribute mask,
+ MemoryAttribute value) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryState state{};
+ MemoryPermission perm{};
+ MemoryAttribute attribute{};
+
+ CASCADE_CODE(CheckMemoryState(&state, &perm, &attribute, addr, size,
+ MemoryState::FlagCanChangeAttribute,
+ MemoryState::FlagCanChangeAttribute, MemoryPermission::None,
+ MemoryPermission::None, MemoryAttribute::LockedAndIpcLocked,
+ MemoryAttribute::None, MemoryAttribute::DeviceSharedAndUncached));
+
+ attribute = attribute & ~mask;
+ attribute = attribute | (mask & value);
+
+ block_manager->Update(addr, size / PageSize, state, perm, attribute);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::SetHeapCapacity(std::size_t new_heap_capacity) {
+ std::lock_guard lock{page_table_lock};
+ heap_capacity = new_heap_capacity;
+ return RESULT_SUCCESS;
+}
+
+ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) {
+
+ if (size > heap_region_end - heap_region_start) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ const u64 previous_heap_size{GetHeapSize()};
+
+ UNIMPLEMENTED_IF_MSG(previous_heap_size > size, "Heap shrink is unimplemented");
+
+ // Increase the heap size
+ {
+ std::lock_guard lock{page_table_lock};
+
+ const u64 delta{size - previous_heap_size};
+
+ auto process{system.Kernel().CurrentProcess()};
+ if (process->GetResourceLimit() && delta != 0 &&
+ !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, delta)) {
+ return ERR_RESOURCE_LIMIT_EXCEEDED;
+ }
+
+ PageLinkedList page_linked_list;
+ const std::size_t num_pages{delta / PageSize};
+
+ CASCADE_CODE(
+ system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool));
+
+ if (IsRegionMapped(current_heap_addr, delta)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ CASCADE_CODE(
+ Operate(current_heap_addr, num_pages, page_linked_list, OperationType::MapGroup));
+
+ block_manager->Update(current_heap_addr, num_pages, MemoryState::Normal,
+ MemoryPermission::ReadAndWrite);
+
+ current_heap_addr = heap_region_start + size;
+ }
+
+ return MakeResult<VAddr>(heap_region_start);
+}
+
+ResultVal<VAddr> PageTable::AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align,
+ bool is_map_only, VAddr region_start,
+ std::size_t region_num_pages, MemoryState state,
+ MemoryPermission perm, PAddr map_addr) {
+ std::lock_guard lock{page_table_lock};
+
+ if (!CanContain(region_start, region_num_pages * PageSize, state)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (region_num_pages <= needed_num_pages) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ const VAddr addr{
+ AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)};
+ if (!addr) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ if (is_map_only) {
+ CASCADE_CODE(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
+ } else {
+ PageLinkedList page_group;
+ CASCADE_CODE(
+ system.Kernel().MemoryManager().Allocate(page_group, needed_num_pages, memory_pool));
+ CASCADE_CODE(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
+ }
+
+ block_manager->Update(addr, needed_num_pages, state, perm);
+
+ return MakeResult<VAddr>(addr);
+}
+
+ResultCode PageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryPermission perm{};
+ if (const ResultCode result{CheckMemoryState(
+ nullptr, &perm, nullptr, addr, size, MemoryState::FlagCanChangeAttribute,
+ MemoryState::FlagCanChangeAttribute, MemoryPermission::None, MemoryPermission::None,
+ MemoryAttribute::LockedAndIpcLocked, MemoryAttribute::None,
+ MemoryAttribute::DeviceSharedAndUncached)};
+ result.IsError()) {
+ return result;
+ }
+
+ block_manager->UpdateLock(
+ addr, size / PageSize,
+ [](MemoryBlockManager::iterator block, MemoryPermission perm) {
+ block->ShareToDevice(perm);
+ },
+ perm);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) {
+ std::lock_guard lock{page_table_lock};
+
+ MemoryPermission perm{};
+ if (const ResultCode result{CheckMemoryState(
+ nullptr, &perm, nullptr, addr, size, MemoryState::FlagCanChangeAttribute,
+ MemoryState::FlagCanChangeAttribute, MemoryPermission::None, MemoryPermission::None,
+ MemoryAttribute::LockedAndIpcLocked, MemoryAttribute::None,
+ MemoryAttribute::DeviceSharedAndUncached)};
+ result.IsError()) {
+ return result;
+ }
+
+ block_manager->UpdateLock(
+ addr, size / PageSize,
+ [](MemoryBlockManager::iterator block, MemoryPermission perm) {
+ block->UnshareToDevice(perm);
+ },
+ perm);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
+ block_manager = std::make_unique<MemoryBlockManager>(start, end);
+
+ return RESULT_SUCCESS;
+}
+
+bool PageTable::IsRegionMapped(VAddr address, u64 size) {
+ return CheckMemoryState(address, size, MemoryState::All, MemoryState::Free,
+ MemoryPermission::Mask, MemoryPermission::None, MemoryAttribute::Mask,
+ MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped)
+ .IsError();
+}
+
+bool PageTable::IsRegionContiguous(VAddr addr, u64 size) const {
+ auto start_ptr = system.Memory().GetPointer(addr);
+ for (u64 offset{}; offset < size; offset += PageSize) {
+ if (start_ptr != system.Memory().GetPointer(addr + offset)) {
+ return false;
+ }
+ start_ptr += PageSize;
+ }
+ return true;
+}
+
+void PageTable::AddRegionToPages(VAddr start, std::size_t num_pages,
+ PageLinkedList& page_linked_list) {
+ VAddr addr{start};
+ while (addr < start + (num_pages * PageSize)) {
+ const PAddr paddr{GetPhysicalAddr(addr)};
+ if (!paddr) {
+ UNREACHABLE();
+ }
+ page_linked_list.AddBlock(paddr, 1);
+ addr += PageSize;
+ }
+}
+
+VAddr PageTable::AllocateVirtualMemory(VAddr start, std::size_t region_num_pages,
+ u64 needed_num_pages, std::size_t align) {
+ if (is_aslr_enabled) {
+ UNIMPLEMENTED();
+ }
+ return block_manager->FindFreeArea(start, region_num_pages, needed_num_pages, align, 0,
+ IsKernel() ? 1 : 4);
+}
+
+ResultCode PageTable::Operate(VAddr addr, std::size_t num_pages, const PageLinkedList& page_group,
+ OperationType operation) {
+ std::lock_guard lock{page_table_lock};
+
+ ASSERT(Common::IsAligned(addr, PageSize));
+ ASSERT(num_pages > 0);
+ ASSERT(num_pages == page_group.GetNumPages());
+
+ for (const auto& node : page_group.Nodes()) {
+ const std::size_t size{node.GetNumPages() * PageSize};
+
+ switch (operation) {
+ case OperationType::MapGroup:
+ system.Memory().MapMemoryRegion(page_table_impl, addr, size, node.GetAddress());
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ addr += size;
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::Operate(VAddr addr, std::size_t num_pages, MemoryPermission perm,
+ OperationType operation, PAddr map_addr) {
+ std::lock_guard lock{page_table_lock};
+
+ ASSERT(num_pages > 0);
+ ASSERT(Common::IsAligned(addr, PageSize));
+ ASSERT(ContainsPages(addr, num_pages));
+
+ switch (operation) {
+ case OperationType::Unmap:
+ system.Memory().UnmapRegion(page_table_impl, addr, num_pages * PageSize);
+ break;
+ case OperationType::Map: {
+ ASSERT(map_addr);
+ ASSERT(Common::IsAligned(map_addr, PageSize));
+ system.Memory().MapMemoryRegion(page_table_impl, addr, num_pages * PageSize, map_addr);
+ break;
+ }
+ case OperationType::ChangePermissions:
+ case OperationType::ChangePermissionsAndRefresh:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return RESULT_SUCCESS;
+}
+
+constexpr VAddr PageTable::GetRegionAddress(MemoryState state) const {
+ switch (state) {
+ case MemoryState::Free:
+ case MemoryState::Kernel:
+ return address_space_start;
+ case MemoryState::Normal:
+ return heap_region_start;
+ case MemoryState::Ipc:
+ case MemoryState::NonSecureIpc:
+ case MemoryState::NonDeviceIpc:
+ return alias_region_start;
+ case MemoryState::Stack:
+ return stack_region_start;
+ case MemoryState::Io:
+ case MemoryState::Static:
+ case MemoryState::ThreadLocal:
+ return kernel_map_region_start;
+ case MemoryState::Shared:
+ case MemoryState::AliasCode:
+ case MemoryState::AliasCodeData:
+ case MemoryState::Transfered:
+ case MemoryState::SharedTransfered:
+ case MemoryState::SharedCode:
+ case MemoryState::GeneratedCode:
+ case MemoryState::CodeOut:
+ return alias_code_region_start;
+ case MemoryState::Code:
+ case MemoryState::CodeData:
+ return code_region_start;
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+constexpr std::size_t PageTable::GetRegionSize(MemoryState state) const {
+ switch (state) {
+ case MemoryState::Free:
+ case MemoryState::Kernel:
+ return address_space_end - address_space_start;
+ case MemoryState::Normal:
+ return heap_region_end - heap_region_start;
+ case MemoryState::Ipc:
+ case MemoryState::NonSecureIpc:
+ case MemoryState::NonDeviceIpc:
+ return alias_region_end - alias_region_start;
+ case MemoryState::Stack:
+ return stack_region_end - stack_region_start;
+ case MemoryState::Io:
+ case MemoryState::Static:
+ case MemoryState::ThreadLocal:
+ return kernel_map_region_end - kernel_map_region_start;
+ case MemoryState::Shared:
+ case MemoryState::AliasCode:
+ case MemoryState::AliasCodeData:
+ case MemoryState::Transfered:
+ case MemoryState::SharedTransfered:
+ case MemoryState::SharedCode:
+ case MemoryState::GeneratedCode:
+ case MemoryState::CodeOut:
+ return alias_code_region_end - alias_code_region_start;
+ case MemoryState::Code:
+ case MemoryState::CodeData:
+ return code_region_end - code_region_start;
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+constexpr bool PageTable::CanContain(VAddr addr, std::size_t size, MemoryState state) const {
+ const VAddr end{addr + size};
+ const VAddr last{end - 1};
+ const VAddr region_start{GetRegionAddress(state)};
+ const std::size_t region_size{GetRegionSize(state)};
+ const bool is_in_region{region_start <= addr && addr < end &&
+ last <= region_start + region_size - 1};
+ const bool is_in_heap{!(end <= heap_region_start || heap_region_end <= addr)};
+ const bool is_in_alias{!(end <= alias_region_start || alias_region_end <= addr)};
+
+ switch (state) {
+ case MemoryState::Free:
+ case MemoryState::Kernel:
+ return is_in_region;
+ case MemoryState::Io:
+ case MemoryState::Static:
+ case MemoryState::Code:
+ case MemoryState::CodeData:
+ case MemoryState::Shared:
+ case MemoryState::AliasCode:
+ case MemoryState::AliasCodeData:
+ case MemoryState::Stack:
+ case MemoryState::ThreadLocal:
+ case MemoryState::Transfered:
+ case MemoryState::SharedTransfered:
+ case MemoryState::SharedCode:
+ case MemoryState::GeneratedCode:
+ case MemoryState::CodeOut:
+ return is_in_region && !is_in_heap && !is_in_alias;
+ case MemoryState::Normal:
+ ASSERT(is_in_heap);
+ return is_in_region && !is_in_alias;
+ case MemoryState::Ipc:
+ case MemoryState::NonSecureIpc:
+ case MemoryState::NonDeviceIpc:
+ ASSERT(is_in_alias);
+ return is_in_region && !is_in_heap;
+ default:
+ return false;
+ }
+}
+
+constexpr ResultCode PageTable::CheckMemoryState(const MemoryInfo& info, MemoryState state_mask,
+ MemoryState state, MemoryPermission perm_mask,
+ MemoryPermission perm, MemoryAttribute attr_mask,
+ MemoryAttribute attr) const {
+ // Validate the states match expectation
+ if ((info.state & state_mask) != state) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ if ((info.perm & perm_mask) != perm) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ if ((info.attribute & attr_mask) != attr) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode PageTable::CheckMemoryState(MemoryState* out_state, MemoryPermission* out_perm,
+ MemoryAttribute* out_attr, VAddr addr, std::size_t size,
+ MemoryState state_mask, MemoryState state,
+ MemoryPermission perm_mask, MemoryPermission perm,
+ MemoryAttribute attr_mask, MemoryAttribute attr,
+ MemoryAttribute ignore_attr) {
+ std::lock_guard lock{page_table_lock};
+
+ // Get information about the first block
+ const VAddr last_addr{addr + size - 1};
+ MemoryBlockManager::const_iterator it{block_manager->FindIterator(addr)};
+ MemoryInfo info{it->GetMemoryInfo()};
+
+ // Validate all blocks in the range have correct state
+ const MemoryState first_state{info.state};
+ const MemoryPermission first_perm{info.perm};
+ const MemoryAttribute first_attr{info.attribute};
+
+ while (true) {
+ // Validate the current block
+ if (!(info.state == first_state)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ if (!(info.perm == first_perm)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ if (!((info.attribute | static_cast<MemoryAttribute>(ignore_attr)) ==
+ (first_attr | static_cast<MemoryAttribute>(ignore_attr)))) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ // Validate against the provided masks
+ CASCADE_CODE(CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
+
+ // Break once we're done
+ if (last_addr <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance our iterator
+ it++;
+ ASSERT(it != block_manager->cend());
+ info = it->GetMemoryInfo();
+ }
+
+ // Write output state
+ if (out_state) {
+ *out_state = first_state;
+ }
+ if (out_perm) {
+ *out_perm = first_perm;
+ }
+ if (out_attr) {
+ *out_attr = first_attr & static_cast<MemoryAttribute>(~ignore_attr);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/page_table.h b/src/core/hle/kernel/memory/page_table.h
new file mode 100644
index 000000000..ce0d38849
--- /dev/null
+++ b/src/core/hle/kernel/memory/page_table.h
@@ -0,0 +1,277 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "common/page_table.h"
+#include "core/file_sys/program_metadata.h"
+#include "core/hle/kernel/memory/memory_block.h"
+#include "core/hle/kernel/memory/memory_manager.h"
+#include "core/hle/result.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel::Memory {
+
+class MemoryBlockManager;
+
+class PageTable final : NonCopyable {
+public:
+ explicit PageTable(Core::System& system);
+
+ ResultCode InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
+ VAddr code_addr, std::size_t code_size,
+ Memory::MemoryManager::Pool pool);
+ ResultCode MapProcessCode(VAddr addr, std::size_t pages_count, MemoryState state,
+ MemoryPermission perm);
+ ResultCode MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ ResultCode UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
+ ResultCode UnmapPhysicalMemory(VAddr addr, std::size_t size);
+ ResultCode UnmapMemory(VAddr addr, std::size_t size);
+ ResultCode Map(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ ResultCode Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size);
+ ResultCode MapPages(VAddr addr, PageLinkedList& page_linked_list, MemoryState state,
+ MemoryPermission perm);
+ ResultCode SetCodeMemoryPermission(VAddr addr, std::size_t size, MemoryPermission perm);
+ MemoryInfo QueryInfo(VAddr addr);
+ ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, MemoryPermission perm);
+ ResultCode ResetTransferMemory(VAddr addr, std::size_t size);
+ ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, MemoryAttribute mask,
+ MemoryAttribute value);
+ ResultCode SetHeapCapacity(std::size_t new_heap_capacity);
+ ResultVal<VAddr> SetHeapSize(std::size_t size);
+ ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align,
+ bool is_map_only, VAddr region_start,
+ std::size_t region_num_pages, MemoryState state,
+ MemoryPermission perm, PAddr map_addr = 0);
+ ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
+ ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
+
+ Common::PageTable& PageTableImpl() {
+ return page_table_impl;
+ }
+
+ const Common::PageTable& PageTableImpl() const {
+ return page_table_impl;
+ }
+
+private:
+ enum class OperationType : u32 {
+ Map,
+ MapGroup,
+ Unmap,
+ ChangePermissions,
+ ChangePermissionsAndRefresh,
+ };
+
+ static constexpr MemoryAttribute DefaultMemoryIgnoreAttr =
+ MemoryAttribute::DontCareMask | MemoryAttribute::IpcLocked | MemoryAttribute::DeviceShared;
+
+ ResultCode InitializeMemoryLayout(VAddr start, VAddr end);
+ ResultCode MapPages(VAddr addr, const PageLinkedList& page_linked_list, MemoryPermission perm);
+ void MapPhysicalMemory(PageLinkedList& page_linked_list, VAddr start, VAddr end);
+ bool IsRegionMapped(VAddr address, u64 size);
+ bool IsRegionContiguous(VAddr addr, u64 size) const;
+ void AddRegionToPages(VAddr start, std::size_t num_pages, PageLinkedList& page_linked_list);
+ MemoryInfo QueryInfoImpl(VAddr addr);
+ VAddr AllocateVirtualMemory(VAddr start, std::size_t region_num_pages, u64 needed_num_pages,
+ std::size_t align);
+ ResultCode Operate(VAddr addr, std::size_t num_pages, const PageLinkedList& page_group,
+ OperationType operation);
+ ResultCode Operate(VAddr addr, std::size_t num_pages, MemoryPermission perm,
+ OperationType operation, PAddr map_addr = 0);
+ constexpr VAddr GetRegionAddress(MemoryState state) const;
+ constexpr std::size_t GetRegionSize(MemoryState state) const;
+ constexpr bool CanContain(VAddr addr, std::size_t size, MemoryState state) const;
+
+ constexpr ResultCode CheckMemoryState(const MemoryInfo& info, MemoryState state_mask,
+ MemoryState state, MemoryPermission perm_mask,
+ MemoryPermission perm, MemoryAttribute attr_mask,
+ MemoryAttribute attr) const;
+ ResultCode CheckMemoryState(MemoryState* out_state, MemoryPermission* out_perm,
+ MemoryAttribute* out_attr, VAddr addr, std::size_t size,
+ MemoryState state_mask, MemoryState state,
+ MemoryPermission perm_mask, MemoryPermission perm,
+ MemoryAttribute attr_mask, MemoryAttribute attr,
+ MemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr);
+ ResultCode CheckMemoryState(VAddr addr, std::size_t size, MemoryState state_mask,
+ MemoryState state, MemoryPermission perm_mask,
+ MemoryPermission perm, MemoryAttribute attr_mask,
+ MemoryAttribute attr,
+ MemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) {
+ return CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask,
+ perm, attr_mask, attr, ignore_attr);
+ }
+
+ std::recursive_mutex page_table_lock;
+ std::unique_ptr<MemoryBlockManager> block_manager;
+
+public:
+ constexpr VAddr GetAddressSpaceStart() const {
+ return address_space_start;
+ }
+ constexpr VAddr GetAddressSpaceEnd() const {
+ return address_space_end;
+ }
+ constexpr std::size_t GetAddressSpaceSize() const {
+ return address_space_end - address_space_start;
+ }
+ constexpr VAddr GetHeapRegionStart() const {
+ return heap_region_start;
+ }
+ constexpr VAddr GetHeapRegionEnd() const {
+ return heap_region_end;
+ }
+ constexpr std::size_t GetHeapRegionSize() const {
+ return heap_region_end - heap_region_start;
+ }
+ constexpr VAddr GetAliasRegionStart() const {
+ return alias_region_start;
+ }
+ constexpr VAddr GetAliasRegionEnd() const {
+ return alias_region_end;
+ }
+ constexpr std::size_t GetAliasRegionSize() const {
+ return alias_region_end - alias_region_start;
+ }
+ constexpr VAddr GetStackRegionStart() const {
+ return stack_region_start;
+ }
+ constexpr VAddr GetStackRegionEnd() const {
+ return stack_region_end;
+ }
+ constexpr std::size_t GetStackRegionSize() const {
+ return stack_region_end - stack_region_start;
+ }
+ constexpr VAddr GetKernelMapRegionStart() const {
+ return kernel_map_region_start;
+ }
+ constexpr VAddr GetKernelMapRegionEnd() const {
+ return kernel_map_region_end;
+ }
+ constexpr VAddr GetCodeRegionStart() const {
+ return code_region_start;
+ }
+ constexpr VAddr GetCodeRegionEnd() const {
+ return code_region_end;
+ }
+ constexpr VAddr GetAliasCodeRegionStart() const {
+ return alias_code_region_start;
+ }
+ constexpr VAddr GetAliasCodeRegionSize() const {
+ return alias_code_region_end - alias_code_region_start;
+ }
+ constexpr std::size_t GetAddressSpaceWidth() const {
+ return address_space_width;
+ }
+ constexpr std::size_t GetHeapSize() {
+ return current_heap_addr - heap_region_start;
+ }
+ constexpr std::size_t GetTotalHeapSize() {
+ return GetHeapSize() + physical_memory_usage;
+ }
+ constexpr bool IsInsideAddressSpace(VAddr address, std::size_t size) const {
+ return address_space_start <= address && address + size - 1 <= address_space_end - 1;
+ }
+ constexpr bool IsOutsideAliasRegion(VAddr address, std::size_t size) const {
+ return alias_region_start > address || address + size - 1 > alias_region_end - 1;
+ }
+ constexpr bool IsOutsideStackRegion(VAddr address, std::size_t size) const {
+ return stack_region_start > address || address + size - 1 > stack_region_end - 1;
+ }
+ constexpr bool IsInvalidRegion(VAddr address, std::size_t size) const {
+ return address + size - 1 > GetAliasCodeRegionStart() + GetAliasCodeRegionSize() - 1;
+ }
+ constexpr bool IsInsideHeapRegion(VAddr address, std::size_t size) const {
+ return address + size > heap_region_start && heap_region_end > address;
+ }
+ constexpr bool IsInsideAliasRegion(VAddr address, std::size_t size) const {
+ return address + size > alias_region_start && alias_region_end > address;
+ }
+ constexpr bool IsOutsideASLRRegion(VAddr address, std::size_t size) const {
+ if (IsInvalidRegion(address, size)) {
+ return true;
+ }
+ if (IsInsideHeapRegion(address, size)) {
+ return true;
+ }
+ if (IsInsideAliasRegion(address, size)) {
+ return true;
+ }
+ return {};
+ }
+ constexpr bool IsInsideASLRRegion(VAddr address, std::size_t size) const {
+ return !IsOutsideASLRRegion(address, size);
+ }
+ constexpr PAddr GetPhysicalAddr(VAddr addr) {
+ return page_table_impl.backing_addr[addr >> Memory::PageBits] + addr;
+ }
+
+private:
+ constexpr bool Contains(VAddr addr) const {
+ return address_space_start <= addr && addr <= address_space_end - 1;
+ }
+ constexpr bool Contains(VAddr addr, std::size_t size) const {
+ return address_space_start <= addr && addr < addr + size &&
+ addr + size - 1 <= address_space_end - 1;
+ }
+ constexpr bool IsKernel() const {
+ return is_kernel;
+ }
+ constexpr bool IsAslrEnabled() const {
+ return is_aslr_enabled;
+ }
+
+ constexpr std::size_t GetNumGuardPages() const {
+ return IsKernel() ? 1 : 4;
+ }
+
+ constexpr bool ContainsPages(VAddr addr, std::size_t num_pages) const {
+ return (address_space_start <= addr) &&
+ (num_pages <= (address_space_end - address_space_start) / PageSize) &&
+ (addr + num_pages * PageSize - 1 <= address_space_end - 1);
+ }
+
+private:
+ VAddr address_space_start{};
+ VAddr address_space_end{};
+ VAddr heap_region_start{};
+ VAddr heap_region_end{};
+ VAddr current_heap_end{};
+ VAddr alias_region_start{};
+ VAddr alias_region_end{};
+ VAddr stack_region_start{};
+ VAddr stack_region_end{};
+ VAddr kernel_map_region_start{};
+ VAddr kernel_map_region_end{};
+ VAddr code_region_start{};
+ VAddr code_region_end{};
+ VAddr alias_code_region_start{};
+ VAddr alias_code_region_end{};
+ VAddr current_heap_addr{};
+
+ std::size_t heap_capacity{};
+ std::size_t physical_memory_usage{};
+ std::size_t max_heap_size{};
+ std::size_t max_physical_memory_size{};
+ std::size_t address_space_width{};
+
+ bool is_kernel{};
+ bool is_aslr_enabled{};
+
+ MemoryManager::Pool memory_pool{MemoryManager::Pool::Application};
+
+ Common::PageTable page_table_impl;
+
+ Core::System& system;
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/slab_heap.h b/src/core/hle/kernel/memory/slab_heap.h
new file mode 100644
index 000000000..465eaddb3
--- /dev/null
+++ b/src/core/hle/kernel/memory/slab_heap.h
@@ -0,0 +1,163 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include <atomic>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace Kernel::Memory {
+
+namespace impl {
+
+class SlabHeapImpl final : NonCopyable {
+public:
+ struct Node {
+ Node* next{};
+ };
+
+ constexpr SlabHeapImpl() = default;
+
+ void Initialize(std::size_t size) {
+ ASSERT(head == nullptr);
+ obj_size = size;
+ }
+
+ constexpr std::size_t GetObjectSize() const {
+ return obj_size;
+ }
+
+ Node* GetHead() const {
+ return head;
+ }
+
+ void* Allocate() {
+ Node* ret = head.load();
+
+ do {
+ if (ret == nullptr) {
+ break;
+ }
+ } while (!head.compare_exchange_weak(ret, ret->next));
+
+ return ret;
+ }
+
+ void Free(void* obj) {
+ Node* node = static_cast<Node*>(obj);
+
+ Node* cur_head = head.load();
+ do {
+ node->next = cur_head;
+ } while (!head.compare_exchange_weak(cur_head, node));
+ }
+
+private:
+ std::atomic<Node*> head{};
+ std::size_t obj_size{};
+};
+
+} // namespace impl
+
+class SlabHeapBase : NonCopyable {
+public:
+ constexpr SlabHeapBase() = default;
+
+ constexpr bool Contains(uintptr_t addr) const {
+ return start <= addr && addr < end;
+ }
+
+ constexpr std::size_t GetSlabHeapSize() const {
+ return (end - start) / GetObjectSize();
+ }
+
+ constexpr std::size_t GetObjectSize() const {
+ return impl.GetObjectSize();
+ }
+
+ constexpr uintptr_t GetSlabHeapAddress() const {
+ return start;
+ }
+
+ std::size_t GetObjectIndexImpl(const void* obj) const {
+ return (reinterpret_cast<uintptr_t>(obj) - start) / GetObjectSize();
+ }
+
+ std::size_t GetPeakIndex() const {
+ return GetObjectIndexImpl(reinterpret_cast<const void*>(peak));
+ }
+
+ void* AllocateImpl() {
+ return impl.Allocate();
+ }
+
+ void FreeImpl(void* obj) {
+ // Don't allow freeing an object that wasn't allocated from this heap
+ ASSERT(Contains(reinterpret_cast<uintptr_t>(obj)));
+ impl.Free(obj);
+ }
+
+ void InitializeImpl(std::size_t obj_size, void* memory, std::size_t memory_size) {
+ // Ensure we don't initialize a slab using null memory
+ ASSERT(memory != nullptr);
+
+ // Initialize the base allocator
+ impl.Initialize(obj_size);
+
+ // Set our tracking variables
+ const std::size_t num_obj = (memory_size / obj_size);
+ start = reinterpret_cast<uintptr_t>(memory);
+ end = start + num_obj * obj_size;
+ peak = start;
+
+ // Free the objects
+ u8* cur = reinterpret_cast<u8*>(end);
+
+ for (std::size_t i{}; i < num_obj; i++) {
+ cur -= obj_size;
+ impl.Free(cur);
+ }
+ }
+
+private:
+ using Impl = impl::SlabHeapImpl;
+
+ Impl impl;
+ uintptr_t peak{};
+ uintptr_t start{};
+ uintptr_t end{};
+};
+
+template <typename T>
+class SlabHeap final : public SlabHeapBase {
+public:
+ constexpr SlabHeap() : SlabHeapBase() {}
+
+ void Initialize(void* memory, std::size_t memory_size) {
+ InitializeImpl(sizeof(T), memory, memory_size);
+ }
+
+ T* Allocate() {
+ T* obj = static_cast<T*>(AllocateImpl());
+ if (obj != nullptr) {
+ new (obj) T();
+ }
+ return obj;
+ }
+
+ void Free(T* obj) {
+ FreeImpl(obj);
+ }
+
+ constexpr std::size_t GetObjectIndex(const T* obj) const {
+ return GetObjectIndexImpl(obj);
+ }
+};
+
+} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/system_control.cpp b/src/core/hle/kernel/memory/system_control.cpp
new file mode 100644
index 000000000..11d204bc2
--- /dev/null
+++ b/src/core/hle/kernel/memory/system_control.cpp
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <random>
+
+#include "core/hle/kernel/memory/system_control.h"
+
+namespace Kernel::Memory::SystemControl {
+namespace {
+template <typename F>
+u64 GenerateUniformRange(u64 min, u64 max, F f) {
+ // Handle the case where the difference is too large to represent.
+ if (max == std::numeric_limits<u64>::max() && min == std::numeric_limits<u64>::min()) {
+ return f();
+ }
+
+ // Iterate until we get a value in range.
+ const u64 range_size = ((max + 1) - min);
+ const u64 effective_max = (std::numeric_limits<u64>::max() / range_size) * range_size;
+ while (true) {
+ if (const u64 rnd = f(); rnd < effective_max) {
+ return min + (rnd % range_size);
+ }
+ }
+}
+
+u64 GenerateRandomU64ForInit() {
+ static std::random_device device;
+ static std::mt19937 gen(device());
+ static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
+ return distribution(gen);
+}
+} // Anonymous namespace
+
+u64 GenerateRandomRange(u64 min, u64 max) {
+ return GenerateUniformRange(min, max, GenerateRandomU64ForInit);
+}
+
+} // namespace Kernel::Memory::SystemControl
diff --git a/src/core/hle/kernel/memory/system_control.h b/src/core/hle/kernel/memory/system_control.h
new file mode 100644
index 000000000..19cab8cbc
--- /dev/null
+++ b/src/core/hle/kernel/memory/system_control.h
@@ -0,0 +1,13 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel::Memory::SystemControl {
+
+u64 GenerateRandomRange(u64 min, u64 max);
+
+} // namespace Kernel::Memory::SystemControl
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index eff4e45b0..8f6c944d1 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -7,6 +7,7 @@
#include <vector>
#include "common/assert.h"
+#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
@@ -33,8 +34,6 @@ static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThr
if (thread->GetMutexWaitAddress() != mutex_addr)
continue;
- ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
-
++num_waiters;
if (highest_priority_thread == nullptr ||
thread->GetPriority() < highest_priority_thread->GetPriority()) {
@@ -48,6 +47,7 @@ static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThr
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
static void TransferMutexOwnership(VAddr mutex_addr, std::shared_ptr<Thread> current_thread,
std::shared_ptr<Thread> new_owner) {
+ current_thread->RemoveMutexWaiter(new_owner);
const auto threads = current_thread->GetMutexWaitingThreads();
for (const auto& thread : threads) {
if (thread->GetMutexWaitAddress() != mutex_addr)
@@ -67,85 +67,104 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
Handle requesting_thread_handle) {
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
+ LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address);
return ERR_INVALID_ADDRESS;
}
- const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ auto& kernel = system.Kernel();
std::shared_ptr<Thread> current_thread =
- SharedFrom(system.CurrentScheduler().GetCurrentThread());
- std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
- std::shared_ptr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
+ SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
+ {
+ SchedulerLock lock(kernel);
+ // The mutex address must be 4-byte aligned
+ if ((address % sizeof(u32)) != 0) {
+ return ERR_INVALID_ADDRESS;
+ }
- // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
- // thread.
- ASSERT(requesting_thread == current_thread);
+ const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
+ std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
+ std::shared_ptr<Thread> requesting_thread =
+ handle_table.Get<Thread>(requesting_thread_handle);
- const u32 addr_value = system.Memory().Read32(address);
+ // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of
+ // another thread.
+ ASSERT(requesting_thread == current_thread);
- // If the mutex isn't being held, just return success.
- if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
- return RESULT_SUCCESS;
- }
+ current_thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
- if (holding_thread == nullptr) {
- return ERR_INVALID_HANDLE;
- }
+ const u32 addr_value = system.Memory().Read32(address);
+
+ // If the mutex isn't being held, just return success.
+ if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
+ return RESULT_SUCCESS;
+ }
- // Wait until the mutex is released
- current_thread->SetMutexWaitAddress(address);
- current_thread->SetWaitHandle(requesting_thread_handle);
+ if (holding_thread == nullptr) {
+ return ERR_INVALID_HANDLE;
+ }
- current_thread->SetStatus(ThreadStatus::WaitMutex);
- current_thread->InvalidateWakeupCallback();
+ // Wait until the mutex is released
+ current_thread->SetMutexWaitAddress(address);
+ current_thread->SetWaitHandle(requesting_thread_handle);
- // Update the lock holder thread's priority to prevent priority inversion.
- holding_thread->AddMutexWaiter(current_thread);
+ current_thread->SetStatus(ThreadStatus::WaitMutex);
- system.PrepareReschedule();
+ // Update the lock holder thread's priority to prevent priority inversion.
+ holding_thread->AddMutexWaiter(current_thread);
+ }
- return RESULT_SUCCESS;
+ {
+ SchedulerLock lock(kernel);
+ auto* owner = current_thread->GetLockOwner();
+ if (owner != nullptr) {
+ owner->RemoveMutexWaiter(current_thread);
+ }
+ }
+ return current_thread->GetSignalingResult();
}
-ResultCode Mutex::Release(VAddr address) {
+std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thread> owner,
+ VAddr address) {
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
- return ERR_INVALID_ADDRESS;
+ LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address);
+ return {ERR_INVALID_ADDRESS, nullptr};
}
- std::shared_ptr<Thread> current_thread =
- SharedFrom(system.CurrentScheduler().GetCurrentThread());
- auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
-
- // There are no more threads waiting for the mutex, release it completely.
- if (thread == nullptr) {
+ auto [new_owner, num_waiters] = GetHighestPriorityMutexWaitingThread(owner, address);
+ if (new_owner == nullptr) {
system.Memory().Write32(address, 0);
- return RESULT_SUCCESS;
+ return {RESULT_SUCCESS, nullptr};
}
-
// Transfer the ownership of the mutex from the previous owner to the new one.
- TransferMutexOwnership(address, current_thread, thread);
-
- u32 mutex_value = thread->GetWaitHandle();
-
+ TransferMutexOwnership(address, owner, new_owner);
+ u32 mutex_value = new_owner->GetWaitHandle();
if (num_waiters >= 2) {
// Notify the guest that there are still some threads waiting for the mutex
mutex_value |= Mutex::MutexHasWaitersFlag;
}
+ new_owner->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
+ new_owner->SetLockOwner(nullptr);
+ new_owner->ResumeFromWait();
- // Grant the mutex to the next waiting thread and resume it.
system.Memory().Write32(address, mutex_value);
+ return {RESULT_SUCCESS, new_owner};
+}
- ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
- thread->ResumeFromWait();
+ResultCode Mutex::Release(VAddr address) {
+ auto& kernel = system.Kernel();
+ SchedulerLock lock(kernel);
- thread->SetLockOwner(nullptr);
- thread->SetCondVarWaitAddress(0);
- thread->SetMutexWaitAddress(0);
- thread->SetWaitHandle(0);
- thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ std::shared_ptr<Thread> current_thread =
+ SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
- system.PrepareReschedule();
+ auto [result, new_owner] = Unlock(current_thread, address);
- return RESULT_SUCCESS;
+ if (result != RESULT_SUCCESS && new_owner != nullptr) {
+ new_owner->SetSynchronizationResults(nullptr, result);
+ }
+
+ return result;
}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index b904de2e8..3b81dc3df 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -28,6 +28,10 @@ public:
ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
Handle requesting_thread_handle);
+ /// Unlocks a mutex for owner at address
+ std::pair<ResultCode, std::shared_ptr<Thread>> Unlock(std::shared_ptr<Thread> owner,
+ VAddr address);
+
/// Releases the mutex at the specified address.
ResultCode Release(VAddr address);
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index aa2787467..6e04d025f 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -2,63 +2,43 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/arm/arm_interface.h"
-#ifdef ARCHITECTURE_x86_64
-#include "core/arm/dynarmic/arm_dynarmic_32.h"
-#include "core/arm/dynarmic/arm_dynarmic_64.h"
-#endif
-#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
+#include "common/spin_lock.h"
+#include "core/arm/cpu_interrupt_handler.h"
#include "core/core.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
namespace Kernel {
-PhysicalCore::PhysicalCore(Core::System& system, std::size_t id,
- Core::ExclusiveMonitor& exclusive_monitor)
- : core_index{id} {
-#ifdef ARCHITECTURE_x86_64
- arm_interface_32 =
- std::make_unique<Core::ARM_Dynarmic_32>(system, exclusive_monitor, core_index);
- arm_interface_64 =
- std::make_unique<Core::ARM_Dynarmic_64>(system, exclusive_monitor, core_index);
-
-#else
- arm_interface = std::make_shared<Core::ARM_Unicorn>(system);
- LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
-#endif
-
- scheduler = std::make_unique<Kernel::Scheduler>(system, core_index);
-}
+PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
+ Core::CPUInterruptHandler& interrupt_handler)
+ : interrupt_handler{interrupt_handler},
+ core_index{id}, scheduler{scheduler}, guard{std::make_unique<Common::SpinLock>()} {}
PhysicalCore::~PhysicalCore() = default;
-void PhysicalCore::Run() {
- arm_interface->Run();
- arm_interface->ClearExclusiveState();
+void PhysicalCore::Idle() {
+ interrupt_handler.AwaitInterrupt();
}
-void PhysicalCore::Step() {
- arm_interface->Step();
+void PhysicalCore::Shutdown() {
+ scheduler.Shutdown();
}
-void PhysicalCore::Stop() {
- arm_interface->PrepareReschedule();
+bool PhysicalCore::IsInterrupted() const {
+ return interrupt_handler.IsInterrupted();
}
-void PhysicalCore::Shutdown() {
- scheduler->Shutdown();
+void PhysicalCore::Interrupt() {
+ guard->lock();
+ interrupt_handler.SetInterrupt(true);
+ guard->unlock();
}
-void PhysicalCore::SetIs64Bit(bool is_64_bit) {
- if (is_64_bit) {
- arm_interface = arm_interface_64.get();
- } else {
- arm_interface = arm_interface_32.get();
- }
+void PhysicalCore::ClearInterrupt() {
+ guard->lock();
+ interrupt_handler.SetInterrupt(false);
+ guard->unlock();
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
index 3269166be..d7a7a951c 100644
--- a/src/core/hle/kernel/physical_core.h
+++ b/src/core/hle/kernel/physical_core.h
@@ -7,12 +7,17 @@
#include <cstddef>
#include <memory>
+namespace Common {
+class SpinLock;
+}
+
namespace Kernel {
class Scheduler;
} // namespace Kernel
namespace Core {
class ARM_Interface;
+class CPUInterruptHandler;
class ExclusiveMonitor;
class System;
} // namespace Core
@@ -21,7 +26,8 @@ namespace Kernel {
class PhysicalCore {
public:
- PhysicalCore(Core::System& system, std::size_t id, Core::ExclusiveMonitor& exclusive_monitor);
+ PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
+ Core::CPUInterruptHandler& interrupt_handler);
~PhysicalCore();
PhysicalCore(const PhysicalCore&) = delete;
@@ -30,23 +36,18 @@ public:
PhysicalCore(PhysicalCore&&) = default;
PhysicalCore& operator=(PhysicalCore&&) = default;
- /// Execute current jit state
- void Run();
- /// Execute a single instruction in current jit.
- void Step();
- /// Stop JIT execution/exit
- void Stop();
+ void Idle();
+ /// Interrupt this physical core.
+ void Interrupt();
- // Shutdown this physical core.
- void Shutdown();
+ /// Clear this core's interrupt
+ void ClearInterrupt();
- Core::ARM_Interface& ArmInterface() {
- return *arm_interface;
- }
+ /// Check if this core is interrupted
+ bool IsInterrupted() const;
- const Core::ARM_Interface& ArmInterface() const {
- return *arm_interface;
- }
+ // Shutdown this physical core.
+ void Shutdown();
bool IsMainCore() const {
return core_index == 0;
@@ -61,21 +62,18 @@ public:
}
Kernel::Scheduler& Scheduler() {
- return *scheduler;
+ return scheduler;
}
const Kernel::Scheduler& Scheduler() const {
- return *scheduler;
+ return scheduler;
}
- void SetIs64Bit(bool is_64_bit);
-
private:
+ Core::CPUInterruptHandler& interrupt_handler;
std::size_t core_index;
- std::unique_ptr<Core::ARM_Interface> arm_interface_32;
- std::unique_ptr<Core::ARM_Interface> arm_interface_64;
- std::unique_ptr<Kernel::Scheduler> scheduler;
- Core::ARM_Interface* arm_interface{};
+ Kernel::Scheduler& scheduler;
+ std::unique_ptr<Common::SpinLock> guard;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h
index b689e8e8b..7a0266780 100644
--- a/src/core/hle/kernel/physical_memory.h
+++ b/src/core/hle/kernel/physical_memory.h
@@ -4,6 +4,8 @@
#pragma once
+#include <vector>
+
#include "common/alignment.h"
namespace Kernel {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index edc414d69..b17529dee 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -4,21 +4,26 @@
#include <algorithm>
#include <bitset>
+#include <ctime>
#include <memory>
#include <random>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/device_memory.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/memory_block_manager.h"
+#include "core/hle/kernel/memory/page_table.h"
+#include "core/hle/kernel/memory/slab_heap.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/vm_manager.h"
+#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/settings.h"
@@ -27,26 +32,31 @@ namespace {
/**
* Sets up the primary application thread
*
+ * @param system The system instance to create the main thread under.
* @param owner_process The parent process for the main thread
- * @param kernel The kernel instance to create the main thread under.
* @param priority The priority to give the main thread
*/
-void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
- const auto& vm_manager = owner_process.VMManager();
- const VAddr entry_point = vm_manager.GetCodeRegionBaseAddress();
- const VAddr stack_top = vm_manager.GetTLSIORegionEndAddress();
- auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0,
- owner_process.GetIdealCore(), stack_top, owner_process);
+void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, VAddr stack_top) {
+ const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart();
+ ThreadType type = THREADTYPE_USER;
+ auto thread_res = Thread::Create(system, type, "main", entry_point, priority, 0,
+ owner_process.GetIdealCore(), stack_top, &owner_process);
std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
const Handle thread_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
+ thread->GetContext32().cpu_registers[0] = 0;
+ thread->GetContext64().cpu_registers[0] = 0;
thread->GetContext32().cpu_registers[1] = thread_handle;
thread->GetContext64().cpu_registers[1] = thread_handle;
+ auto& kernel = system.Kernel();
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
- thread->ResumeFromWait();
+ {
+ SchedulerLock lock{kernel};
+ thread->SetStatus(ThreadStatus::Ready);
+ }
}
} // Anonymous namespace
@@ -57,7 +67,8 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
// (whichever page happens to have an available slot).
class TLSPage {
public:
- static constexpr std::size_t num_slot_entries = Memory::PAGE_SIZE / Memory::TLS_ENTRY_SIZE;
+ static constexpr std::size_t num_slot_entries =
+ Core::Memory::PAGE_SIZE / Core::Memory::TLS_ENTRY_SIZE;
explicit TLSPage(VAddr address) : base_address{address} {}
@@ -76,7 +87,7 @@ public:
}
is_slot_used[i] = true;
- return base_address + (i * Memory::TLS_ENTRY_SIZE);
+ return base_address + (i * Core::Memory::TLS_ENTRY_SIZE);
}
return std::nullopt;
@@ -86,15 +97,15 @@ public:
// Ensure that all given addresses are consistent with how TLS pages
// are intended to be used when releasing slots.
ASSERT(IsWithinPage(address));
- ASSERT((address % Memory::TLS_ENTRY_SIZE) == 0);
+ ASSERT((address % Core::Memory::TLS_ENTRY_SIZE) == 0);
- const std::size_t index = (address - base_address) / Memory::TLS_ENTRY_SIZE;
+ const std::size_t index = (address - base_address) / Core::Memory::TLS_ENTRY_SIZE;
is_slot_used[index] = false;
}
private:
bool IsWithinPage(VAddr address) const {
- return base_address <= address && address < base_address + Memory::PAGE_SIZE;
+ return base_address <= address && address < base_address + Core::Memory::PAGE_SIZE;
}
VAddr base_address;
@@ -106,14 +117,14 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name,
std::shared_ptr<Process> process = std::make_shared<Process>(system);
process->name = std::move(name);
- process->resource_limit = kernel.GetSystemResourceLimit();
+ process->resource_limit = ResourceLimit::Create(kernel);
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
: kernel.CreateNewUserProcessID();
process->capabilities.InitializeForMetadatalessProcess();
- std::mt19937 rng(Settings::values.rng_seed.value_or(0));
+ std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr)));
std::uniform_int_distribution<u64> distribution;
std::generate(process->random_entropy.begin(), process->random_entropy.end(),
[&] { return distribution(rng); });
@@ -127,7 +138,15 @@ std::shared_ptr<ResourceLimit> Process::GetResourceLimit() const {
}
u64 Process::GetTotalPhysicalMemoryAvailable() const {
- return vm_manager.GetTotalPhysicalMemoryAvailable();
+ const u64 capacity{resource_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory) +
+ page_table->GetTotalHeapSize() + GetSystemResourceSize() + image_size +
+ main_thread_stack_size};
+
+ if (capacity < memory_usage_capacity) {
+ return capacity;
+ }
+
+ return memory_usage_capacity;
}
u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const {
@@ -135,8 +154,8 @@ u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const {
}
u64 Process::GetTotalPhysicalMemoryUsed() const {
- return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size +
- GetSystemResourceUsage();
+ return image_size + main_thread_stack_size + page_table->GetTotalHeapSize() +
+ GetSystemResourceSize();
}
u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const {
@@ -170,7 +189,6 @@ void Process::RemoveConditionVariableThread(std::shared_ptr<Thread> thread) {
}
++it;
}
- UNREACHABLE();
}
std::vector<std::shared_ptr<Thread>> Process::GetConditionVariableThreads(
@@ -195,6 +213,7 @@ void Process::UnregisterThread(const Thread* thread) {
}
ResultCode Process::ClearSignalState() {
+ SchedulerLock lock(system.Kernel());
if (status == ProcessStatus::Exited) {
LOG_ERROR(Kernel, "called on a terminated process instance.");
return ERR_INVALID_STATE;
@@ -209,33 +228,82 @@ ResultCode Process::ClearSignalState() {
return RESULT_SUCCESS;
}
-ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
+ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
+ std::size_t code_size) {
program_id = metadata.GetTitleID();
ideal_core = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
system_resource_size = metadata.GetSystemResourceSize();
+ image_size = code_size;
+
+ // Initialize proces address space
+ if (const ResultCode result{
+ page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false, 0x8000000,
+ code_size, Memory::MemoryManager::Pool::Application)};
+ result.IsError()) {
+ return result;
+ }
- vm_manager.Reset(metadata.GetAddressSpaceType());
+ // Map process code region
+ if (const ResultCode result{page_table->MapProcessCode(
+ page_table->GetCodeRegionStart(), code_size / Memory::PageSize,
+ Memory::MemoryState::Code, Memory::MemoryPermission::None)};
+ result.IsError()) {
+ return result;
+ }
+
+ // Initialize process capabilities
+ const auto& caps{metadata.GetKernelCapabilities()};
+ if (const ResultCode result{
+ capabilities.InitializeForUserProcess(caps.data(), caps.size(), *page_table)};
+ result.IsError()) {
+ return result;
+ }
- const auto& caps = metadata.GetKernelCapabilities();
- const auto capability_init_result =
- capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager);
- if (capability_init_result.IsError()) {
- return capability_init_result;
+ // Set memory usage capacity
+ switch (metadata.GetAddressSpaceType()) {
+ case FileSys::ProgramAddressSpaceType::Is32Bit:
+ case FileSys::ProgramAddressSpaceType::Is36Bit:
+ case FileSys::ProgramAddressSpaceType::Is39Bit:
+ memory_usage_capacity = page_table->GetHeapRegionEnd() - page_table->GetHeapRegionStart();
+ break;
+
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
+ memory_usage_capacity = page_table->GetHeapRegionEnd() - page_table->GetHeapRegionStart() +
+ page_table->GetAliasRegionEnd() - page_table->GetAliasRegionStart();
+ break;
+
+ default:
+ UNREACHABLE();
}
+ // Set initial resource limits
+ resource_limit->SetLimitValue(
+ ResourceType::PhysicalMemory,
+ kernel.MemoryManager().GetSize(Memory::MemoryManager::Pool::Application));
+ resource_limit->SetLimitValue(ResourceType::Threads, 608);
+ resource_limit->SetLimitValue(ResourceType::Events, 700);
+ resource_limit->SetLimitValue(ResourceType::TransferMemory, 128);
+ resource_limit->SetLimitValue(ResourceType::Sessions, 894);
+ ASSERT(resource_limit->Reserve(ResourceType::PhysicalMemory, code_size));
+
+ // Create TLS region
+ tls_region_address = CreateTLSRegion();
+
return handle_table.SetSize(capabilities.GetHandleTableSize());
}
void Process::Run(s32 main_thread_priority, u64 stack_size) {
AllocateMainThreadStack(stack_size);
- tls_region_address = CreateTLSRegion();
- vm_manager.LogLayout();
+ const std::size_t heap_capacity{memory_usage_capacity - main_thread_stack_size - image_size};
+ ASSERT(!page_table->SetHeapCapacity(heap_capacity).IsError());
ChangeStatus(ProcessStatus::Running);
- SetupMainThread(*this, kernel, main_thread_priority);
+ SetupMainThread(system, *this, main_thread_priority, main_thread_stack_top);
+ resource_limit->Reserve(ResourceType::Threads, 1);
+ resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size);
}
void Process::PrepareForTermination() {
@@ -279,32 +347,39 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
}
VAddr Process::CreateTLSRegion() {
- auto tls_page_iter = FindTLSPageWithAvailableSlots(tls_pages);
+ SchedulerLock lock(system.Kernel());
+ if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
+ tls_page_iter != tls_pages.cend()) {
+ return *tls_page_iter->ReserveSlot();
+ }
- if (tls_page_iter == tls_pages.cend()) {
- const auto region_address =
- vm_manager.FindFreeRegion(vm_manager.GetTLSIORegionBaseAddress(),
- vm_manager.GetTLSIORegionEndAddress(), Memory::PAGE_SIZE);
- ASSERT(region_address.Succeeded());
+ Memory::Page* const tls_page_ptr{kernel.GetUserSlabHeapPages().Allocate()};
+ ASSERT(tls_page_ptr);
- const auto map_result = vm_manager.MapMemoryBlock(
- *region_address, std::make_shared<PhysicalMemory>(Memory::PAGE_SIZE), 0,
- Memory::PAGE_SIZE, MemoryState::ThreadLocal);
- ASSERT(map_result.Succeeded());
+ const VAddr start{page_table->GetKernelMapRegionStart()};
+ const VAddr size{page_table->GetKernelMapRegionEnd() - start};
+ const PAddr tls_map_addr{system.DeviceMemory().GetPhysicalAddr(tls_page_ptr)};
+ const VAddr tls_page_addr{
+ page_table
+ ->AllocateAndMapMemory(1, Memory::PageSize, true, start, size / Memory::PageSize,
+ Memory::MemoryState::ThreadLocal,
+ Memory::MemoryPermission::ReadAndWrite, tls_map_addr)
+ .ValueOr(0)};
- tls_pages.emplace_back(*region_address);
+ ASSERT(tls_page_addr);
- const auto reserve_result = tls_pages.back().ReserveSlot();
- ASSERT(reserve_result.has_value());
+ std::memset(tls_page_ptr, 0, Memory::PageSize);
+ tls_pages.emplace_back(tls_page_addr);
- return *reserve_result;
- }
+ const auto reserve_result{tls_pages.back().ReserveSlot()};
+ ASSERT(reserve_result.has_value());
- return *tls_page_iter->ReserveSlot();
+ return *reserve_result;
}
void Process::FreeTLSRegion(VAddr tls_address) {
- const VAddr aligned_address = Common::AlignDown(tls_address, Memory::PAGE_SIZE);
+ SchedulerLock lock(system.Kernel());
+ const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
auto iter =
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
return page.GetBaseAddress() == aligned_address;
@@ -317,29 +392,24 @@ void Process::FreeTLSRegion(VAddr tls_address) {
iter->ReleaseSlot(tls_address);
}
-void Process::LoadModule(CodeSet module_, VAddr base_addr) {
- code_memory_size += module_.memory.size();
-
- const auto memory = std::make_shared<PhysicalMemory>(std::move(module_.memory));
-
- const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
- MemoryState memory_state) {
- const auto vma = vm_manager
- .MapMemoryBlock(segment.addr + base_addr, memory, segment.offset,
- segment.size, memory_state)
- .Unwrap();
- vm_manager.Reprotect(vma, permissions);
+void Process::LoadModule(CodeSet code_set, VAddr base_addr) {
+ std::lock_guard lock{HLE::g_hle_lock};
+ const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
+ Memory::MemoryPermission permission) {
+ page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission);
};
- // Map CodeSet segments
- MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
- MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData);
- MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData);
+ system.Memory().WriteBlock(*this, base_addr, code_set.memory.data(), code_set.memory.size());
+
+ ReprotectSegment(code_set.CodeSegment(), Memory::MemoryPermission::ReadAndExecute);
+ ReprotectSegment(code_set.RODataSegment(), Memory::MemoryPermission::Read);
+ ReprotectSegment(code_set.DataSegment(), Memory::MemoryPermission::ReadAndWrite);
}
Process::Process(Core::System& system)
- : SynchronizationObject{system.Kernel()}, vm_manager{system},
- address_arbiter{system}, mutex{system}, system{system} {}
+ : SynchronizationObject{system.Kernel()}, page_table{std::make_unique<Memory::PageTable>(
+ system)},
+ handle_table{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {}
Process::~Process() = default;
@@ -361,16 +431,24 @@ void Process::ChangeStatus(ProcessStatus new_status) {
Signal();
}
-void Process::AllocateMainThreadStack(u64 stack_size) {
+ResultCode Process::AllocateMainThreadStack(std::size_t stack_size) {
+ ASSERT(stack_size);
+
// The kernel always ensures that the given stack size is page aligned.
- main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE);
-
- // Allocate and map the main thread stack
- const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size;
- vm_manager
- .MapMemoryBlock(mapping_address, std::make_shared<PhysicalMemory>(main_thread_stack_size),
- 0, main_thread_stack_size, MemoryState::Stack)
- .Unwrap();
+ main_thread_stack_size = Common::AlignUp(stack_size, Memory::PageSize);
+
+ const VAddr start{page_table->GetStackRegionStart()};
+ const std::size_t size{page_table->GetStackRegionEnd() - start};
+
+ CASCADE_RESULT(main_thread_stack_top,
+ page_table->AllocateAndMapMemory(
+ main_thread_stack_size / Memory::PageSize, Memory::PageSize, false, start,
+ size / Memory::PageSize, Memory::MemoryState::Stack,
+ Memory::MemoryPermission::ReadAndWrite));
+
+ main_thread_stack_top += main_thread_stack_size;
+
+ return RESULT_SUCCESS;
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 4887132a7..f45cb5674 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -16,7 +16,6 @@
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/synchronization_object.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/hle/result.h"
namespace Core {
@@ -36,6 +35,10 @@ class TLSPage;
struct CodeSet;
+namespace Memory {
+class PageTable;
+}
+
enum class MemoryRegion : u16 {
APPLICATION = 1,
SYSTEM = 2,
@@ -100,14 +103,14 @@ public:
return HANDLE_TYPE;
}
- /// Gets a reference to the process' memory manager.
- Kernel::VMManager& VMManager() {
- return vm_manager;
+ /// Gets a reference to the process' page table.
+ Memory::PageTable& PageTable() {
+ return *page_table;
}
- /// Gets a const reference to the process' memory manager.
- const Kernel::VMManager& VMManager() const {
- return vm_manager;
+ /// Gets const a reference to the process' page table.
+ const Memory::PageTable& PageTable() const {
+ return *page_table;
}
/// Gets a reference to the process' handle table.
@@ -273,7 +276,7 @@ public:
* @returns RESULT_SUCCESS if all relevant metadata was able to be
* loaded and parsed. Otherwise, an error code is returned.
*/
- ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
+ ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
/**
* Starts the main application thread for this process.
@@ -289,7 +292,7 @@ public:
*/
void PrepareForTermination();
- void LoadModule(CodeSet module_, VAddr base_addr);
+ void LoadModule(CodeSet code_set, VAddr base_addr);
///////////////////////////////////////////////////////////////////////////////////////////////
// Thread-local storage management
@@ -313,16 +316,10 @@ private:
void ChangeStatus(ProcessStatus new_status);
/// Allocates the main thread stack for the process, given the stack size in bytes.
- void AllocateMainThreadStack(u64 stack_size);
-
- /// Memory manager for this process.
- Kernel::VMManager vm_manager;
+ ResultCode AllocateMainThreadStack(std::size_t stack_size);
- /// Size of the main thread's stack in bytes.
- u64 main_thread_stack_size = 0;
-
- /// Size of the loaded code memory in bytes.
- u64 code_memory_size = 0;
+ /// Memory manager for this process
+ std::unique_ptr<Memory::PageTable> page_table;
/// Current status of the process
ProcessStatus status{};
@@ -385,11 +382,23 @@ private:
/// List of threads waiting for a condition variable
std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> cond_var_threads;
- /// System context
- Core::System& system;
+ /// Address of the top of the main thread's stack
+ VAddr main_thread_stack_top{};
+
+ /// Size of the main thread's stack
+ std::size_t main_thread_stack_size{};
+
+ /// Memory usage capacity for the process
+ std::size_t memory_usage_capacity{};
+
+ /// Process total image size
+ std::size_t image_size{};
/// Name of this process
std::string name;
+
+ /// System context
+ Core::System& system;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp
index 583e35b79..63880f13d 100644
--- a/src/core/hle/kernel/process_capability.cpp
+++ b/src/core/hle/kernel/process_capability.cpp
@@ -3,10 +3,11 @@
// Refer to the license.txt file included.
#include "common/bit_util.h"
+#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process_capability.h"
-#include "core/hle/kernel/vm_manager.h"
namespace Kernel {
namespace {
@@ -66,7 +67,7 @@ u32 GetFlagBitOffset(CapabilityType type) {
ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
std::size_t num_capabilities,
- VMManager& vm_manager) {
+ Memory::PageTable& page_table) {
Clear();
// Allow all cores and priorities.
@@ -74,15 +75,15 @@ ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabiliti
priority_mask = 0xFFFFFFFFFFFFFFFF;
kernel_version = PackedKernelVersion;
- return ParseCapabilities(capabilities, num_capabilities, vm_manager);
+ return ParseCapabilities(capabilities, num_capabilities, page_table);
}
ResultCode ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
std::size_t num_capabilities,
- VMManager& vm_manager) {
+ Memory::PageTable& page_table) {
Clear();
- return ParseCapabilities(capabilities, num_capabilities, vm_manager);
+ return ParseCapabilities(capabilities, num_capabilities, page_table);
}
void ProcessCapabilities::InitializeForMetadatalessProcess() {
@@ -105,7 +106,7 @@ void ProcessCapabilities::InitializeForMetadatalessProcess() {
ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
std::size_t num_capabilities,
- VMManager& vm_manager) {
+ Memory::PageTable& page_table) {
u32 set_flags = 0;
u32 set_svc_bits = 0;
@@ -119,22 +120,30 @@ ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
// The MapPhysical type uses two descriptor flags for its parameters.
// If there's only one, then there's a problem.
if (i >= num_capabilities) {
+ LOG_ERROR(Kernel, "Invalid combination! i={}", i);
return ERR_INVALID_COMBINATION;
}
const auto size_flags = capabilities[i];
if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) {
+ LOG_ERROR(Kernel, "Invalid capability type! size_flags={}", size_flags);
return ERR_INVALID_COMBINATION;
}
- const auto result = HandleMapPhysicalFlags(descriptor, size_flags, vm_manager);
+ const auto result = HandleMapPhysicalFlags(descriptor, size_flags, page_table);
if (result.IsError()) {
+ LOG_ERROR(Kernel, "Failed to map physical flags! descriptor={}, size_flags={}",
+ descriptor, size_flags);
return result;
}
} else {
const auto result =
- ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, vm_manager);
+ ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, page_table);
if (result.IsError()) {
+ LOG_ERROR(
+ Kernel,
+ "Failed to parse capability flag! set_flags={}, set_svc_bits={}, descriptor={}",
+ set_flags, set_svc_bits, descriptor);
return result;
}
}
@@ -144,7 +153,7 @@ ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
}
ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits,
- u32 flag, VMManager& vm_manager) {
+ u32 flag, Memory::PageTable& page_table) {
const auto type = GetCapabilityType(flag);
if (type == CapabilityType::Unset) {
@@ -162,6 +171,9 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
const u32 flag_length = GetFlagBitOffset(type);
const u32 set_flag = 1U << flag_length;
if ((set_flag & set_flags & InitializeOnceMask) != 0) {
+ LOG_ERROR(Kernel,
+ "Attempted to initialize flags that may only be initialized once. set_flags={}",
+ set_flags);
return ERR_INVALID_COMBINATION;
}
set_flags |= set_flag;
@@ -172,7 +184,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
case CapabilityType::Syscall:
return HandleSyscallFlags(set_svc_bits, flag);
case CapabilityType::MapIO:
- return HandleMapIOFlags(flag, vm_manager);
+ return HandleMapIOFlags(flag, page_table);
case CapabilityType::Interrupt:
return HandleInterruptFlags(flag);
case CapabilityType::ProgramType:
@@ -187,6 +199,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
break;
}
+ LOG_ERROR(Kernel, "Invalid capability type! type={}", static_cast<u32>(type));
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
@@ -208,23 +221,31 @@ void ProcessCapabilities::Clear() {
ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
if (priority_mask != 0 || core_mask != 0) {
+ LOG_ERROR(Kernel, "Core or priority mask are not zero! priority_mask={}, core_mask={}",
+ priority_mask, core_mask);
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
const u32 core_num_min = (flags >> 16) & 0xFF;
const u32 core_num_max = (flags >> 24) & 0xFF;
if (core_num_min > core_num_max) {
+ LOG_ERROR(Kernel, "Core min is greater than core max! core_num_min={}, core_num_max={}",
+ core_num_min, core_num_max);
return ERR_INVALID_COMBINATION;
}
const u32 priority_min = (flags >> 10) & 0x3F;
const u32 priority_max = (flags >> 4) & 0x3F;
if (priority_min > priority_max) {
+ LOG_ERROR(Kernel,
+ "Priority min is greater than priority max! priority_min={}, priority_max={}",
+ core_num_min, priority_max);
return ERR_INVALID_COMBINATION;
}
// The switch only has 4 usable cores.
if (core_num_max >= 4) {
+ LOG_ERROR(Kernel, "Invalid max cores specified! core_num_max={}", core_num_max);
return ERR_INVALID_PROCESSOR_ID;
}
@@ -259,6 +280,7 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags)
}
if (svc_number >= svc_capabilities.size()) {
+ LOG_ERROR(Kernel, "Process svc capability is out of range! svc_number={}", svc_number);
return ERR_OUT_OF_RANGE;
}
@@ -269,12 +291,12 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags)
}
ResultCode ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
- VMManager& vm_manager) {
+ Memory::PageTable& page_table) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return RESULT_SUCCESS;
}
-ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, VMManager& vm_manager) {
+ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, Memory::PageTable& page_table) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return RESULT_SUCCESS;
}
@@ -295,6 +317,8 @@ ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
// emulate that, it's sufficient to mark every interrupt as defined.
if (interrupt >= interrupt_capabilities.size()) {
+ LOG_ERROR(Kernel, "Process interrupt capability is out of range! svc_number={}",
+ interrupt);
return ERR_OUT_OF_RANGE;
}
@@ -307,6 +331,7 @@ ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
const u32 reserved = flags >> 17;
if (reserved != 0) {
+ LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
return ERR_RESERVED_VALUE;
}
@@ -324,6 +349,9 @@ ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
const u32 major_version = kernel_version >> 19;
if (major_version != 0 || flags < 0x80000) {
+ LOG_ERROR(Kernel,
+ "Kernel version is non zero or flags are too small! major_version={}, flags={}",
+ major_version, flags);
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
@@ -334,6 +362,7 @@ ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
const u32 reserved = flags >> 26;
if (reserved != 0) {
+ LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
return ERR_RESERVED_VALUE;
}
@@ -344,6 +373,7 @@ ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) {
const u32 reserved = flags >> 19;
if (reserved != 0) {
+ LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
return ERR_RESERVED_VALUE;
}
diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h
index 5cdd80747..ea9d12c16 100644
--- a/src/core/hle/kernel/process_capability.h
+++ b/src/core/hle/kernel/process_capability.h
@@ -12,7 +12,9 @@ union ResultCode;
namespace Kernel {
-class VMManager;
+namespace Memory {
+class PageTable;
+}
/// The possible types of programs that may be indicated
/// by the program type capability descriptor.
@@ -81,27 +83,27 @@ public:
///
/// @param capabilities The capabilities to parse
/// @param num_capabilities The number of capabilities to parse.
- /// @param vm_manager The memory manager to use for handling any mapping-related
+ /// @param page_table The memory manager to use for handling any mapping-related
/// operations (such as mapping IO memory, etc).
///
/// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
ResultCode InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
- VMManager& vm_manager);
+ Memory::PageTable& page_table);
/// Initializes this process capabilities instance for a userland process.
///
/// @param capabilities The capabilities to parse.
/// @param num_capabilities The total number of capabilities to parse.
- /// @param vm_manager The memory manager to use for handling any mapping-related
+ /// @param page_table The memory manager to use for handling any mapping-related
/// operations (such as mapping IO memory, etc).
///
/// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
ResultCode InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
- VMManager& vm_manager);
+ Memory::PageTable& page_table);
/// Initializes this process capabilities instance for a process that does not
/// have any metadata to parse.
@@ -181,13 +183,13 @@ private:
///
/// @param capabilities The sequence of capability descriptors to parse.
/// @param num_capabilities The number of descriptors within the given sequence.
- /// @param vm_manager The memory manager that will perform any memory
+ /// @param page_table The memory manager that will perform any memory
/// mapping if necessary.
///
/// @return RESULT_SUCCESS if no errors occur, otherwise an error code.
///
ResultCode ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
- VMManager& vm_manager);
+ Memory::PageTable& page_table);
/// Attempts to parse a capability descriptor that is only represented by a
/// single flag set.
@@ -196,13 +198,13 @@ private:
/// flags being initialized more than once when they shouldn't be.
/// @param set_svc_bits Running set of bits representing the allowed supervisor calls mask.
/// @param flag The flag to attempt to parse.
- /// @param vm_manager The memory manager that will perform any memory
+ /// @param page_table The memory manager that will perform any memory
/// mapping if necessary.
///
/// @return RESULT_SUCCESS if no errors occurred, otherwise an error code.
///
ResultCode ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
- VMManager& vm_manager);
+ Memory::PageTable& page_table);
/// Clears the internal state of this process capability instance. Necessary,
/// to have a sane starting point due to us allowing running executables without
@@ -226,10 +228,10 @@ private:
ResultCode HandleSyscallFlags(u32& set_svc_bits, u32 flags);
/// Handles flags related to mapping physical memory pages.
- ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, VMManager& vm_manager);
+ ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, Memory::PageTable& page_table);
/// Handles flags related to mapping IO pages.
- ResultCode HandleMapIOFlags(u32 flags, VMManager& vm_manager);
+ ResultCode HandleMapIOFlags(u32 flags, Memory::PageTable& page_table);
/// Handles flags related to the interrupt capability flags.
ResultCode HandleInterruptFlags(u32 flags);
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
index 9d3d3a81b..6e286419e 100644
--- a/src/core/hle/kernel/readable_event.cpp
+++ b/src/core/hle/kernel/readable_event.cpp
@@ -4,9 +4,12 @@
#include <algorithm>
#include "common/assert.h"
+#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -23,10 +26,12 @@ void ReadableEvent::Acquire(Thread* thread) {
}
void ReadableEvent::Signal() {
- if (!is_signaled) {
- is_signaled = true;
- SynchronizationObject::Signal();
- };
+ if (is_signaled) {
+ return;
+ }
+
+ is_signaled = true;
+ SynchronizationObject::Signal();
}
void ReadableEvent::Clear() {
@@ -34,7 +39,10 @@ void ReadableEvent::Clear() {
}
ResultCode ReadableEvent::Reset() {
+ SchedulerLock lock(kernel);
if (!is_signaled) {
+ LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}",
+ GetObjectId(), GetTypeName(), GetName());
return ERR_INVALID_STATE;
}
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index b53423462..212e442f4 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -16,26 +16,58 @@ constexpr std::size_t ResourceTypeToIndex(ResourceType type) {
ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {}
ResourceLimit::~ResourceLimit() = default;
+bool ResourceLimit::Reserve(ResourceType resource, s64 amount) {
+ return Reserve(resource, amount, 10000000000);
+}
+
+bool ResourceLimit::Reserve(ResourceType resource, s64 amount, u64 timeout) {
+ const std::size_t index{ResourceTypeToIndex(resource)};
+
+ s64 new_value = current[index] + amount;
+ if (new_value > limit[index] && available[index] + amount <= limit[index]) {
+ // TODO(bunnei): This is wrong for multicore, we should wait the calling thread for timeout
+ new_value = current[index] + amount;
+ }
+
+ if (new_value <= limit[index]) {
+ current[index] = new_value;
+ return true;
+ }
+ return false;
+}
+
+void ResourceLimit::Release(ResourceType resource, u64 amount) {
+ Release(resource, amount, amount);
+}
+
+void ResourceLimit::Release(ResourceType resource, u64 used_amount, u64 available_amount) {
+ const std::size_t index{ResourceTypeToIndex(resource)};
+
+ current[index] -= used_amount;
+ available[index] -= available_amount;
+}
+
std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel) {
return std::make_shared<ResourceLimit>(kernel);
}
s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
- return values.at(ResourceTypeToIndex(resource));
+ return limit.at(ResourceTypeToIndex(resource)) - current.at(ResourceTypeToIndex(resource));
}
s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
- return limits.at(ResourceTypeToIndex(resource));
+ return limit.at(ResourceTypeToIndex(resource));
}
ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) {
- const auto index = ResourceTypeToIndex(resource);
-
- if (value < values[index]) {
+ const std::size_t index{ResourceTypeToIndex(resource)};
+ if (current[index] <= value) {
+ limit[index] = value;
+ return RESULT_SUCCESS;
+ } else {
+ LOG_ERROR(Kernel, "Limit value is too large! resource={}, value={}, index={}",
+ static_cast<u32>(resource), value, index);
return ERR_INVALID_STATE;
}
-
- values[index] = value;
- return RESULT_SUCCESS;
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 53b89e621..936cc4d0f 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -51,6 +51,11 @@ public:
return HANDLE_TYPE;
}
+ bool Reserve(ResourceType resource, s64 amount);
+ bool Reserve(ResourceType resource, s64 amount, u64 timeout);
+ void Release(ResourceType resource, u64 amount);
+ void Release(ResourceType resource, u64 used_amount, u64 available_amount);
+
/**
* Gets the current value for the specified resource.
* @param resource Requested resource type
@@ -91,10 +96,9 @@ private:
using ResourceArray =
std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>;
- /// Maximum values a resource type may reach.
- ResourceArray limits{};
- /// Current resource limit values.
- ResourceArray values{};
+ ResourceArray limit{};
+ ResourceArray current{};
+ ResourceArray available{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 1140c72a3..6b7db5372 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -6,16 +6,21 @@
// licensed under GPLv2 or later under exception provided by the author.
#include <algorithm>
+#include <mutex>
#include <set>
#include <unordered_set>
#include <utility>
#include "common/assert.h"
+#include "common/bit_util.h"
+#include "common/fiber.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/cpu_manager.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/time_manager.h"
@@ -27,103 +32,149 @@ GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {}
GlobalScheduler::~GlobalScheduler() = default;
void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) {
+ std::scoped_lock lock{global_list_guard};
thread_list.push_back(std::move(thread));
}
void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) {
+ std::scoped_lock lock{global_list_guard};
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
thread_list.end());
}
-void GlobalScheduler::UnloadThread(std::size_t core) {
- Scheduler& sched = kernel.Scheduler(core);
- sched.UnloadThread();
-}
-
-void GlobalScheduler::SelectThread(std::size_t core) {
+u32 GlobalScheduler::SelectThreads() {
+ ASSERT(is_locked);
const auto update_thread = [](Thread* thread, Scheduler& sched) {
- if (thread != sched.selected_thread.get()) {
+ std::scoped_lock lock{sched.guard};
+ if (thread != sched.selected_thread_set.get()) {
if (thread == nullptr) {
++sched.idle_selection_count;
}
- sched.selected_thread = SharedFrom(thread);
+ sched.selected_thread_set = SharedFrom(thread);
}
- sched.is_context_switch_pending = sched.selected_thread != sched.current_thread;
+ const bool reschedule_pending =
+ sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread);
+ sched.is_context_switch_pending = reschedule_pending;
std::atomic_thread_fence(std::memory_order_seq_cst);
+ return reschedule_pending;
};
- Scheduler& sched = kernel.Scheduler(core);
- Thread* current_thread = nullptr;
- // Step 1: Get top thread in schedule queue.
- current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
- if (current_thread) {
- update_thread(current_thread, sched);
- return;
+ if (!is_reselection_pending.load()) {
+ return 0;
}
- // Step 2: Try selecting a suggested thread.
- Thread* winner = nullptr;
- std::set<s32> sug_cores;
- for (auto thread : suggested_queue[core]) {
- s32 this_core = thread->GetProcessorID();
- Thread* thread_on_core = nullptr;
- if (this_core >= 0) {
- thread_on_core = scheduled_queue[this_core].front();
- }
- if (this_core < 0 || thread != thread_on_core) {
- winner = thread;
- break;
+ std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{};
+
+ u32 idle_cores{};
+
+ // Step 1: Get top thread in schedule queue.
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ Thread* top_thread =
+ scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
+ if (top_thread != nullptr) {
+ // TODO(Blinkhawk): Implement Thread Pinning
+ } else {
+ idle_cores |= (1U << core);
}
- sug_cores.insert(this_core);
+ top_threads[core] = top_thread;
}
- // if we got a suggested thread, select it, else do a second pass.
- if (winner && winner->GetPriority() > 2) {
- if (winner->IsRunning()) {
- UnloadThread(static_cast<u32>(winner->GetProcessorID()));
+
+ while (idle_cores != 0) {
+ u32 core_id = Common::CountTrailingZeroes32(idle_cores);
+
+ if (!suggested_queue[core_id].empty()) {
+ std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{};
+ std::size_t num_candidates = 0;
+ auto iter = suggested_queue[core_id].begin();
+ Thread* suggested = nullptr;
+ // Step 2: Try selecting a suggested thread.
+ while (iter != suggested_queue[core_id].end()) {
+ suggested = *iter;
+ iter++;
+ s32 suggested_core_id = suggested->GetProcessorID();
+ Thread* top_thread =
+ suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr;
+ if (top_thread != suggested) {
+ if (top_thread != nullptr &&
+ top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) {
+ suggested = nullptr;
+ break;
+ // There's a too high thread to do core migration, cancel
+ }
+ TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested);
+ break;
+ }
+ suggested = nullptr;
+ migration_candidates[num_candidates++] = suggested_core_id;
+ }
+ // Step 3: Select a suggested thread from another core
+ if (suggested == nullptr) {
+ for (std::size_t i = 0; i < num_candidates; i++) {
+ s32 candidate_core = migration_candidates[i];
+ suggested = top_threads[candidate_core];
+ auto it = scheduled_queue[candidate_core].begin();
+ it++;
+ Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr;
+ if (next != nullptr) {
+ TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id),
+ suggested);
+ top_threads[candidate_core] = next;
+ break;
+ } else {
+ suggested = nullptr;
+ }
+ }
+ }
+ top_threads[core_id] = suggested;
}
- TransferToCore(winner->GetPriority(), static_cast<s32>(core), winner);
- update_thread(winner, sched);
- return;
+
+ idle_cores &= ~(1U << core_id);
}
- // Step 3: Select a suggested thread from another core
- for (auto& src_core : sug_cores) {
- auto it = scheduled_queue[src_core].begin();
- it++;
- if (it != scheduled_queue[src_core].end()) {
- Thread* thread_on_core = scheduled_queue[src_core].front();
- Thread* to_change = *it;
- if (thread_on_core->IsRunning() || to_change->IsRunning()) {
- UnloadThread(static_cast<u32>(src_core));
- }
- TransferToCore(thread_on_core->GetPriority(), static_cast<s32>(core), thread_on_core);
- current_thread = thread_on_core;
- break;
+ u32 cores_needing_context_switch{};
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ Scheduler& sched = kernel.Scheduler(core);
+ ASSERT(top_threads[core] == nullptr ||
+ static_cast<u32>(top_threads[core]->GetProcessorID()) == core);
+ if (update_thread(top_threads[core], sched)) {
+ cores_needing_context_switch |= (1U << core);
}
}
- update_thread(current_thread, sched);
+ return cores_needing_context_switch;
}
bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
+ ASSERT(is_locked);
// Note: caller should use critical section, etc.
+ if (!yielding_thread->IsRunnable()) {
+ // Normally this case shouldn't happen except for SetThreadActivity.
+ is_reselection_pending.store(true, std::memory_order_release);
+ return false;
+ }
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority();
// Yield the thread
- const Thread* const winner = scheduled_queue[core_id].front(priority);
- ASSERT_MSG(yielding_thread == winner, "Thread yielding without being in front");
- scheduled_queue[core_id].yield(priority);
+ Reschedule(priority, core_id, yielding_thread);
+ const Thread* const winner = scheduled_queue[core_id].front();
+ if (kernel.GetCurrentHostThreadID() != core_id) {
+ is_reselection_pending.store(true, std::memory_order_release);
+ }
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
+ ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc.
+ if (!yielding_thread->IsRunnable()) {
+ // Normally this case shouldn't happen except for SetThreadActivity.
+ is_reselection_pending.store(true, std::memory_order_release);
+ return false;
+ }
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority();
// Yield the thread
- ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority),
- "Thread yielding without being in front");
- scheduled_queue[core_id].yield(priority);
+ Reschedule(priority, core_id, yielding_thread);
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
for (std::size_t i = 0; i < current_threads.size(); i++) {
@@ -153,21 +204,28 @@ bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
if (winner != nullptr) {
if (winner != yielding_thread) {
- if (winner->IsRunning()) {
- UnloadThread(static_cast<u32>(winner->GetProcessorID()));
- }
TransferToCore(winner->GetPriority(), s32(core_id), winner);
}
} else {
winner = next_thread;
}
+ if (kernel.GetCurrentHostThreadID() != core_id) {
+ is_reselection_pending.store(true, std::memory_order_release);
+ }
+
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
+ ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc.
+ if (!yielding_thread->IsRunnable()) {
+ // Normally this case shouldn't happen except for SetThreadActivity.
+ is_reselection_pending.store(true, std::memory_order_release);
+ return false;
+ }
Thread* winner = nullptr;
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
@@ -195,25 +253,31 @@ bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread
}
if (winner != nullptr) {
if (winner != yielding_thread) {
- if (winner->IsRunning()) {
- UnloadThread(static_cast<u32>(winner->GetProcessorID()));
- }
TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner);
}
} else {
winner = yielding_thread;
}
+ } else {
+ winner = scheduled_queue[core_id].front();
+ }
+
+ if (kernel.GetCurrentHostThreadID() != core_id) {
+ is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
void GlobalScheduler::PreemptThreads() {
+ ASSERT(is_locked);
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id];
if (scheduled_queue[core_id].size(priority) > 0) {
- scheduled_queue[core_id].front(priority)->IncrementYieldCount();
+ if (scheduled_queue[core_id].size(priority) > 1) {
+ scheduled_queue[core_id].front(priority)->IncrementYieldCount();
+ }
scheduled_queue[core_id].yield(priority);
if (scheduled_queue[core_id].size(priority) > 1) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
@@ -247,9 +311,6 @@ void GlobalScheduler::PreemptThreads() {
}
if (winner != nullptr) {
- if (winner->IsRunning()) {
- UnloadThread(static_cast<u32>(winner->GetProcessorID()));
- }
TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread =
winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
@@ -280,9 +341,6 @@ void GlobalScheduler::PreemptThreads() {
}
if (winner != nullptr) {
- if (winner->IsRunning()) {
- UnloadThread(static_cast<u32>(winner->GetProcessorID()));
- }
TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread = winner;
}
@@ -292,34 +350,65 @@ void GlobalScheduler::PreemptThreads() {
}
}
+void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule,
+ Core::EmuThreadHandle global_thread) {
+ u32 current_core = global_thread.host_handle;
+ bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
+ (current_core < Core::Hardware::NUM_CPU_CORES);
+ while (cores_pending_reschedule != 0) {
+ u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule);
+ ASSERT(core < Core::Hardware::NUM_CPU_CORES);
+ if (!must_context_switch || core != current_core) {
+ auto& phys_core = kernel.PhysicalCore(core);
+ phys_core.Interrupt();
+ } else {
+ must_context_switch = true;
+ }
+ cores_pending_reschedule &= ~(1U << core);
+ }
+ if (must_context_switch) {
+ auto& core_scheduler = kernel.CurrentScheduler();
+ kernel.ExitSVCProfile();
+ core_scheduler.TryDoContextSwitch();
+ kernel.EnterSVCProfile();
+ }
+}
+
void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
suggested_queue[core].add(thread, priority);
}
void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
suggested_queue[core].remove(thread, priority);
}
void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority);
}
void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority, false);
}
void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority);
scheduled_queue[core].add(thread, priority);
}
void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) {
+ ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority);
}
void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
+ ASSERT(is_locked);
const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
const s32 source_core = thread->GetProcessorID();
if (source_core == destination_core || !schedulable) {
@@ -349,6 +438,108 @@ bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
}
}
+void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) {
+ if (old_flags == thread->scheduling_state) {
+ return;
+ }
+ ASSERT(is_locked);
+
+ if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) {
+ // In this case the thread was running, now it's pausing/exitting
+ if (thread->processor_id >= 0) {
+ Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
+ }
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (core != static_cast<u32>(thread->processor_id) &&
+ ((thread->affinity_mask >> core) & 1) != 0) {
+ Unsuggest(thread->current_priority, core, thread);
+ }
+ }
+ } else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
+ // The thread is now set to running from being stopped
+ if (thread->processor_id >= 0) {
+ Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
+ }
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (core != static_cast<u32>(thread->processor_id) &&
+ ((thread->affinity_mask >> core) & 1) != 0) {
+ Suggest(thread->current_priority, core, thread);
+ }
+ }
+ }
+
+ SetReselectionPending();
+}
+
+void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) {
+ if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) {
+ return;
+ }
+ ASSERT(is_locked);
+ if (thread->processor_id >= 0) {
+ Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread);
+ }
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (core != static_cast<u32>(thread->processor_id) &&
+ ((thread->affinity_mask >> core) & 1) != 0) {
+ Unsuggest(old_priority, core, thread);
+ }
+ }
+
+ if (thread->processor_id >= 0) {
+ if (thread == kernel.CurrentScheduler().GetCurrentThread()) {
+ SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id),
+ thread);
+ } else {
+ Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
+ }
+ }
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (core != static_cast<u32>(thread->processor_id) &&
+ ((thread->affinity_mask >> core) & 1) != 0) {
+ Suggest(thread->current_priority, core, thread);
+ }
+ }
+ thread->IncrementYieldCount();
+ SetReselectionPending();
+}
+
+void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask,
+ s32 old_core) {
+ if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) ||
+ thread->current_priority >= THREADPRIO_COUNT) {
+ return;
+ }
+ ASSERT(is_locked);
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (((old_affinity_mask >> core) & 1) != 0) {
+ if (core == static_cast<u32>(old_core)) {
+ Unschedule(thread->current_priority, core, thread);
+ } else {
+ Unsuggest(thread->current_priority, core, thread);
+ }
+ }
+ }
+
+ for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
+ if (((thread->affinity_mask >> core) & 1) != 0) {
+ if (core == static_cast<u32>(thread->processor_id)) {
+ Schedule(thread->current_priority, core, thread);
+ } else {
+ Suggest(thread->current_priority, core, thread);
+ }
+ }
+ }
+
+ thread->IncrementYieldCount();
+ SetReselectionPending();
+}
+
void GlobalScheduler::Shutdown() {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
scheduled_queue[core].clear();
@@ -359,10 +550,12 @@ void GlobalScheduler::Shutdown() {
void GlobalScheduler::Lock() {
Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID();
+ ASSERT(!current_thread.IsInvalid());
if (current_thread == current_owner) {
++scope_lock;
} else {
inner_lock.lock();
+ is_locked = true;
current_owner = current_thread;
ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle());
scope_lock = 1;
@@ -374,17 +567,18 @@ void GlobalScheduler::Unlock() {
ASSERT(scope_lock > 0);
return;
}
- for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- SelectThread(i);
- }
+ u32 cores_pending_reschedule = SelectThreads();
+ Core::EmuThreadHandle leaving_thread = current_owner;
current_owner = Core::EmuThreadHandle::InvalidHandle();
scope_lock = 1;
+ is_locked = false;
inner_lock.unlock();
- // TODO(Blinkhawk): Setup the interrupts and change context on current core.
+ EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread);
}
-Scheduler::Scheduler(Core::System& system, std::size_t core_id)
- : system{system}, core_id{core_id} {}
+Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) {
+ switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this);
+}
Scheduler::~Scheduler() = default;
@@ -393,56 +587,122 @@ bool Scheduler::HaveReadyThreads() const {
}
Thread* Scheduler::GetCurrentThread() const {
- return current_thread.get();
+ if (current_thread) {
+ return current_thread.get();
+ }
+ return idle_thread.get();
}
Thread* Scheduler::GetSelectedThread() const {
return selected_thread.get();
}
-void Scheduler::SelectThreads() {
- system.GlobalScheduler().SelectThread(core_id);
-}
-
u64 Scheduler::GetLastContextSwitchTicks() const {
return last_context_switch_time;
}
void Scheduler::TryDoContextSwitch() {
+ auto& phys_core = system.Kernel().CurrentPhysicalCore();
+ if (phys_core.IsInterrupted()) {
+ phys_core.ClearInterrupt();
+ }
+ guard.lock();
if (is_context_switch_pending) {
SwitchContext();
+ } else {
+ guard.unlock();
}
}
-void Scheduler::UnloadThread() {
- Thread* const previous_thread = GetCurrentThread();
- Process* const previous_process = system.Kernel().CurrentProcess();
+void Scheduler::OnThreadStart() {
+ SwitchContextStep2();
+}
- UpdateLastContextSwitchTime(previous_thread, previous_process);
+void Scheduler::Unload() {
+ Thread* thread = current_thread.get();
+ if (thread) {
+ thread->SetContinuousOnSVC(false);
+ thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
+ thread->SetIsRunning(false);
+ if (!thread->IsHLEThread() && !thread->HasExited()) {
+ Core::ARM_Interface& cpu_core = thread->ArmInterface();
+ cpu_core.SaveContext(thread->GetContext32());
+ cpu_core.SaveContext(thread->GetContext64());
+ // Save the TPIDR_EL0 system register in case it was modified.
+ thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
+ cpu_core.ClearExclusiveState();
+ }
+ thread->context_guard.unlock();
+ }
+}
- // Save context for previous thread
- if (previous_thread) {
- system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32());
- system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64());
- // Save the TPIDR_EL0 system register in case it was modified.
- previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0());
+void Scheduler::Reload() {
+ Thread* thread = current_thread.get();
+ if (thread) {
+ ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
+ "Thread must be runnable.");
+
+ // Cancel any outstanding wakeup events for this thread
+ thread->SetIsRunning(true);
+ thread->SetWasRunning(false);
+ thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
- if (previous_thread->GetStatus() == ThreadStatus::Running) {
- // This is only the case when a reschedule is triggered without the current thread
- // yielding execution (i.e. an event triggered, system core time-sliced, etc)
- previous_thread->SetStatus(ThreadStatus::Ready);
+ auto* const thread_owner_process = thread->GetOwnerProcess();
+ if (thread_owner_process != nullptr) {
+ system.Kernel().MakeCurrentProcess(thread_owner_process);
+ }
+ if (!thread->IsHLEThread()) {
+ Core::ARM_Interface& cpu_core = thread->ArmInterface();
+ cpu_core.LoadContext(thread->GetContext32());
+ cpu_core.LoadContext(thread->GetContext64());
+ cpu_core.SetTlsAddress(thread->GetTLSAddress());
+ cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
+ cpu_core.ChangeProcessorID(this->core_id);
+ cpu_core.ClearExclusiveState();
}
- previous_thread->SetIsRunning(false);
}
- current_thread = nullptr;
+}
+
+void Scheduler::SwitchContextStep2() {
+ // Load context of new thread
+ if (selected_thread) {
+ ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
+ "Thread must be runnable.");
+
+ // Cancel any outstanding wakeup events for this thread
+ selected_thread->SetIsRunning(true);
+ selected_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
+ selected_thread->SetWasRunning(false);
+
+ auto* const thread_owner_process = current_thread->GetOwnerProcess();
+ if (thread_owner_process != nullptr) {
+ system.Kernel().MakeCurrentProcess(thread_owner_process);
+ }
+ if (!selected_thread->IsHLEThread()) {
+ Core::ARM_Interface& cpu_core = selected_thread->ArmInterface();
+ cpu_core.LoadContext(selected_thread->GetContext32());
+ cpu_core.LoadContext(selected_thread->GetContext64());
+ cpu_core.SetTlsAddress(selected_thread->GetTLSAddress());
+ cpu_core.SetTPIDR_EL0(selected_thread->GetTPIDR_EL0());
+ cpu_core.ChangeProcessorID(this->core_id);
+ cpu_core.ClearExclusiveState();
+ }
+ }
+
+ TryDoContextSwitch();
}
void Scheduler::SwitchContext() {
- Thread* const previous_thread = GetCurrentThread();
- Thread* const new_thread = GetSelectedThread();
+ current_thread_prev = current_thread;
+ selected_thread = selected_thread_set;
+ Thread* previous_thread = current_thread_prev.get();
+ Thread* new_thread = selected_thread.get();
+ current_thread = selected_thread;
is_context_switch_pending = false;
+
if (new_thread == previous_thread) {
+ guard.unlock();
return;
}
@@ -452,51 +712,80 @@ void Scheduler::SwitchContext() {
// Save context for previous thread
if (previous_thread) {
- system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32());
- system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64());
- // Save the TPIDR_EL0 system register in case it was modified.
- previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0());
-
- if (previous_thread->GetStatus() == ThreadStatus::Running) {
- // This is only the case when a reschedule is triggered without the current thread
- // yielding execution (i.e. an event triggered, system core time-sliced, etc)
- previous_thread->SetStatus(ThreadStatus::Ready);
+ if (new_thread != nullptr && new_thread->IsSuspendThread()) {
+ previous_thread->SetWasRunning(true);
}
+ previous_thread->SetContinuousOnSVC(false);
+ previous_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
previous_thread->SetIsRunning(false);
+ if (!previous_thread->IsHLEThread() && !previous_thread->HasExited()) {
+ Core::ARM_Interface& cpu_core = previous_thread->ArmInterface();
+ cpu_core.SaveContext(previous_thread->GetContext32());
+ cpu_core.SaveContext(previous_thread->GetContext64());
+ // Save the TPIDR_EL0 system register in case it was modified.
+ previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
+ cpu_core.ClearExclusiveState();
+ }
+ previous_thread->context_guard.unlock();
}
- // Load context of new thread
- if (new_thread) {
- ASSERT_MSG(new_thread->GetProcessorID() == s32(this->core_id),
- "Thread must be assigned to this core.");
- ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
- "Thread must be ready to become running.");
+ std::shared_ptr<Common::Fiber>* old_context;
+ if (previous_thread != nullptr) {
+ old_context = &previous_thread->GetHostContext();
+ } else {
+ old_context = &idle_thread->GetHostContext();
+ }
+ guard.unlock();
- // Cancel any outstanding wakeup events for this thread
- new_thread->CancelWakeupTimer();
- current_thread = SharedFrom(new_thread);
- new_thread->SetStatus(ThreadStatus::Running);
- new_thread->SetIsRunning(true);
+ Common::Fiber::YieldTo(*old_context, switch_fiber);
+ /// When a thread wakes up, the scheduler may have changed to other in another core.
+ auto& next_scheduler = system.Kernel().CurrentScheduler();
+ next_scheduler.SwitchContextStep2();
+}
- auto* const thread_owner_process = current_thread->GetOwnerProcess();
- if (previous_process != thread_owner_process) {
- system.Kernel().MakeCurrentProcess(thread_owner_process);
- }
+void Scheduler::OnSwitch(void* this_scheduler) {
+ Scheduler* sched = static_cast<Scheduler*>(this_scheduler);
+ sched->SwitchToCurrent();
+}
- system.ArmInterface(core_id).LoadContext(new_thread->GetContext32());
- system.ArmInterface(core_id).LoadContext(new_thread->GetContext64());
- system.ArmInterface(core_id).SetTlsAddress(new_thread->GetTLSAddress());
- system.ArmInterface(core_id).SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
- } else {
- current_thread = nullptr;
- // Note: We do not reset the current process and current page table when idling because
- // technically we haven't changed processes, our threads are just paused.
+void Scheduler::SwitchToCurrent() {
+ while (true) {
+ {
+ std::scoped_lock lock{guard};
+ selected_thread = selected_thread_set;
+ current_thread = selected_thread;
+ is_context_switch_pending = false;
+ }
+ const auto is_switch_pending = [this] {
+ std::scoped_lock lock{guard};
+ return is_context_switch_pending;
+ };
+ do {
+ if (current_thread != nullptr && !current_thread->IsHLEThread()) {
+ current_thread->context_guard.lock();
+ if (!current_thread->IsRunnable()) {
+ current_thread->context_guard.unlock();
+ break;
+ }
+ if (static_cast<u32>(current_thread->GetProcessorID()) != core_id) {
+ current_thread->context_guard.unlock();
+ break;
+ }
+ }
+ std::shared_ptr<Common::Fiber>* next_context;
+ if (current_thread != nullptr) {
+ next_context = &current_thread->GetHostContext();
+ } else {
+ next_context = &idle_thread->GetHostContext();
+ }
+ Common::Fiber::YieldTo(switch_fiber, *next_context);
+ } while (!is_switch_pending());
}
}
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time;
- const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks();
+ const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) {
@@ -510,6 +799,16 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
last_context_switch_time = most_recent_switch_ticks;
}
+void Scheduler::Initialize() {
+ std::string name = "Idle Thread Id:" + std::to_string(core_id);
+ std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
+ void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
+ ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
+ auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
+ nullptr, std::move(init_func), init_func_parameter);
+ idle_thread = std::move(thread_res).Unwrap();
+}
+
void Scheduler::Shutdown() {
current_thread = nullptr;
selected_thread = nullptr;
@@ -538,4 +837,13 @@ SchedulerLockAndSleep::~SchedulerLockAndSleep() {
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
}
+void SchedulerLockAndSleep::Release() {
+ if (sleep_cancelled) {
+ return;
+ }
+ auto& time_manager = kernel.TimeManager();
+ time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
+ sleep_cancelled = true;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 07df33f9c..b6f04dcea 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -11,9 +11,14 @@
#include "common/common_types.h"
#include "common/multi_level_queue.h"
+#include "common/spin_lock.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/thread.h"
+namespace Common {
+class Fiber;
+}
+
namespace Core {
class ARM_Interface;
class System;
@@ -41,41 +46,17 @@ public:
return thread_list;
}
- /**
- * Add a thread to the suggested queue of a cpu core. Suggested threads may be
- * picked if no thread is scheduled to run on the core.
- */
- void Suggest(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Remove a thread to the suggested queue of a cpu core. Suggested threads may be
- * picked if no thread is scheduled to run on the core.
- */
- void Unsuggest(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Add a thread to the scheduling queue of a cpu core. The thread is added at the
- * back the queue in its priority level.
- */
- void Schedule(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Add a thread to the scheduling queue of a cpu core. The thread is added at the
- * front the queue in its priority level.
- */
- void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
+ /// Notify the scheduler a thread's status has changed.
+ void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags);
- /// Reschedule an already scheduled thread based on a new priority
- void Reschedule(u32 priority, std::size_t core, Thread* thread);
-
- /// Unschedules a thread.
- void Unschedule(u32 priority, std::size_t core, Thread* thread);
+ /// Notify the scheduler a thread's priority has changed.
+ void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority);
- /// Selects a core and forces it to unload its current thread's context
- void UnloadThread(std::size_t core);
+ /// Notify the scheduler a thread's core and/or affinity mask has changed.
+ void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core);
/**
- * Takes care of selecting the new scheduled thread in three steps:
+ * Takes care of selecting the new scheduled threads in three steps:
*
* 1. First a thread is selected from the top of the priority queue. If no thread
* is obtained then we move to step two, else we are done.
@@ -85,8 +66,10 @@ public:
*
* 3. Third is no suggested thread is found, we do a second pass and pick a running
* thread in another core and swap it with its current thread.
+ *
+ * returns the cores needing scheduling.
*/
- void SelectThread(std::size_t core);
+ u32 SelectThreads();
bool HaveReadyThreads(std::size_t core_id) const {
return !scheduled_queue[core_id].empty();
@@ -149,6 +132,40 @@ private:
/// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling
/// and reschedules current core if needed.
void Unlock();
+
+ void EnableInterruptAndSchedule(u32 cores_pending_reschedule,
+ Core::EmuThreadHandle global_thread);
+
+ /**
+ * Add a thread to the suggested queue of a cpu core. Suggested threads may be
+ * picked if no thread is scheduled to run on the core.
+ */
+ void Suggest(u32 priority, std::size_t core, Thread* thread);
+
+ /**
+ * Remove a thread to the suggested queue of a cpu core. Suggested threads may be
+ * picked if no thread is scheduled to run on the core.
+ */
+ void Unsuggest(u32 priority, std::size_t core, Thread* thread);
+
+ /**
+ * Add a thread to the scheduling queue of a cpu core. The thread is added at the
+ * back the queue in its priority level.
+ */
+ void Schedule(u32 priority, std::size_t core, Thread* thread);
+
+ /**
+ * Add a thread to the scheduling queue of a cpu core. The thread is added at the
+ * front the queue in its priority level.
+ */
+ void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
+
+ /// Reschedule an already scheduled thread based on a new priority
+ void Reschedule(u32 priority, std::size_t core, Thread* thread);
+
+ /// Unschedules a thread.
+ void Unschedule(u32 priority, std::size_t core, Thread* thread);
+
/**
* Transfers a thread into an specific core. If the destination_core is -1
* it will be unscheduled from its source code and added into its suggested
@@ -170,10 +187,13 @@ private:
std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
/// Scheduler lock mechanisms.
- std::mutex inner_lock{}; // TODO(Blinkhawk): Replace for a SpinLock
+ bool is_locked{};
+ std::mutex inner_lock;
std::atomic<s64> scope_lock{};
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
+ Common::SpinLock global_list_guard{};
+
/// Lists all thread ids that aren't deleted/etc.
std::vector<std::shared_ptr<Thread>> thread_list;
KernelCore& kernel;
@@ -190,11 +210,11 @@ public:
/// Reschedules to the next available thread (call after current thread is suspended)
void TryDoContextSwitch();
- /// Unloads currently running thread
- void UnloadThread();
-
- /// Select the threads in top of the scheduling multilist.
- void SelectThreads();
+ /// The next two are for SingleCore Only.
+ /// Unload current thread before preempting core.
+ void Unload();
+ /// Reload current thread after core preemption.
+ void Reload();
/// Gets the current running thread
Thread* GetCurrentThread() const;
@@ -209,15 +229,30 @@ public:
return is_context_switch_pending;
}
+ void Initialize();
+
/// Shutdowns the scheduler.
void Shutdown();
+ void OnThreadStart();
+
+ std::shared_ptr<Common::Fiber>& ControlContext() {
+ return switch_fiber;
+ }
+
+ const std::shared_ptr<Common::Fiber>& ControlContext() const {
+ return switch_fiber;
+ }
+
private:
friend class GlobalScheduler;
/// Switches the CPU's active thread context to that of the specified thread
void SwitchContext();
+ /// When a thread wakes up, it must run this through it's new scheduler
+ void SwitchContextStep2();
+
/**
* Called on every context switch to update the internal timestamp
* This also updates the running time ticks for the given thread and
@@ -231,20 +266,30 @@ private:
*/
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
+ static void OnSwitch(void* this_scheduler);
+ void SwitchToCurrent();
+
std::shared_ptr<Thread> current_thread = nullptr;
std::shared_ptr<Thread> selected_thread = nullptr;
+ std::shared_ptr<Thread> current_thread_prev = nullptr;
+ std::shared_ptr<Thread> selected_thread_set = nullptr;
+ std::shared_ptr<Thread> idle_thread = nullptr;
+
+ std::shared_ptr<Common::Fiber> switch_fiber = nullptr;
Core::System& system;
u64 last_context_switch_time = 0;
u64 idle_selection_count = 0;
const std::size_t core_id;
+ Common::SpinLock guard{};
+
bool is_context_switch_pending = false;
};
class SchedulerLock {
public:
- explicit SchedulerLock(KernelCore& kernel);
+ [[nodiscard]] explicit SchedulerLock(KernelCore& kernel);
~SchedulerLock();
protected:
@@ -261,6 +306,8 @@ public:
sleep_cancelled = true;
}
+ void Release();
+
private:
Handle& event_handle;
Thread* time_task;
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 4604e35c5..8c19f2534 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -8,7 +8,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
@@ -17,6 +16,7 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
@@ -32,8 +32,10 @@ ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kern
std::string name) {
std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
- session->request_event = Core::Timing::CreateEvent(
- name, [session](u64 userdata, s64 cycles_late) { session->CompleteSyncRequest(); });
+ session->request_event =
+ Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) {
+ session->CompleteSyncRequest();
+ });
session->name = std::move(name);
session->parent = std::move(parent);
@@ -134,10 +136,11 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
return RESULT_SUCCESS;
}
-ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
+ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread,
+ Core::Memory::Memory& memory) {
u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
- std::shared_ptr<Kernel::HLERequestContext> context{
- std::make_shared<Kernel::HLERequestContext>(SharedFrom(this), std::move(thread))};
+ auto context =
+ std::make_shared<HLERequestContext>(kernel, memory, SharedFrom(this), std::move(thread));
context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
request_queue.Push(std::move(context));
@@ -167,9 +170,12 @@ ResultCode ServerSession::CompleteSyncRequest() {
}
// Some service requests require the thread to block
- if (!context.IsThreadWaiting()) {
- context.GetThread().ResumeFromWait();
- context.GetThread().SetWaitSynchronizationResult(result);
+ {
+ SchedulerLock lock(kernel);
+ if (!context.IsThreadWaiting()) {
+ context.GetThread().ResumeFromWait();
+ context.GetThread().SetSynchronizationResults(nullptr, result);
+ }
}
request_queue.Pop();
@@ -178,9 +184,12 @@ ResultCode ServerSession::CompleteSyncRequest() {
}
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
- Memory::Memory& memory) {
- Core::System::GetInstance().CoreTiming().ScheduleEvent(20000, request_event, {});
- return QueueSyncRequest(std::move(thread), memory);
+ Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
+ const ResultCode result = QueueSyncRequest(std::move(thread), memory);
+ const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
+ core_timing.ScheduleEvent(delay, request_event, {});
+ return result;
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 77e4f6721..d23e9ec68 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -13,13 +13,14 @@
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h"
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
namespace Core::Timing {
+class CoreTiming;
struct EventType;
-}
+} // namespace Core::Timing
namespace Kernel {
@@ -87,12 +88,14 @@ public:
/**
* Handle a sync request from the emulated application.
*
- * @param thread Thread that initiated the request.
- * @param memory Memory context to handle the sync request under.
+ * @param thread Thread that initiated the request.
+ * @param memory Memory context to handle the sync request under.
+ * @param core_timing Core timing context to schedule the request event under.
*
* @returns ResultCode from the operation.
*/
- ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
+ ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
bool ShouldWait(const Thread* thread) const override;
@@ -126,7 +129,7 @@ public:
private:
/// Queues a sync request from the emulated application.
- ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
+ ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
/// Completes a sync request from the emulated application.
ResultCode CompleteSyncRequest();
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index afb2e3fc2..0cd467110 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -2,149 +2,56 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <utility>
-
#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/hle/kernel/errors.h"
+#include "core/core.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/shared_memory.h"
namespace Kernel {
-SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {}
-SharedMemory::~SharedMemory() = default;
-
-std::shared_ptr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_process,
- u64 size, MemoryPermission permissions,
- MemoryPermission other_permissions,
- VAddr address, MemoryRegion region,
- std::string name) {
- std::shared_ptr<SharedMemory> shared_memory = std::make_shared<SharedMemory>(kernel);
-
- shared_memory->owner_process = owner_process;
- shared_memory->name = std::move(name);
- shared_memory->size = size;
- shared_memory->permissions = permissions;
- shared_memory->other_permissions = other_permissions;
-
- if (address == 0) {
- shared_memory->backing_block = std::make_shared<Kernel::PhysicalMemory>(size);
- shared_memory->backing_block_offset = 0;
-
- // Refresh the address mappings for the current process.
- if (kernel.CurrentProcess() != nullptr) {
- kernel.CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
- shared_memory->backing_block.get());
- }
- } else {
- const auto& vm_manager = shared_memory->owner_process->VMManager();
+SharedMemory::SharedMemory(KernelCore& kernel, Core::DeviceMemory& device_memory)
+ : Object{kernel}, device_memory{device_memory} {}
- // The memory is already available and mapped in the owner process.
- const auto vma = vm_manager.FindVMA(address);
- ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address");
- ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
-
- // The returned VMA might be a bigger one encompassing the desired address.
- const auto vma_offset = address - vma->first;
- ASSERT_MSG(vma_offset + size <= vma->second.size,
- "Shared memory exceeds bounds of mapped block");
-
- shared_memory->backing_block = vma->second.backing_block;
- shared_memory->backing_block_offset = vma->second.offset + vma_offset;
- }
-
- shared_memory->base_address = address;
+SharedMemory::~SharedMemory() = default;
- return shared_memory;
-}
+std::shared_ptr<SharedMemory> SharedMemory::Create(
+ KernelCore& kernel, Core::DeviceMemory& device_memory, Process* owner_process,
+ Memory::PageLinkedList&& page_list, Memory::MemoryPermission owner_permission,
+ Memory::MemoryPermission user_permission, PAddr physical_address, std::size_t size,
+ std::string name) {
-std::shared_ptr<SharedMemory> SharedMemory::CreateForApplet(
- KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset,
- u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
- std::shared_ptr<SharedMemory> shared_memory = std::make_shared<SharedMemory>(kernel);
+ std::shared_ptr<SharedMemory> shared_memory{
+ std::make_shared<SharedMemory>(kernel, device_memory)};
- shared_memory->owner_process = nullptr;
- shared_memory->name = std::move(name);
+ shared_memory->owner_process = owner_process;
+ shared_memory->page_list = std::move(page_list);
+ shared_memory->owner_permission = owner_permission;
+ shared_memory->user_permission = user_permission;
+ shared_memory->physical_address = physical_address;
shared_memory->size = size;
- shared_memory->permissions = permissions;
- shared_memory->other_permissions = other_permissions;
- shared_memory->backing_block = std::move(heap_block);
- shared_memory->backing_block_offset = offset;
- shared_memory->base_address =
- kernel.CurrentProcess()->VMManager().GetHeapRegionBaseAddress() + offset;
+ shared_memory->name = name;
return shared_memory;
}
-ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions,
- MemoryPermission other_permissions) {
- const MemoryPermission own_other_permissions =
- &target_process == owner_process ? this->permissions : this->other_permissions;
-
- // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
- if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
- return ERR_INVALID_MEMORY_PERMISSIONS;
- }
-
- // Error out if the requested permissions don't match what the creator process allows.
- if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
- GetObjectId(), address, name);
- return ERR_INVALID_MEMORY_PERMISSIONS;
- }
+ResultCode SharedMemory::Map(Process& target_process, VAddr address, std::size_t size,
+ Memory::MemoryPermission permissions) {
+ const u64 page_count{(size + Memory::PageSize - 1) / Memory::PageSize};
- // Error out if the provided permissions are not compatible with what the creator process needs.
- if (other_permissions != MemoryPermission::DontCare &&
- static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
- GetObjectId(), address, name);
- return ERR_INVALID_MEMORY_PERMISSIONS;
+ if (page_list.GetNumPages() != page_count) {
+ UNIMPLEMENTED_MSG("Page count does not match");
}
- VAddr target_address = address;
+ const Memory::MemoryPermission expected =
+ &target_process == owner_process ? owner_permission : user_permission;
- // Map the memory block into the target process
- auto result = target_process.VMManager().MapMemoryBlock(
- target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
- if (result.Failed()) {
- LOG_ERROR(
- Kernel,
- "cannot map id={}, target_address=0x{:X} name={}, error mapping to virtual memory",
- GetObjectId(), target_address, name);
- return result.Code();
+ if (permissions != expected) {
+ UNIMPLEMENTED_MSG("Permission does not match");
}
- return target_process.VMManager().ReprotectRange(target_address, size,
- ConvertPermissions(permissions));
-}
-
-ResultCode SharedMemory::Unmap(Process& target_process, VAddr address, u64 unmap_size) {
- if (unmap_size != size) {
- LOG_ERROR(Kernel,
- "Invalid size passed to Unmap. Size must be equal to the size of the "
- "memory managed. Shared memory size=0x{:016X}, Unmap size=0x{:016X}",
- size, unmap_size);
- return ERR_INVALID_SIZE;
- }
-
- // TODO(Subv): Verify what happens if the application tries to unmap an address that is not
- // mapped to a SharedMemory.
- return target_process.VMManager().UnmapRange(address, size);
-}
-
-VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
- u32 masked_permissions =
- static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
- return static_cast<VMAPermission>(masked_permissions);
-}
-
-u8* SharedMemory::GetPointer(std::size_t offset) {
- return backing_block->data() + backing_block_offset + offset;
-}
-
-const u8* SharedMemory::GetPointer(std::size_t offset) const {
- return backing_block->data() + backing_block_offset + offset;
+ return target_process.PageTable().MapPages(address, page_list, Memory::MemoryState::Shared,
+ permissions);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index 014951d82..0ef87235c 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -8,8 +8,10 @@
#include <string>
#include "common/common_types.h"
+#include "core/device_memory.h"
+#include "core/hle/kernel/memory/memory_block.h"
+#include "core/hle/kernel/memory/page_linked_list.h"
#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/physical_memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
@@ -17,63 +19,21 @@ namespace Kernel {
class KernelCore;
-/// Permissions for mapped shared memory blocks
-enum class MemoryPermission : u32 {
- None = 0,
- Read = (1u << 0),
- Write = (1u << 1),
- ReadWrite = (Read | Write),
- Execute = (1u << 2),
- ReadExecute = (Read | Execute),
- WriteExecute = (Write | Execute),
- ReadWriteExecute = (Read | Write | Execute),
- DontCare = (1u << 28)
-};
-
class SharedMemory final : public Object {
public:
- explicit SharedMemory(KernelCore& kernel);
+ explicit SharedMemory(KernelCore& kernel, Core::DeviceMemory& device_memory);
~SharedMemory() override;
- /**
- * Creates a shared memory object.
- * @param kernel The kernel instance to create a shared memory instance under.
- * @param owner_process Process that created this shared memory object.
- * @param size Size of the memory block. Must be page-aligned.
- * @param permissions Permission restrictions applied to the process which created the block.
- * @param other_permissions Permission restrictions applied to other processes mapping the
- * block.
- * @param address The address from which to map the Shared Memory.
- * @param region If the address is 0, the shared memory will be allocated in this region of the
- * linear heap.
- * @param name Optional object name, used for debugging purposes.
- */
- static std::shared_ptr<SharedMemory> Create(KernelCore& kernel, Process* owner_process,
- u64 size, MemoryPermission permissions,
- MemoryPermission other_permissions,
- VAddr address = 0,
- MemoryRegion region = MemoryRegion::BASE,
- std::string name = "Unknown");
-
- /**
- * Creates a shared memory object from a block of memory managed by an HLE applet.
- * @param kernel The kernel instance to create a shared memory instance under.
- * @param heap_block Heap block of the HLE applet.
- * @param offset The offset into the heap block that the SharedMemory will map.
- * @param size Size of the memory block. Must be page-aligned.
- * @param permissions Permission restrictions applied to the process which created the block.
- * @param other_permissions Permission restrictions applied to other processes mapping the
- * block.
- * @param name Optional object name, used for debugging purposes.
- */
- static std::shared_ptr<SharedMemory> CreateForApplet(
- KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset,
- u64 size, MemoryPermission permissions, MemoryPermission other_permissions,
- std::string name = "Unknown Applet");
+ static std::shared_ptr<SharedMemory> Create(
+ KernelCore& kernel, Core::DeviceMemory& device_memory, Process* owner_process,
+ Memory::PageLinkedList&& page_list, Memory::MemoryPermission owner_permission,
+ Memory::MemoryPermission user_permission, PAddr physical_address, std::size_t size,
+ std::string name);
std::string GetTypeName() const override {
return "SharedMemory";
}
+
std::string GetName() const override {
return name;
}
@@ -83,71 +43,42 @@ public:
return HANDLE_TYPE;
}
- /// Gets the size of the underlying memory block in bytes.
- u64 GetSize() const {
- return size;
- }
-
- /**
- * Converts the specified MemoryPermission into the equivalent VMAPermission.
- * @param permission The MemoryPermission to convert.
- */
- static VMAPermission ConvertPermissions(MemoryPermission permission);
-
/**
* Maps a shared memory block to an address in the target process' address space
- * @param target_process Process on which to map the memory block.
+ * @param target_process Process on which to map the memory block
* @param address Address in system memory to map shared memory block to
+ * @param size Size of the shared memory block to map
* @param permissions Memory block map permissions (specified by SVC field)
- * @param other_permissions Memory block map other permissions (specified by SVC field)
- */
- ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions,
- MemoryPermission other_permissions);
-
- /**
- * Unmaps a shared memory block from the specified address in system memory
- *
- * @param target_process Process from which to unmap the memory block.
- * @param address Address in system memory where the shared memory block is mapped.
- * @param unmap_size The amount of bytes to unmap from this shared memory instance.
- *
- * @return Result code of the unmap operation
- *
- * @pre The given size to unmap must be the same size as the amount of memory managed by
- * the SharedMemory instance itself, otherwise ERR_INVALID_SIZE will be returned.
*/
- ResultCode Unmap(Process& target_process, VAddr address, u64 unmap_size);
+ ResultCode Map(Process& target_process, VAddr address, std::size_t size,
+ Memory::MemoryPermission permissions);
/**
* Gets a pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
* @return A pointer to the shared memory block from the specified offset
*/
- u8* GetPointer(std::size_t offset = 0);
+ u8* GetPointer(std::size_t offset = 0) {
+ return device_memory.GetPointer(physical_address + offset);
+ }
/**
- * Gets a constant pointer to the shared memory block
+ * Gets a pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
- * @return A constant pointer to the shared memory block from the specified offset
+ * @return A pointer to the shared memory block from the specified offset
*/
- const u8* GetPointer(std::size_t offset = 0) const;
+ const u8* GetPointer(std::size_t offset = 0) const {
+ return device_memory.GetPointer(physical_address + offset);
+ }
private:
- /// Backing memory for this shared memory block.
- std::shared_ptr<PhysicalMemory> backing_block;
- /// Offset into the backing block for this shared memory.
- std::size_t backing_block_offset = 0;
- /// Size of the memory block. Page-aligned.
- u64 size = 0;
- /// Permission restrictions applied to the process which created the block.
- MemoryPermission permissions{};
- /// Permission restrictions applied to other processes mapping the block.
- MemoryPermission other_permissions{};
- /// Process that created this shared memory block.
- Process* owner_process;
- /// Address of shared memory block in the owner process if specified.
- VAddr base_address = 0;
- /// Name of shared memory object.
+ Core::DeviceMemory& device_memory;
+ Process* owner_process{};
+ Memory::PageLinkedList page_list;
+ Memory::MemoryPermission owner_permission{};
+ Memory::MemoryPermission user_permission{};
+ PAddr physical_address{};
+ std::size_t size{};
std::string name;
};
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 4ffc113c2..e3b770d66 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -10,30 +10,36 @@
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/fiber.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
+#include "core/cpu_manager.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/memory_block.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/mutex.h"
+#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/svc.h"
+#include "core/hle/kernel/svc_types.h"
#include "core/hle/kernel/svc_wrap.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/time_manager.h"
#include "core/hle/kernel/transfer_memory.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/lock.h"
@@ -42,7 +48,7 @@
#include "core/memory.h"
#include "core/reporter.h"
-namespace Kernel {
+namespace Kernel::Svc {
namespace {
// Checks if address + size is greater than the given address
@@ -52,14 +58,11 @@ constexpr bool IsValidAddressRange(VAddr address, u64 size) {
return address + size > address;
}
-// 8 GiB
-constexpr u64 MAIN_MEMORY_SIZE = 0x200000000;
-
// Helper function that performs the common sanity checks for svcMapMemory
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
// in the same order.
-ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
- u64 size) {
+ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr dst_addr,
+ VAddr src_addr, u64 size) {
if (!Common::Is4KBAligned(dst_addr)) {
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr);
return ERR_INVALID_ADDRESS;
@@ -93,36 +96,33 @@ ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_add
return ERR_INVALID_ADDRESS_STATE;
}
- if (!vm_manager.IsWithinAddressSpace(src_addr, size)) {
+ if (!manager.IsInsideAddressSpace(src_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Source is not within the address space, addr=0x{:016X}, size=0x{:016X}",
src_addr, size);
return ERR_INVALID_ADDRESS_STATE;
}
- if (!vm_manager.IsWithinStackRegion(dst_addr, size)) {
+ if (manager.IsOutsideStackRegion(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}",
dst_addr, size);
return ERR_INVALID_MEMORY_RANGE;
}
- const VAddr dst_end_address = dst_addr + size;
- if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() &&
- vm_manager.GetHeapRegionEndAddress() > dst_addr) {
+ if (manager.IsInsideHeapRegion(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Destination does not fit within the heap region, addr=0x{:016X}, "
- "size=0x{:016X}, end_addr=0x{:016X}",
- dst_addr, size, dst_end_address);
+ "size=0x{:016X}",
+ dst_addr, size);
return ERR_INVALID_MEMORY_RANGE;
}
- if (dst_end_address > vm_manager.GetMapRegionBaseAddress() &&
- vm_manager.GetMapRegionEndAddress() > dst_addr) {
+ if (manager.IsInsideAliasRegion(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Destination does not fit within the map region, addr=0x{:016X}, "
- "size=0x{:016X}, end_addr=0x{:016X}",
- dst_addr, size, dst_end_address);
+ "size=0x{:016X}",
+ dst_addr, size);
return ERR_INVALID_MEMORY_RANGE;
}
@@ -136,6 +136,7 @@ enum class ResourceLimitValueType {
ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit,
u32 resource_type, ResourceLimitValueType value_type) {
+ std::lock_guard lock{HLE::g_hle_lock};
const auto type = static_cast<ResourceType>(resource_type);
if (!IsValidResourceType(type)) {
LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type);
@@ -163,6 +164,7 @@ ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_
/// Set the process heap to a given Size. It can both extend and shrink the heap.
static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_size) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size);
// Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB.
@@ -177,13 +179,10 @@ static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_s
return ERR_INVALID_SIZE;
}
- auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
- const auto alloc_result = vm_manager.SetHeapSize(heap_size);
- if (alloc_result.Failed()) {
- return alloc_result.Code();
- }
+ auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
+
+ CASCADE_RESULT(*heap_addr, page_table.SetHeapSize(heap_size));
- *heap_addr = *alloc_result;
return RESULT_SUCCESS;
}
@@ -194,65 +193,9 @@ static ResultCode SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_s
return result;
}
-static ResultCode SetMemoryPermission(Core::System& system, VAddr addr, u64 size, u32 prot) {
- LOG_TRACE(Kernel_SVC, "called, addr=0x{:X}, size=0x{:X}, prot=0x{:X}", addr, size, prot);
-
- if (!Common::Is4KBAligned(addr)) {
- LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr);
- return ERR_INVALID_ADDRESS;
- }
-
- if (size == 0) {
- LOG_ERROR(Kernel_SVC, "Size is 0");
- return ERR_INVALID_SIZE;
- }
-
- if (!Common::Is4KBAligned(size)) {
- LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size);
- return ERR_INVALID_SIZE;
- }
-
- if (!IsValidAddressRange(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
- addr, size);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const auto permission = static_cast<MemoryPermission>(prot);
- if (permission != MemoryPermission::None && permission != MemoryPermission::Read &&
- permission != MemoryPermission::ReadWrite) {
- LOG_ERROR(Kernel_SVC, "Invalid memory permission specified, Got memory permission=0x{:08X}",
- static_cast<u32>(permission));
- return ERR_INVALID_MEMORY_PERMISSIONS;
- }
-
- auto* const current_process = system.Kernel().CurrentProcess();
- auto& vm_manager = current_process->VMManager();
-
- if (!vm_manager.IsWithinAddressSpace(addr, size)) {
- LOG_ERROR(Kernel_SVC,
- "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
- size);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const VMManager::VMAHandle iter = vm_manager.FindVMA(addr);
- if (!vm_manager.IsValidHandle(iter)) {
- LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- LOG_WARNING(Kernel_SVC, "Uniformity check on protected memory is not implemented.");
- // TODO: Performs a uniformity check to make sure only protected memory is changed (it doesn't
- // make sense to allow changing permissions on kernel memory itself, etc).
-
- const auto converted_permissions = SharedMemory::ConvertPermissions(permission);
-
- return vm_manager.ReprotectRange(addr, size, converted_permissions);
-}
-
static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask,
u32 attribute) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC,
"called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
size, mask, attribute);
@@ -274,72 +217,73 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
return ERR_INVALID_ADDRESS_STATE;
}
- const auto mem_attribute = static_cast<MemoryAttribute>(attribute);
- const auto mem_mask = static_cast<MemoryAttribute>(mask);
- const auto attribute_with_mask = mem_attribute | mem_mask;
-
- if (attribute_with_mask != mem_mask) {
+ const auto attributes{static_cast<Memory::MemoryAttribute>(mask | attribute)};
+ if (attributes != static_cast<Memory::MemoryAttribute>(mask) ||
+ (attributes | Memory::MemoryAttribute::Uncached) != Memory::MemoryAttribute::Uncached) {
LOG_ERROR(Kernel_SVC,
"Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}",
attribute, mask);
return ERR_INVALID_COMBINATION;
}
- if ((attribute_with_mask | MemoryAttribute::Uncached) != MemoryAttribute::Uncached) {
- LOG_ERROR(Kernel_SVC, "Specified attribute isn't equal to MemoryAttributeUncached (8).");
- return ERR_INVALID_COMBINATION;
- }
+ auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
- auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
- if (!vm_manager.IsWithinAddressSpace(address, size)) {
- LOG_ERROR(Kernel_SVC,
- "Given address (0x{:016X}) is outside the bounds of the address space.", address);
- return ERR_INVALID_ADDRESS_STATE;
- }
+ return page_table.SetMemoryAttribute(address, size, static_cast<Memory::MemoryAttribute>(mask),
+ static_cast<Memory::MemoryAttribute>(attribute));
+}
- return vm_manager.SetMemoryAttribute(address, size, mem_mask, mem_attribute);
+static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
+ u32 attribute) {
+ return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size),
+ mask, attribute);
}
/// Maps a memory range into a different range.
static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
- const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
+ auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
- if (result.IsError()) {
+ if (const ResultCode result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
+ result.IsError()) {
return result;
}
- return vm_manager.MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack);
+ return page_table.Map(dst_addr, src_addr, size);
+}
+
+static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
+ return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
+ static_cast<std::size_t>(size));
}
/// Unmaps a region that was previously mapped with svcMapMemory
static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
- const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
+ auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
- if (result.IsError()) {
+ if (const ResultCode result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
+ result.IsError()) {
return result;
}
- const auto unmap_res = vm_manager.UnmapRange(dst_addr, size);
-
- // Reprotect the source mapping on success
- if (unmap_res.IsSuccess()) {
- ASSERT(vm_manager.ReprotectRange(src_addr, size, VMAPermission::ReadWrite).IsSuccess());
- }
+ return page_table.Unmap(dst_addr, src_addr, size);
+}
- return unmap_res;
+static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
+ return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
+ static_cast<std::size_t>(size));
}
/// Connect to an OS service given the port name, returns the handle to the port to out
static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
VAddr port_name_address) {
+ std::lock_guard lock{HLE::g_hle_lock};
auto& memory = system.Memory();
if (!memory.IsValidVirtualAddress(port_name_address)) {
@@ -367,6 +311,8 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
return ERR_NOT_FOUND;
}
+ ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Sessions, 1));
+
auto client_port = it->second;
std::shared_ptr<ClientSession> client_session;
@@ -396,11 +342,30 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
auto thread = system.CurrentScheduler().GetCurrentThread();
- thread->InvalidateWakeupCallback();
- thread->SetStatus(ThreadStatus::WaitIPC);
- system.PrepareReschedule(thread->GetProcessorID());
+ {
+ SchedulerLock lock(system.Kernel());
+ thread->InvalidateHLECallback();
+ thread->SetStatus(ThreadStatus::WaitIPC);
+ session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
+ }
+
+ if (thread->HasHLECallback()) {
+ Handle event_handle = thread->GetHLETimeEvent();
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = system.Kernel().TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
+ }
+
+ {
+ SchedulerLock lock(system.Kernel());
+ auto* sync_object = thread->GetHLESyncObject();
+ sync_object->RemoveWaitingThread(SharedFrom(thread));
+ }
+
+ thread->InvokeHLECallback(SharedFrom(thread));
+ }
- return session->SendSyncRequest(SharedFrom(thread), system.Memory());
+ return thread->GetSignalingResult();
}
static ResultCode SendSyncRequest32(Core::System& system, Handle handle) {
@@ -462,6 +427,15 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
return ERR_INVALID_HANDLE;
}
+static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32* process_id_high,
+ Handle handle) {
+ u64 process_id{};
+ const auto result = GetProcessId(system, &process_id, handle);
+ *process_id_low = static_cast<u32>(process_id);
+ *process_id_high = static_cast<u32>(process_id >> 32);
+ return result;
+}
+
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address,
u64 handle_count, s64 nano_seconds) {
@@ -484,9 +458,7 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
return ERR_OUT_OF_RANGE;
}
- auto* const thread = system.CurrentScheduler().GetCurrentThread();
auto& kernel = system.Kernel();
- using ObjectPtr = Thread::ThreadSynchronizationObjects::value_type;
Thread::ThreadSynchronizationObjects objects(handle_count);
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
@@ -526,10 +498,13 @@ static ResultCode CancelSynchronization(Core::System& system, Handle thread_hand
}
thread->CancelWait();
- system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS;
}
+static ResultCode CancelSynchronization32(Core::System& system, Handle thread_handle) {
+ return CancelSynchronization(system, thread_handle);
+}
+
/// Attempts to locks a mutex, creating it if it does not already exist
static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_handle,
VAddr mutex_addr, Handle requesting_thread_handle) {
@@ -538,7 +513,7 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand
"requesting_current_thread_handle=0x{:08X}",
holding_thread_handle, mutex_addr, requesting_thread_handle);
- if (Memory::IsKernelVirtualAddress(mutex_addr)) {
+ if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}",
mutex_addr);
return ERR_INVALID_ADDRESS_STATE;
@@ -554,11 +529,17 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand
requesting_thread_handle);
}
+static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle,
+ u32 mutex_addr, Handle requesting_thread_handle) {
+ return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr),
+ requesting_thread_handle);
+}
+
/// Unlock a mutex
static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
- if (Memory::IsKernelVirtualAddress(mutex_addr)) {
+ if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}",
mutex_addr);
return ERR_INVALID_ADDRESS_STATE;
@@ -573,6 +554,10 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
return current_process->GetMutex().Release(mutex_addr);
}
+static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) {
+ return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr));
+}
+
enum class BreakType : u32 {
Panic = 0,
AssertionFailed = 1,
@@ -673,6 +658,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
if (!break_reason.signal_debugger) {
+ SchedulerLock lock(system.Kernel());
LOG_CRITICAL(
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@@ -683,18 +669,19 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
const auto thread_processor_id = current_thread->GetProcessorID();
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
- ASSERT(false);
-
- system.Kernel().CurrentProcess()->PrepareForTermination();
// Kill the current thread
+ system.Kernel().ExceptionalExit();
current_thread->Stop();
- system.PrepareReschedule();
}
}
+static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
+ Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2));
+}
+
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
-static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) {
+static void OutputDebugString(Core::System& system, VAddr address, u64 len) {
if (len == 0) {
return;
}
@@ -707,6 +694,7 @@ static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr addre
/// Gets system/memory information for the current process
static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 handle,
u64 info_sub_id) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
@@ -765,6 +753,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: {
if (info_sub_id != 0) {
+ LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
+ info_sub_id);
return ERR_INVALID_ENUM_VALUE;
}
@@ -772,6 +762,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
system.Kernel().CurrentProcess()->GetHandleTable();
const auto process = current_process_handle_table.Get<Process>(static_cast<Handle>(handle));
if (!process) {
+ LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}",
+ info_id, info_sub_id, handle);
return ERR_INVALID_HANDLE;
}
@@ -785,35 +777,35 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
return RESULT_SUCCESS;
case GetInfoType::MapRegionBaseAddr:
- *result = process->VMManager().GetMapRegionBaseAddress();
+ *result = process->PageTable().GetAliasRegionStart();
return RESULT_SUCCESS;
case GetInfoType::MapRegionSize:
- *result = process->VMManager().GetMapRegionSize();
+ *result = process->PageTable().GetAliasRegionSize();
return RESULT_SUCCESS;
case GetInfoType::HeapRegionBaseAddr:
- *result = process->VMManager().GetHeapRegionBaseAddress();
+ *result = process->PageTable().GetHeapRegionStart();
return RESULT_SUCCESS;
case GetInfoType::HeapRegionSize:
- *result = process->VMManager().GetHeapRegionSize();
+ *result = process->PageTable().GetHeapRegionSize();
return RESULT_SUCCESS;
case GetInfoType::ASLRRegionBaseAddr:
- *result = process->VMManager().GetASLRRegionBaseAddress();
+ *result = process->PageTable().GetAliasCodeRegionStart();
return RESULT_SUCCESS;
case GetInfoType::ASLRRegionSize:
- *result = process->VMManager().GetASLRRegionSize();
+ *result = process->PageTable().GetAliasCodeRegionSize();
return RESULT_SUCCESS;
case GetInfoType::StackRegionBaseAddr:
- *result = process->VMManager().GetStackRegionBaseAddress();
+ *result = process->PageTable().GetStackRegionStart();
return RESULT_SUCCESS;
case GetInfoType::StackRegionSize:
- *result = process->VMManager().GetStackRegionSize();
+ *result = process->PageTable().GetStackRegionSize();
return RESULT_SUCCESS;
case GetInfoType::TotalPhysicalMemoryAvailable:
@@ -853,7 +845,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
break;
}
- LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id);
+ LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
return ERR_INVALID_ENUM_VALUE;
}
@@ -863,10 +855,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
case GetInfoType::RegisterResourceLimit: {
if (handle != 0) {
+ LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle);
return ERR_INVALID_HANDLE;
}
if (info_sub_id != 0) {
+ LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
+ info_sub_id);
return ERR_INVALID_COMBINATION;
}
@@ -936,9 +931,9 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
- out_ticks = thread_ticks + (core_timing.GetTicks() - prev_ctx_ticks);
+ out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
- out_ticks = core_timing.GetTicks() - prev_ctx_ticks;
+ out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks;
}
*result = out_ticks;
@@ -946,7 +941,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
}
default:
- LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id);
+ LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
return ERR_INVALID_ENUM_VALUE;
}
}
@@ -965,6 +960,7 @@ static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_h
/// Maps memory at a desired address
static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) {
@@ -987,24 +983,38 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
return ERR_INVALID_MEMORY_RANGE;
}
- Process* const current_process = system.Kernel().CurrentProcess();
- auto& vm_manager = current_process->VMManager();
+ Process* const current_process{system.Kernel().CurrentProcess()};
+ auto& page_table{current_process->PageTable()};
if (current_process->GetSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
return ERR_INVALID_STATE;
}
- if (!vm_manager.IsWithinMapRegion(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Range not within map region");
+ if (!page_table.IsInsideAddressSpace(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
+ size);
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ if (page_table.IsOutsideAliasRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
+ size);
return ERR_INVALID_MEMORY_RANGE;
}
- return vm_manager.MapPhysicalMemory(addr, size);
+ return page_table.MapPhysicalMemory(addr, size);
+}
+
+static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
+ return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
}
/// Unmaps memory previously mapped via MapPhysicalMemory
static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) {
@@ -1027,20 +1037,33 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
return ERR_INVALID_MEMORY_RANGE;
}
- Process* const current_process = system.Kernel().CurrentProcess();
- auto& vm_manager = current_process->VMManager();
+ Process* const current_process{system.Kernel().CurrentProcess()};
+ auto& page_table{current_process->PageTable()};
if (current_process->GetSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
return ERR_INVALID_STATE;
}
- if (!vm_manager.IsWithinMapRegion(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Range not within map region");
+ if (!page_table.IsInsideAddressSpace(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
+ size);
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ if (page_table.IsOutsideAliasRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
+ size);
return ERR_INVALID_MEMORY_RANGE;
}
- return vm_manager.UnmapPhysicalMemory(addr, size);
+ return page_table.UnmapPhysicalMemory(addr, size);
+}
+
+static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
+ return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
}
/// Sets the thread activity
@@ -1072,10 +1095,11 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
return ERR_BUSY;
}
- thread->SetActivity(static_cast<ThreadActivity>(activity));
+ return thread->SetActivity(static_cast<ThreadActivity>(activity));
+}
- system.PrepareReschedule(thread->GetProcessorID());
- return RESULT_SUCCESS;
+static ResultCode SetThreadActivity32(Core::System& system, Handle handle, u32 activity) {
+ return SetThreadActivity(system, handle, activity);
}
/// Gets the thread context
@@ -1119,6 +1143,10 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
return RESULT_SUCCESS;
}
+static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) {
+ return GetThreadContext(system, static_cast<VAddr>(thread_context), handle);
+}
+
/// Gets the priority for the specified thread
static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) {
LOG_TRACE(Kernel_SVC, "called");
@@ -1126,6 +1154,7 @@ static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle);
if (!thread) {
+ *priority = 0;
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
return ERR_INVALID_HANDLE;
}
@@ -1160,18 +1189,26 @@ static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 pri
thread->SetPriority(priority);
- system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS;
}
+static ResultCode SetThreadPriority32(Core::System& system, Handle handle, u32 priority) {
+ return SetThreadPriority(system, handle, priority);
+}
+
/// Get which CPU core is executing the current thread
static u32 GetCurrentProcessorNumber(Core::System& system) {
LOG_TRACE(Kernel_SVC, "called");
- return system.CurrentScheduler().GetCurrentThread()->GetProcessorID();
+ return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex());
+}
+
+static u32 GetCurrentProcessorNumber32(Core::System& system) {
+ return GetCurrentProcessorNumber(system);
}
static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr,
u64 size, u32 permissions) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC,
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shared_memory_handle, addr, size, permissions);
@@ -1197,79 +1234,61 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
return ERR_INVALID_ADDRESS_STATE;
}
- const auto permissions_type = static_cast<MemoryPermission>(permissions);
- if (permissions_type != MemoryPermission::Read &&
- permissions_type != MemoryPermission::ReadWrite) {
+ const auto permission_type = static_cast<Memory::MemoryPermission>(permissions);
+ if ((permission_type | Memory::MemoryPermission::Write) !=
+ Memory::MemoryPermission::ReadAndWrite) {
LOG_ERROR(Kernel_SVC, "Expected Read or ReadWrite permission but got permissions=0x{:08X}",
permissions);
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto* const current_process = system.Kernel().CurrentProcess();
- auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
- if (!shared_memory) {
- LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}",
- shared_memory_handle);
- return ERR_INVALID_HANDLE;
- }
+ auto* const current_process{system.Kernel().CurrentProcess()};
+ auto& page_table{current_process->PageTable()};
- const auto& vm_manager = current_process->VMManager();
- if (!vm_manager.IsWithinASLRRegion(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Region is not within the ASLR region. addr=0x{:016X}, size={:016X}",
+ if (page_table.IsInvalidRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Addr does not fit within the valid region, addr=0x{:016X}, "
+ "size=0x{:016X}",
addr, size);
return ERR_INVALID_MEMORY_RANGE;
}
- return shared_memory->Map(*current_process, addr, permissions_type, MemoryPermission::DontCare);
-}
-
-static ResultCode UnmapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr,
- u64 size) {
- LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
- shared_memory_handle, addr, size);
-
- if (!Common::Is4KBAligned(addr)) {
- LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr);
- return ERR_INVALID_ADDRESS;
- }
-
- if (size == 0) {
- LOG_ERROR(Kernel_SVC, "Size is 0");
- return ERR_INVALID_SIZE;
- }
-
- if (!Common::Is4KBAligned(size)) {
- LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size);
- return ERR_INVALID_SIZE;
+ if (page_table.IsInsideHeapRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Addr does not fit within the heap region, addr=0x{:016X}, "
+ "size=0x{:016X}",
+ addr, size);
+ return ERR_INVALID_MEMORY_RANGE;
}
- if (!IsValidAddressRange(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
+ if (page_table.IsInsideAliasRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Address does not fit within the map region, addr=0x{:016X}, "
+ "size=0x{:016X}",
addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ERR_INVALID_MEMORY_RANGE;
}
- auto* const current_process = system.Kernel().CurrentProcess();
- auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
+ auto shared_memory{current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle)};
if (!shared_memory) {
LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}",
shared_memory_handle);
return ERR_INVALID_HANDLE;
}
- const auto& vm_manager = current_process->VMManager();
- if (!vm_manager.IsWithinASLRRegion(addr, size)) {
- LOG_ERROR(Kernel_SVC, "Region is not within the ASLR region. addr=0x{:016X}, size={:016X}",
- addr, size);
- return ERR_INVALID_MEMORY_RANGE;
- }
+ return shared_memory->Map(*current_process, addr, size, permission_type);
+}
- return shared_memory->Unmap(*current_process, addr, size);
+static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr,
+ u32 size, u32 permissions) {
+ return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr),
+ static_cast<std::size_t>(size), permissions);
}
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
VAddr page_info_address, Handle process_handle,
VAddr address) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
std::shared_ptr<Process> process = handle_table.Get<Process>(process_handle);
@@ -1279,18 +1298,17 @@ static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_add
return ERR_INVALID_HANDLE;
}
- auto& memory = system.Memory();
- const auto& vm_manager = process->VMManager();
- const MemoryInfo memory_info = vm_manager.QueryMemory(address);
-
- memory.Write64(memory_info_address, memory_info.base_address);
- memory.Write64(memory_info_address + 8, memory_info.size);
- memory.Write32(memory_info_address + 16, memory_info.state);
- memory.Write32(memory_info_address + 20, memory_info.attributes);
- memory.Write32(memory_info_address + 24, memory_info.permission);
- memory.Write32(memory_info_address + 32, memory_info.ipc_ref_count);
- memory.Write32(memory_info_address + 28, memory_info.device_ref_count);
- memory.Write32(memory_info_address + 36, 0);
+ auto& memory{system.Memory()};
+ const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()};
+
+ memory.Write64(memory_info_address + 0x00, memory_info.addr);
+ memory.Write64(memory_info_address + 0x08, memory_info.size);
+ memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff);
+ memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attr));
+ memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.perm));
+ memory.Write32(memory_info_address + 0x1c, memory_info.ipc_refcount);
+ memory.Write32(memory_info_address + 0x20, memory_info.device_refcount);
+ memory.Write32(memory_info_address + 0x24, 0);
// Page info appears to be currently unused by the kernel and is always set to zero.
memory.Write32(page_info_address, 0);
@@ -1362,8 +1380,8 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
return ERR_INVALID_HANDLE;
}
- auto& vm_manager = process->VMManager();
- if (!vm_manager.IsWithinAddressSpace(src_address, size)) {
+ auto& page_table = process->PageTable();
+ if (!page_table.IsInsideAddressSpace(src_address, size)) {
LOG_ERROR(Kernel_SVC,
"Source address range is not within the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
@@ -1371,7 +1389,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
return ERR_INVALID_ADDRESS_STATE;
}
- if (!vm_manager.IsWithinASLRRegion(dst_address, size)) {
+ if (!page_table.IsInsideASLRRegion(dst_address, size)) {
LOG_ERROR(Kernel_SVC,
"Destination address range is not within the ASLR region (dst_address=0x{:016X}, "
"size=0x{:016X}).",
@@ -1379,7 +1397,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
return ERR_INVALID_MEMORY_RANGE;
}
- return vm_manager.MapCodeMemory(dst_address, src_address, size);
+ return page_table.MapProcessCodeMemory(dst_address, src_address, size);
}
static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle,
@@ -1430,8 +1448,8 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ERR_INVALID_HANDLE;
}
- auto& vm_manager = process->VMManager();
- if (!vm_manager.IsWithinAddressSpace(src_address, size)) {
+ auto& page_table = process->PageTable();
+ if (!page_table.IsInsideAddressSpace(src_address, size)) {
LOG_ERROR(Kernel_SVC,
"Source address range is not within the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
@@ -1439,7 +1457,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ERR_INVALID_ADDRESS_STATE;
}
- if (!vm_manager.IsWithinASLRRegion(dst_address, size)) {
+ if (!page_table.IsInsideASLRRegion(dst_address, size)) {
LOG_ERROR(Kernel_SVC,
"Destination address range is not within the ASLR region (dst_address=0x{:016X}, "
"size=0x{:016X}).",
@@ -1447,12 +1465,13 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ERR_INVALID_MEMORY_RANGE;
}
- return vm_manager.UnmapCodeMemory(dst_address, src_address, size);
+ return page_table.UnmapProcessCodeMemory(dst_address, src_address, size);
}
/// Exits the current process
static void ExitProcess(Core::System& system) {
auto* current_process = system.Kernel().CurrentProcess();
+ UNIMPLEMENTED();
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
@@ -1462,8 +1481,10 @@ static void ExitProcess(Core::System& system) {
// Kill the current thread
system.CurrentScheduler().GetCurrentThread()->Stop();
+}
- system.PrepareReschedule();
+static void ExitProcess32(Core::System& system) {
+ ExitProcess(system);
}
/// Creates a new thread
@@ -1506,9 +1527,13 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
}
auto& kernel = system.Kernel();
+
+ ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1));
+
+ ThreadType type = THREADTYPE_USER;
CASCADE_RESULT(std::shared_ptr<Thread> thread,
- Thread::Create(kernel, "", entry_point, priority, arg, processor_id, stack_top,
- *current_process));
+ Thread::Create(system, type, "", entry_point, priority, arg, processor_id,
+ stack_top, current_process));
const auto new_thread_handle = current_process->GetHandleTable().Create(thread);
if (new_thread_handle.Failed()) {
@@ -1522,11 +1547,15 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
thread->SetName(
fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
- system.PrepareReschedule(thread->GetProcessorID());
-
return RESULT_SUCCESS;
}
+static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
+ u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
+ return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg),
+ static_cast<VAddr>(stack_top), priority, processor_id);
+}
+
/// Starts the thread for the provided handle
static ResultCode StartThread(Core::System& system, Handle thread_handle) {
LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
@@ -1541,13 +1570,11 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
- thread->ResumeFromWait();
-
- if (thread->GetStatus() == ThreadStatus::Ready) {
- system.PrepareReschedule(thread->GetProcessorID());
- }
+ return thread->Start();
+}
- return RESULT_SUCCESS;
+static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
+ return StartThread(system, thread_handle);
}
/// Called when a thread exits
@@ -1555,9 +1582,12 @@ static void ExitThread(Core::System& system) {
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
- current_thread->Stop();
system.GlobalScheduler().RemoveThread(SharedFrom(current_thread));
- system.PrepareReschedule();
+ current_thread->Stop();
+}
+
+static void ExitThread32(Core::System& system) {
+ ExitThread(system);
}
/// Sleep the current thread
@@ -1576,15 +1606,21 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
if (nanoseconds <= 0) {
switch (static_cast<SleepType>(nanoseconds)) {
- case SleepType::YieldWithoutLoadBalancing:
- is_redundant = current_thread->YieldSimple();
+ case SleepType::YieldWithoutLoadBalancing: {
+ auto pair = current_thread->YieldSimple();
+ is_redundant = pair.second;
break;
- case SleepType::YieldWithLoadBalancing:
- is_redundant = current_thread->YieldAndBalanceLoad();
+ }
+ case SleepType::YieldWithLoadBalancing: {
+ auto pair = current_thread->YieldAndBalanceLoad();
+ is_redundant = pair.second;
break;
- case SleepType::YieldAndWaitForLoadBalancing:
- is_redundant = current_thread->YieldAndWaitForLoadBalancing();
+ }
+ case SleepType::YieldAndWaitForLoadBalancing: {
+ auto pair = current_thread->YieldAndWaitForLoadBalancing();
+ is_redundant = pair.second;
break;
+ }
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
@@ -1592,13 +1628,18 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
current_thread->Sleep(nanoseconds);
}
- if (is_redundant) {
- // If it's redundant, the core is pretty much idle. Some games keep idling
- // a core while it's doing nothing, we advance timing to avoid costly continuous
- // calls.
- system.CoreTiming().AddTicks(2000);
+ if (is_redundant && !system.Kernel().IsMulticore()) {
+ system.Kernel().ExitSVCProfile();
+ system.CoreTiming().AddTicks(1000U);
+ system.GetCpuManager().PreemptSingleCore();
+ system.Kernel().EnterSVCProfile();
}
- system.PrepareReschedule(current_thread->GetProcessorID());
+}
+
+static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) {
+ const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) |
+ (static_cast<u64>(nanoseconds_high) << 32));
+ SleepThread(system, nanoseconds);
}
/// Wait process wide key atomic
@@ -1610,7 +1651,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
- if (Memory::IsKernelVirtualAddress(mutex_addr)) {
+ if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
LOG_ERROR(
Kernel_SVC,
"Given mutex address must not be within the kernel address space. address=0x{:016X}",
@@ -1625,31 +1666,69 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
}
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
-
+ auto& kernel = system.Kernel();
+ Handle event_handle;
+ Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
auto* const current_process = system.Kernel().CurrentProcess();
- const auto& handle_table = current_process->GetHandleTable();
- std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
- ASSERT(thread);
+ {
+ SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
+ const auto& handle_table = current_process->GetHandleTable();
+ std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ ASSERT(thread);
+
+ current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
+
+ if (thread->IsPendingTermination()) {
+ lock.CancelSleep();
+ return ERR_THREAD_TERMINATING;
+ }
+
+ const auto release_result = current_process->GetMutex().Release(mutex_addr);
+ if (release_result.IsError()) {
+ lock.CancelSleep();
+ return release_result;
+ }
- const auto release_result = current_process->GetMutex().Release(mutex_addr);
- if (release_result.IsError()) {
- return release_result;
+ if (nano_seconds == 0) {
+ lock.CancelSleep();
+ return RESULT_TIMEOUT;
+ }
+
+ current_thread->SetCondVarWaitAddress(condition_variable_addr);
+ current_thread->SetMutexWaitAddress(mutex_addr);
+ current_thread->SetWaitHandle(thread_handle);
+ current_thread->SetStatus(ThreadStatus::WaitCondVar);
+ current_process->InsertConditionVariableThread(SharedFrom(current_thread));
}
- Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
- current_thread->SetCondVarWaitAddress(condition_variable_addr);
- current_thread->SetMutexWaitAddress(mutex_addr);
- current_thread->SetWaitHandle(thread_handle);
- current_thread->SetStatus(ThreadStatus::WaitCondVar);
- current_thread->InvalidateWakeupCallback();
- current_process->InsertConditionVariableThread(SharedFrom(current_thread));
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = kernel.TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
+ }
+
+ {
+ SchedulerLock lock(kernel);
- current_thread->WakeAfterDelay(nano_seconds);
+ auto* owner = current_thread->GetLockOwner();
+ if (owner != nullptr) {
+ owner->RemoveMutexWaiter(SharedFrom(current_thread));
+ }
+ current_process->RemoveConditionVariableThread(SharedFrom(current_thread));
+ }
// Note: Deliberately don't attempt to inherit the lock owner's priority.
- system.PrepareReschedule(current_thread->GetProcessorID());
- return RESULT_SUCCESS;
+ return current_thread->GetSignalingResult();
+}
+
+static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr,
+ u32 condition_variable_addr, Handle thread_handle,
+ u32 nanoseconds_low, u32 nanoseconds_high) {
+ const s64 nanoseconds =
+ static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32));
+ return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr),
+ static_cast<VAddr>(condition_variable_addr), thread_handle,
+ nanoseconds);
}
/// Signal process wide key
@@ -1660,16 +1739,18 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
// Retrieve a list of all threads that are waiting for this condition variable.
- auto* const current_process = system.Kernel().CurrentProcess();
+ auto& kernel = system.Kernel();
+ SchedulerLock lock(kernel);
+ auto* const current_process = kernel.CurrentProcess();
std::vector<std::shared_ptr<Thread>> waiting_threads =
current_process->GetConditionVariableThreads(condition_variable_addr);
// Only process up to 'target' threads, unless 'target' is less equal 0, in which case process
// them all.
std::size_t last = waiting_threads.size();
- if (target > 0)
+ if (target > 0) {
last = std::min(waiting_threads.size(), static_cast<std::size_t>(target));
-
+ }
for (std::size_t index = 0; index < last; ++index) {
auto& thread = waiting_threads[index];
@@ -1677,21 +1758,17 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
// liberate Cond Var Thread.
current_process->RemoveConditionVariableThread(thread);
- thread->SetCondVarWaitAddress(0);
const std::size_t current_core = system.CurrentCoreIndex();
auto& monitor = system.Monitor();
- auto& memory = system.Memory();
// Atomically read the value of the mutex.
u32 mutex_val = 0;
u32 update_val = 0;
const VAddr mutex_address = thread->GetMutexWaitAddress();
do {
- monitor.SetExclusive(current_core, mutex_address);
-
// If the mutex is not yet acquired, acquire it.
- mutex_val = memory.Read32(mutex_address);
+ mutex_val = monitor.ExclusiveRead32(current_core, mutex_address);
if (mutex_val != 0) {
update_val = mutex_val | Mutex::MutexHasWaitersFlag;
@@ -1699,33 +1776,28 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
update_val = thread->GetWaitHandle();
}
} while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val));
+ monitor.ClearExclusive();
if (mutex_val == 0) {
// We were able to acquire the mutex, resume this thread.
- ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
- thread->ResumeFromWait();
-
auto* const lock_owner = thread->GetLockOwner();
if (lock_owner != nullptr) {
lock_owner->RemoveMutexWaiter(thread);
}
thread->SetLockOwner(nullptr);
- thread->SetMutexWaitAddress(0);
- thread->SetWaitHandle(0);
- thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
- system.PrepareReschedule(thread->GetProcessorID());
+ thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
+ thread->ResumeFromWait();
} else {
// The mutex is already owned by some other thread, make this thread wait on it.
const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto owner = handle_table.Get<Thread>(owner_handle);
ASSERT(owner);
- ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
- thread->InvalidateWakeupCallback();
- thread->SetStatus(ThreadStatus::WaitMutex);
+ if (thread->GetStatus() == ThreadStatus::WaitCondVar) {
+ thread->SetStatus(ThreadStatus::WaitMutex);
+ }
owner->AddMutexWaiter(thread);
- system.PrepareReschedule(thread->GetProcessorID());
}
}
}
@@ -1741,7 +1813,7 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
type, value, timeout);
// If the passed address is a kernel virtual address, return invalid memory state.
- if (Memory::IsKernelVirtualAddress(address)) {
+ if (Core::Memory::IsKernelVirtualAddress(address)) {
LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
return ERR_INVALID_ADDRESS_STATE;
}
@@ -1756,12 +1828,15 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
const ResultCode result =
address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
- if (result == RESULT_SUCCESS) {
- system.PrepareReschedule();
- }
return result;
}
+static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value,
+ u32 timeout_low, u32 timeout_high) {
+ s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32));
+ return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout);
+}
+
// Signals to an address (via Address Arbiter)
static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value,
s32 num_to_wake) {
@@ -1769,7 +1844,7 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type,
address, type, value, num_to_wake);
// If the passed address is a kernel virtual address, return invalid memory state.
- if (Memory::IsKernelVirtualAddress(address)) {
+ if (Core::Memory::IsKernelVirtualAddress(address)) {
LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
return ERR_INVALID_ADDRESS_STATE;
}
@@ -1785,6 +1860,11 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type,
return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake);
}
+static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value,
+ s32 num_to_wake) {
+ return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake);
+}
+
static void KernelDebug([[maybe_unused]] Core::System& system,
[[maybe_unused]] u32 kernel_debug_type, [[maybe_unused]] u64 param1,
[[maybe_unused]] u64 param2, [[maybe_unused]] u64 param3) {
@@ -1803,14 +1883,21 @@ static u64 GetSystemTick(Core::System& system) {
auto& core_timing = system.CoreTiming();
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
- const u64 result{Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks())};
+ const u64 result{system.CoreTiming().GetClockTicks()};
- // Advance time to defeat dumb games that busy-wait for the frame to end.
- core_timing.AddTicks(400);
+ if (!system.Kernel().IsMulticore()) {
+ core_timing.AddTicks(400U);
+ }
return result;
}
+static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) {
+ u64 time = GetSystemTick(system);
+ *time_low = static_cast<u32>(time);
+ *time_high = static_cast<u32>(time >> 32);
+}
+
/// Close a handle
static ResultCode CloseHandle(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
@@ -1843,9 +1930,14 @@ static ResultCode ResetSignal(Core::System& system, Handle handle) {
return ERR_INVALID_HANDLE;
}
+static ResultCode ResetSignal32(Core::System& system, Handle handle) {
+ return ResetSignal(system, handle);
+}
+
/// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAddr addr, u64 size,
u32 permissions) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
permissions);
@@ -1865,9 +1957,9 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
return ERR_INVALID_ADDRESS_STATE;
}
- const auto perms = static_cast<MemoryPermission>(permissions);
- if (perms != MemoryPermission::None && perms != MemoryPermission::Read &&
- perms != MemoryPermission::ReadWrite) {
+ const auto perms{static_cast<Memory::MemoryPermission>(permissions)};
+ if (perms > Memory::MemoryPermission::ReadAndWrite ||
+ perms == Memory::MemoryPermission::Write) {
LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})",
permissions);
return ERR_INVALID_MEMORY_PERMISSIONS;
@@ -1890,109 +1982,10 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
return RESULT_SUCCESS;
}
-static ResultCode MapTransferMemory(Core::System& system, Handle handle, VAddr address, u64 size,
- u32 permission_raw) {
- LOG_DEBUG(Kernel_SVC,
- "called. handle=0x{:08X}, address=0x{:016X}, size=0x{:016X}, permissions=0x{:08X}",
- handle, address, size, permission_raw);
-
- if (!Common::Is4KBAligned(address)) {
- LOG_ERROR(Kernel_SVC, "Transfer memory addresses must be 4KB aligned (size=0x{:016X}).",
- address);
- return ERR_INVALID_ADDRESS;
- }
-
- if (size == 0 || !Common::Is4KBAligned(size)) {
- LOG_ERROR(Kernel_SVC,
- "Transfer memory sizes must be 4KB aligned and not be zero (size=0x{:016X}).",
- size);
- return ERR_INVALID_SIZE;
- }
-
- if (!IsValidAddressRange(address, size)) {
- LOG_ERROR(Kernel_SVC,
- "Given address and size overflows the 64-bit range (address=0x{:016X}, "
- "size=0x{:016X}).",
- address, size);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const auto permissions = static_cast<MemoryPermission>(permission_raw);
- if (permissions != MemoryPermission::None && permissions != MemoryPermission::Read &&
- permissions != MemoryPermission::ReadWrite) {
- LOG_ERROR(Kernel_SVC, "Invalid transfer memory permissions given (permissions=0x{:08X}).",
- permission_raw);
- return ERR_INVALID_STATE;
- }
-
- const auto& kernel = system.Kernel();
- const auto* const current_process = kernel.CurrentProcess();
- const auto& handle_table = current_process->GetHandleTable();
-
- auto transfer_memory = handle_table.Get<TransferMemory>(handle);
- if (!transfer_memory) {
- LOG_ERROR(Kernel_SVC, "Nonexistent transfer memory handle given (handle=0x{:08X}).",
- handle);
- return ERR_INVALID_HANDLE;
- }
-
- if (!current_process->VMManager().IsWithinASLRRegion(address, size)) {
- LOG_ERROR(Kernel_SVC,
- "Given address and size don't fully fit within the ASLR region "
- "(address=0x{:016X}, size=0x{:016X}).",
- address, size);
- return ERR_INVALID_MEMORY_RANGE;
- }
-
- return transfer_memory->MapMemory(address, size, permissions);
-}
-
-static ResultCode UnmapTransferMemory(Core::System& system, Handle handle, VAddr address,
- u64 size) {
- LOG_DEBUG(Kernel_SVC, "called. handle=0x{:08X}, address=0x{:016X}, size=0x{:016X}", handle,
- address, size);
-
- if (!Common::Is4KBAligned(address)) {
- LOG_ERROR(Kernel_SVC, "Transfer memory addresses must be 4KB aligned (size=0x{:016X}).",
- address);
- return ERR_INVALID_ADDRESS;
- }
-
- if (size == 0 || !Common::Is4KBAligned(size)) {
- LOG_ERROR(Kernel_SVC,
- "Transfer memory sizes must be 4KB aligned and not be zero (size=0x{:016X}).",
- size);
- return ERR_INVALID_SIZE;
- }
-
- if (!IsValidAddressRange(address, size)) {
- LOG_ERROR(Kernel_SVC,
- "Given address and size overflows the 64-bit range (address=0x{:016X}, "
- "size=0x{:016X}).",
- address, size);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const auto& kernel = system.Kernel();
- const auto* const current_process = kernel.CurrentProcess();
- const auto& handle_table = current_process->GetHandleTable();
-
- auto transfer_memory = handle_table.Get<TransferMemory>(handle);
- if (!transfer_memory) {
- LOG_ERROR(Kernel_SVC, "Nonexistent transfer memory handle given (handle=0x{:08X}).",
- handle);
- return ERR_INVALID_HANDLE;
- }
-
- if (!current_process->VMManager().IsWithinASLRRegion(address, size)) {
- LOG_ERROR(Kernel_SVC,
- "Given address and size don't fully fit within the ASLR region "
- "(address=0x{:016X}, size=0x{:016X}).",
- address, size);
- return ERR_INVALID_MEMORY_RANGE;
- }
-
- return transfer_memory->UnmapMemory(address, size);
+static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size,
+ u32 permissions) {
+ return CreateTransferMemory(system, handle, static_cast<VAddr>(addr),
+ static_cast<std::size_t>(size), permissions);
}
static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
@@ -2004,6 +1997,8 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
thread_handle);
+ *core = 0;
+ *mask = 0;
return ERR_INVALID_HANDLE;
}
@@ -2013,6 +2008,15 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
return RESULT_SUCCESS;
}
+static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, u32* core,
+ u32* mask_low, u32* mask_high) {
+ u64 mask{};
+ const auto result = GetThreadCoreMask(system, thread_handle, core, &mask);
+ *mask_high = static_cast<u32>(mask >> 32);
+ *mask_low = static_cast<u32>(mask);
+ return result;
+}
+
static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core,
u64 affinity_mask) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}",
@@ -2044,7 +2048,7 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
return ERR_INVALID_COMBINATION;
}
- if (core < Core::NUM_CPU_CORES) {
+ if (core < Core::Hardware::NUM_CPU_CORES) {
if ((affinity_mask & (1ULL << core)) == 0) {
LOG_ERROR(Kernel_SVC,
"Core is not enabled for the current mask, core={}, mask={:016X}", core,
@@ -2066,57 +2070,14 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
return ERR_INVALID_HANDLE;
}
- system.PrepareReschedule(thread->GetProcessorID());
- thread->ChangeCore(core, affinity_mask);
- system.PrepareReschedule(thread->GetProcessorID());
-
- return RESULT_SUCCESS;
+ return thread->SetCoreAndAffinityMask(core, affinity_mask);
}
-static ResultCode CreateSharedMemory(Core::System& system, Handle* handle, u64 size,
- u32 local_permissions, u32 remote_permissions) {
- LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size,
- local_permissions, remote_permissions);
- if (size == 0) {
- LOG_ERROR(Kernel_SVC, "Size is 0");
- return ERR_INVALID_SIZE;
- }
- if (!Common::Is4KBAligned(size)) {
- LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size);
- return ERR_INVALID_SIZE;
- }
-
- if (size >= MAIN_MEMORY_SIZE) {
- LOG_ERROR(Kernel_SVC, "Size is not less than 8GB, 0x{:016X}", size);
- return ERR_INVALID_SIZE;
- }
-
- const auto local_perms = static_cast<MemoryPermission>(local_permissions);
- if (local_perms != MemoryPermission::Read && local_perms != MemoryPermission::ReadWrite) {
- LOG_ERROR(Kernel_SVC,
- "Invalid local memory permissions, expected Read or ReadWrite but got "
- "local_permissions={}",
- static_cast<u32>(local_permissions));
- return ERR_INVALID_MEMORY_PERMISSIONS;
- }
-
- const auto remote_perms = static_cast<MemoryPermission>(remote_permissions);
- if (remote_perms != MemoryPermission::Read && remote_perms != MemoryPermission::ReadWrite &&
- remote_perms != MemoryPermission::DontCare) {
- LOG_ERROR(Kernel_SVC,
- "Invalid remote memory permissions, expected Read, ReadWrite or DontCare but got "
- "remote_permissions={}",
- static_cast<u32>(remote_permissions));
- return ERR_INVALID_MEMORY_PERMISSIONS;
- }
-
- auto& kernel = system.Kernel();
- auto process = kernel.CurrentProcess();
- auto& handle_table = process->GetHandleTable();
- auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms);
-
- CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
- return RESULT_SUCCESS;
+static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core,
+ u32 affinity_mask_low, u32 affinity_mask_high) {
+ const u64 affinity_mask =
+ static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32);
+ return SetThreadCoreMask(system, thread_handle, core, affinity_mask);
}
static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) {
@@ -2147,6 +2108,10 @@ static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle
return RESULT_SUCCESS;
}
+static ResultCode CreateEvent32(Core::System& system, Handle* write_handle, Handle* read_handle) {
+ return CreateEvent(system, write_handle, read_handle);
+}
+
static ResultCode ClearEvent(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
@@ -2168,6 +2133,10 @@ static ResultCode ClearEvent(Core::System& system, Handle handle) {
return ERR_INVALID_HANDLE;
}
+static ResultCode ClearEvent32(Core::System& system, Handle handle) {
+ return ClearEvent(system, handle);
+}
+
static ResultCode SignalEvent(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
@@ -2180,10 +2149,13 @@ static ResultCode SignalEvent(Core::System& system, Handle handle) {
}
writable_event->Signal();
- system.PrepareReschedule();
return RESULT_SUCCESS;
}
+static ResultCode SignalEvent32(Core::System& system, Handle handle) {
+ return SignalEvent(system, handle);
+}
+
static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
@@ -2211,6 +2183,7 @@ static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_
}
static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) {
+ std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called");
auto& kernel = system.Kernel();
@@ -2305,11 +2278,10 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
}
const auto& kernel = system.Kernel();
- const auto& vm_manager = kernel.CurrentProcess()->VMManager();
const auto total_copy_size = out_process_ids_size * sizeof(u64);
- if (out_process_ids_size > 0 &&
- !vm_manager.IsWithinAddressSpace(out_process_ids, total_copy_size)) {
+ if (out_process_ids_size > 0 && !kernel.CurrentProcess()->PageTable().IsInsideAddressSpace(
+ out_process_ids, total_copy_size)) {
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
out_process_ids, out_process_ids + total_copy_size);
return ERR_INVALID_ADDRESS_STATE;
@@ -2345,11 +2317,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
}
const auto* const current_process = system.Kernel().CurrentProcess();
- const auto& vm_manager = current_process->VMManager();
const auto total_copy_size = out_thread_ids_size * sizeof(u64);
if (out_thread_ids_size > 0 &&
- !vm_manager.IsWithinAddressSpace(out_thread_ids, total_copy_size)) {
+ !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) {
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
out_thread_ids, out_thread_ids + total_copy_size);
return ERR_INVALID_ADDRESS_STATE;
@@ -2370,6 +2341,15 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return RESULT_SUCCESS;
}
+static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address,
+ u32 size) {
+ // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope
+ // as all emulation is done in the same cache level in host architecture, thus data cache
+ // does not need flushing.
+ LOG_DEBUG(Kernel_SVC, "called");
+ return RESULT_SUCCESS;
+}
+
namespace {
struct FunctionDef {
using Func = void(Core::System&);
@@ -2384,57 +2364,57 @@ static const FunctionDef SVC_Table_32[] = {
{0x00, nullptr, "Unknown"},
{0x01, SvcWrap32<SetHeapSize32>, "SetHeapSize32"},
{0x02, nullptr, "Unknown"},
- {0x03, nullptr, "SetMemoryAttribute32"},
- {0x04, nullptr, "MapMemory32"},
- {0x05, nullptr, "UnmapMemory32"},
+ {0x03, SvcWrap32<SetMemoryAttribute32>, "SetMemoryAttribute32"},
+ {0x04, SvcWrap32<MapMemory32>, "MapMemory32"},
+ {0x05, SvcWrap32<UnmapMemory32>, "UnmapMemory32"},
{0x06, SvcWrap32<QueryMemory32>, "QueryMemory32"},
- {0x07, nullptr, "ExitProcess32"},
- {0x08, nullptr, "CreateThread32"},
- {0x09, nullptr, "StartThread32"},
- {0x0a, nullptr, "ExitThread32"},
- {0x0b, nullptr, "SleepThread32"},
+ {0x07, SvcWrap32<ExitProcess32>, "ExitProcess32"},
+ {0x08, SvcWrap32<CreateThread32>, "CreateThread32"},
+ {0x09, SvcWrap32<StartThread32>, "StartThread32"},
+ {0x0a, SvcWrap32<ExitThread32>, "ExitThread32"},
+ {0x0b, SvcWrap32<SleepThread32>, "SleepThread32"},
{0x0c, SvcWrap32<GetThreadPriority32>, "GetThreadPriority32"},
- {0x0d, nullptr, "SetThreadPriority32"},
- {0x0e, nullptr, "GetThreadCoreMask32"},
- {0x0f, nullptr, "SetThreadCoreMask32"},
- {0x10, nullptr, "GetCurrentProcessorNumber32"},
- {0x11, nullptr, "SignalEvent32"},
- {0x12, nullptr, "ClearEvent32"},
- {0x13, nullptr, "MapSharedMemory32"},
+ {0x0d, SvcWrap32<SetThreadPriority32>, "SetThreadPriority32"},
+ {0x0e, SvcWrap32<GetThreadCoreMask32>, "GetThreadCoreMask32"},
+ {0x0f, SvcWrap32<SetThreadCoreMask32>, "SetThreadCoreMask32"},
+ {0x10, SvcWrap32<GetCurrentProcessorNumber32>, "GetCurrentProcessorNumber32"},
+ {0x11, SvcWrap32<SignalEvent32>, "SignalEvent32"},
+ {0x12, SvcWrap32<ClearEvent32>, "ClearEvent32"},
+ {0x13, SvcWrap32<MapSharedMemory32>, "MapSharedMemory32"},
{0x14, nullptr, "UnmapSharedMemory32"},
- {0x15, nullptr, "CreateTransferMemory32"},
+ {0x15, SvcWrap32<CreateTransferMemory32>, "CreateTransferMemory32"},
{0x16, SvcWrap32<CloseHandle32>, "CloseHandle32"},
- {0x17, nullptr, "ResetSignal32"},
+ {0x17, SvcWrap32<ResetSignal32>, "ResetSignal32"},
{0x18, SvcWrap32<WaitSynchronization32>, "WaitSynchronization32"},
- {0x19, nullptr, "CancelSynchronization32"},
- {0x1a, nullptr, "ArbitrateLock32"},
- {0x1b, nullptr, "ArbitrateUnlock32"},
- {0x1c, nullptr, "WaitProcessWideKeyAtomic32"},
+ {0x19, SvcWrap32<CancelSynchronization32>, "CancelSynchronization32"},
+ {0x1a, SvcWrap32<ArbitrateLock32>, "ArbitrateLock32"},
+ {0x1b, SvcWrap32<ArbitrateUnlock32>, "ArbitrateUnlock32"},
+ {0x1c, SvcWrap32<WaitProcessWideKeyAtomic32>, "WaitProcessWideKeyAtomic32"},
{0x1d, SvcWrap32<SignalProcessWideKey32>, "SignalProcessWideKey32"},
- {0x1e, nullptr, "GetSystemTick32"},
+ {0x1e, SvcWrap32<GetSystemTick32>, "GetSystemTick32"},
{0x1f, SvcWrap32<ConnectToNamedPort32>, "ConnectToNamedPort32"},
{0x20, nullptr, "Unknown"},
{0x21, SvcWrap32<SendSyncRequest32>, "SendSyncRequest32"},
{0x22, nullptr, "SendSyncRequestWithUserBuffer32"},
{0x23, nullptr, "Unknown"},
- {0x24, nullptr, "GetProcessId32"},
+ {0x24, SvcWrap32<GetProcessId32>, "GetProcessId32"},
{0x25, SvcWrap32<GetThreadId32>, "GetThreadId32"},
- {0x26, nullptr, "Break32"},
+ {0x26, SvcWrap32<Break32>, "Break32"},
{0x27, nullptr, "OutputDebugString32"},
{0x28, nullptr, "Unknown"},
{0x29, SvcWrap32<GetInfo32>, "GetInfo32"},
{0x2a, nullptr, "Unknown"},
{0x2b, nullptr, "Unknown"},
- {0x2c, nullptr, "MapPhysicalMemory32"},
- {0x2d, nullptr, "UnmapPhysicalMemory32"},
+ {0x2c, SvcWrap32<MapPhysicalMemory32>, "MapPhysicalMemory32"},
+ {0x2d, SvcWrap32<UnmapPhysicalMemory32>, "UnmapPhysicalMemory32"},
{0x2e, nullptr, "Unknown"},
{0x2f, nullptr, "Unknown"},
{0x30, nullptr, "Unknown"},
{0x31, nullptr, "Unknown"},
- {0x32, nullptr, "SetThreadActivity32"},
- {0x33, nullptr, "GetThreadContext32"},
- {0x34, nullptr, "WaitForAddress32"},
- {0x35, nullptr, "SignalToAddress32"},
+ {0x32, SvcWrap32<SetThreadActivity32>, "SetThreadActivity32"},
+ {0x33, SvcWrap32<GetThreadContext32>, "GetThreadContext32"},
+ {0x34, SvcWrap32<WaitForAddress32>, "WaitForAddress32"},
+ {0x35, SvcWrap32<SignalToAddress32>, "SignalToAddress32"},
{0x36, nullptr, "Unknown"},
{0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"},
@@ -2450,7 +2430,7 @@ static const FunctionDef SVC_Table_32[] = {
{0x42, nullptr, "Unknown"},
{0x43, nullptr, "ReplyAndReceive32"},
{0x44, nullptr, "Unknown"},
- {0x45, nullptr, "CreateEvent32"},
+ {0x45, SvcWrap32<CreateEvent32>, "CreateEvent32"},
{0x46, nullptr, "Unknown"},
{0x47, nullptr, "Unknown"},
{0x48, nullptr, "Unknown"},
@@ -2476,7 +2456,7 @@ static const FunctionDef SVC_Table_32[] = {
{0x5c, nullptr, "Unknown"},
{0x5d, nullptr, "Unknown"},
{0x5e, nullptr, "Unknown"},
- {0x5F, nullptr, "FlushProcessDataCache32"},
+ {0x5F, SvcWrap32<FlushProcessDataCache32>, "FlushProcessDataCache32"},
{0x60, nullptr, "Unknown"},
{0x61, nullptr, "Unknown"},
{0x62, nullptr, "Unknown"},
@@ -2510,7 +2490,7 @@ static const FunctionDef SVC_Table_32[] = {
static const FunctionDef SVC_Table_64[] = {
{0x00, nullptr, "Unknown"},
{0x01, SvcWrap64<SetHeapSize>, "SetHeapSize"},
- {0x02, SvcWrap64<SetMemoryPermission>, "SetMemoryPermission"},
+ {0x02, nullptr, "SetMemoryPermission"},
{0x03, SvcWrap64<SetMemoryAttribute>, "SetMemoryAttribute"},
{0x04, SvcWrap64<MapMemory>, "MapMemory"},
{0x05, SvcWrap64<UnmapMemory>, "UnmapMemory"},
@@ -2528,7 +2508,7 @@ static const FunctionDef SVC_Table_64[] = {
{0x11, SvcWrap64<SignalEvent>, "SignalEvent"},
{0x12, SvcWrap64<ClearEvent>, "ClearEvent"},
{0x13, SvcWrap64<MapSharedMemory>, "MapSharedMemory"},
- {0x14, SvcWrap64<UnmapSharedMemory>, "UnmapSharedMemory"},
+ {0x14, nullptr, "UnmapSharedMemory"},
{0x15, SvcWrap64<CreateTransferMemory>, "CreateTransferMemory"},
{0x16, SvcWrap64<CloseHandle>, "CloseHandle"},
{0x17, SvcWrap64<ResetSignal>, "ResetSignal"},
@@ -2588,9 +2568,9 @@ static const FunctionDef SVC_Table_64[] = {
{0x4D, nullptr, "SleepSystem"},
{0x4E, nullptr, "ReadWriteRegister"},
{0x4F, nullptr, "SetProcessActivity"},
- {0x50, SvcWrap64<CreateSharedMemory>, "CreateSharedMemory"},
- {0x51, SvcWrap64<MapTransferMemory>, "MapTransferMemory"},
- {0x52, SvcWrap64<UnmapTransferMemory>, "UnmapTransferMemory"},
+ {0x50, nullptr, "CreateSharedMemory"},
+ {0x51, nullptr, "MapTransferMemory"},
+ {0x52, nullptr, "UnmapTransferMemory"},
{0x53, nullptr, "CreateInterruptEvent"},
{0x54, nullptr, "QueryPhysicalAddress"},
{0x55, nullptr, "QueryIoMapping"},
@@ -2654,13 +2634,10 @@ static const FunctionDef* GetSVCInfo64(u32 func_num) {
return &SVC_Table_64[func_num];
}
-MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
-
-void CallSVC(Core::System& system, u32 immediate) {
- MICROPROFILE_SCOPE(Kernel_SVC);
-
- // Lock the global kernel mutex when we enter the kernel HLE.
- std::lock_guard lock{HLE::g_hle_lock};
+void Call(Core::System& system, u32 immediate) {
+ system.ExitDynarmicProfile();
+ auto& kernel = system.Kernel();
+ kernel.EnterSVCProfile();
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)
: GetSVCInfo32(immediate);
@@ -2673,6 +2650,9 @@ void CallSVC(Core::System& system, u32 immediate) {
} else {
LOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate);
}
+
+ kernel.ExitSVCProfile();
+ system.EnterDynarmicProfile();
}
-} // namespace Kernel
+} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index c5539ac1c..46e64277e 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -10,8 +10,8 @@ namespace Core {
class System;
}
-namespace Kernel {
+namespace Kernel::Svc {
-void CallSVC(Core::System& system, u32 immediate);
+void Call(Core::System& system, u32 immediate);
-} // namespace Kernel
+} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
new file mode 100644
index 000000000..986724beb
--- /dev/null
+++ b/src/core/hle/kernel/svc_types.h
@@ -0,0 +1,68 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Kernel::Svc {
+
+enum class MemoryState : u32 {
+ Free = 0x00,
+ Io = 0x01,
+ Static = 0x02,
+ Code = 0x03,
+ CodeData = 0x04,
+ Normal = 0x05,
+ Shared = 0x06,
+ Alias = 0x07,
+ AliasCode = 0x08,
+ AliasCodeData = 0x09,
+ Ipc = 0x0A,
+ Stack = 0x0B,
+ ThreadLocal = 0x0C,
+ Transfered = 0x0D,
+ SharedTransfered = 0x0E,
+ SharedCode = 0x0F,
+ Inaccessible = 0x10,
+ NonSecureIpc = 0x11,
+ NonDeviceIpc = 0x12,
+ Kernel = 0x13,
+ GeneratedCode = 0x14,
+ CodeOut = 0x15,
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryState);
+
+enum class MemoryAttribute : u32 {
+ Locked = (1 << 0),
+ IpcLocked = (1 << 1),
+ DeviceShared = (1 << 2),
+ Uncached = (1 << 3),
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute);
+
+enum class MemoryPermission : u32 {
+ None = (0 << 0),
+ Read = (1 << 0),
+ Write = (1 << 1),
+ Execute = (1 << 2),
+ ReadWrite = Read | Write,
+ ReadExecute = Read | Execute,
+ DontCare = (1 << 28),
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission);
+
+struct MemoryInfo {
+ u64 addr{};
+ u64 size{};
+ MemoryState state{};
+ MemoryAttribute attr{};
+ MemoryPermission perm{};
+ u32 ipc_refcount{};
+ u32 device_refcount{};
+ u32 padding{};
+};
+
+} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 7d735e3fa..0b6dd9df0 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -350,13 +350,50 @@ void SvcWrap64(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2));
}
-// Used by QueryMemory32
+// Used by QueryMemory32, ArbitrateLock32
template <ResultCode func(Core::System&, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
FuncReturn32(system,
func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)).raw);
}
+// Used by Break32
+template <void func(Core::System&, u32, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2));
+}
+
+// Used by ExitProcess32, ExitThread32
+template <void func(Core::System&)>
+void SvcWrap32(Core::System& system) {
+ func(system);
+}
+
+// Used by GetCurrentProcessorNumber32
+template <u32 func(Core::System&)>
+void SvcWrap32(Core::System& system) {
+ FuncReturn32(system, func(system));
+}
+
+// Used by SleepThread32
+template <void func(Core::System&, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ func(system, Param32(system, 0), Param32(system, 1));
+}
+
+// Used by CreateThread32
+template <ResultCode func(Core::System&, Handle*, u32, u32, u32, u32, s32)>
+void SvcWrap32(Core::System& system) {
+ Handle param_1 = 0;
+
+ const u32 retval = func(system, &param_1, Param32(system, 0), Param32(system, 1),
+ Param32(system, 2), Param32(system, 3), Param32(system, 4))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
+}
+
// Used by GetInfo32
template <ResultCode func(Core::System&, u32*, u32*, u32, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
@@ -393,18 +430,114 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval);
}
+// Used by GetSystemTick32
+template <void func(Core::System&, u32*, u32*)>
+void SvcWrap32(Core::System& system) {
+ u32 param_1 = 0;
+ u32 param_2 = 0;
+
+ func(system, &param_1, &param_2);
+ system.CurrentArmInterface().SetReg(0, param_1);
+ system.CurrentArmInterface().SetReg(1, param_2);
+}
+
+// Used by CreateEvent32
+template <ResultCode func(Core::System&, Handle*, Handle*)>
+void SvcWrap32(Core::System& system) {
+ Handle param_1 = 0;
+ Handle param_2 = 0;
+
+ const u32 retval = func(system, &param_1, &param_2).raw;
+ system.CurrentArmInterface().SetReg(1, param_1);
+ system.CurrentArmInterface().SetReg(2, param_2);
+ FuncReturn(system, retval);
+}
+
+// Used by GetThreadId32
+template <ResultCode func(Core::System&, Handle, u32*, u32*, u32*)>
+void SvcWrap32(Core::System& system) {
+ u32 param_1 = 0;
+ u32 param_2 = 0;
+ u32 param_3 = 0;
+
+ const u32 retval = func(system, Param32(system, 2), &param_1, &param_2, &param_3).raw;
+ system.CurrentArmInterface().SetReg(1, param_1);
+ system.CurrentArmInterface().SetReg(2, param_2);
+ system.CurrentArmInterface().SetReg(3, param_3);
+ FuncReturn(system, retval);
+}
+
// Used by SignalProcessWideKey32
template <void func(Core::System&, u32, s32)>
void SvcWrap32(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1)));
}
-// Used by SendSyncRequest32
+// Used by SetThreadPriority32
+template <ResultCode func(Core::System&, Handle, u32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw;
+ FuncReturn(system, retval);
+}
+
+// Used by SetThreadCoreMask32
+template <ResultCode func(Core::System&, Handle, u32, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
+ static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
+// Used by WaitProcessWideKeyAtomic32
+template <ResultCode func(Core::System&, u32, u32, Handle, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
+ static_cast<Handle>(Param(system, 2)), static_cast<u32>(Param(system, 3)),
+ static_cast<u32>(Param(system, 4)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
+// Used by WaitForAddress32
+template <ResultCode func(Core::System&, u32, u32, s32, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
+ static_cast<u32>(Param(system, 1)), static_cast<s32>(Param(system, 2)),
+ static_cast<u32>(Param(system, 3)), static_cast<u32>(Param(system, 4)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
+// Used by SignalToAddress32
+template <ResultCode func(Core::System&, u32, u32, s32, s32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
+ static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
+// Used by SendSyncRequest32, ArbitrateUnlock32
template <ResultCode func(Core::System&, u32)>
void SvcWrap32(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
}
+// Used by CreateTransferMemory32
+template <ResultCode func(Core::System&, Handle*, u32, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ Handle handle = 0;
+ const u32 retval =
+ func(system, &handle, Param32(system, 1), Param32(system, 2), Param32(system, 3)).raw;
+ system.CurrentArmInterface().SetReg(1, handle);
+ FuncReturn(system, retval);
+}
+
// Used by WaitSynchronization32
template <ResultCode func(Core::System&, u32, u32, s32, u32, Handle*)>
void SvcWrap32(Core::System& system) {
diff --git a/src/core/hle/kernel/synchronization.cpp b/src/core/hle/kernel/synchronization.cpp
index dc37fad1a..8b875d853 100644
--- a/src/core/hle/kernel/synchronization.cpp
+++ b/src/core/hle/kernel/synchronization.cpp
@@ -10,78 +10,106 @@
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/time_manager.h"
namespace Kernel {
-/// Default thread wakeup callback for WaitSynchronization
-static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
- std::shared_ptr<SynchronizationObject> object,
- std::size_t index) {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
-
- if (reason == ThreadWakeupReason::Timeout) {
- thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
- return true;
- }
-
- ASSERT(reason == ThreadWakeupReason::Signal);
- thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
- thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
- return true;
-}
-
Synchronization::Synchronization(Core::System& system) : system{system} {}
void Synchronization::SignalObject(SynchronizationObject& obj) const {
+ auto& kernel = system.Kernel();
+ SchedulerLock lock(kernel);
if (obj.IsSignaled()) {
- obj.WakeupAllWaitingThreads();
+ for (auto thread : obj.GetWaitingThreads()) {
+ if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) {
+ if (thread->GetStatus() != ThreadStatus::WaitHLEEvent) {
+ ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
+ ASSERT(thread->IsWaitingSync());
+ }
+ thread->SetSynchronizationResults(&obj, RESULT_SUCCESS);
+ thread->ResumeFromWait();
+ }
+ }
+ obj.ClearWaitingThreads();
}
}
std::pair<ResultCode, Handle> Synchronization::WaitFor(
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
+ auto& kernel = system.Kernel();
auto* const thread = system.CurrentScheduler().GetCurrentThread();
- // Find the first object that is acquirable in the provided list of objects
- const auto itr = std::find_if(sync_objects.begin(), sync_objects.end(),
- [thread](const std::shared_ptr<SynchronizationObject>& object) {
- return object->IsSignaled();
- });
-
- if (itr != sync_objects.end()) {
- // We found a ready object, acquire it and set the result value
- SynchronizationObject* object = itr->get();
- object->Acquire(thread);
- const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
- return {RESULT_SUCCESS, index};
+ Handle event_handle = InvalidHandle;
+ {
+ SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
+ const auto itr =
+ std::find_if(sync_objects.begin(), sync_objects.end(),
+ [thread](const std::shared_ptr<SynchronizationObject>& object) {
+ return object->IsSignaled();
+ });
+
+ if (itr != sync_objects.end()) {
+ // We found a ready object, acquire it and set the result value
+ SynchronizationObject* object = itr->get();
+ object->Acquire(thread);
+ const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
+ lock.CancelSleep();
+ return {RESULT_SUCCESS, index};
+ }
+
+ if (nano_seconds == 0) {
+ lock.CancelSleep();
+ return {RESULT_TIMEOUT, InvalidHandle};
+ }
+
+ if (thread->IsPendingTermination()) {
+ lock.CancelSleep();
+ return {ERR_THREAD_TERMINATING, InvalidHandle};
+ }
+
+ if (thread->IsSyncCancelled()) {
+ thread->SetSyncCancelled(false);
+ lock.CancelSleep();
+ return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
+ }
+
+ for (auto& object : sync_objects) {
+ object->AddWaitingThread(SharedFrom(thread));
+ }
+
+ thread->SetSynchronizationObjects(&sync_objects);
+ thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
+ thread->SetStatus(ThreadStatus::WaitSynch);
+ thread->SetWaitingSync(true);
}
+ thread->SetWaitingSync(false);
- // No objects were ready to be acquired, prepare to suspend the thread.
-
- // If a timeout value of 0 was provided, just return the Timeout error code instead of
- // suspending the thread.
- if (nano_seconds == 0) {
- return {RESULT_TIMEOUT, InvalidHandle};
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = kernel.TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
}
- if (thread->IsSyncCancelled()) {
- thread->SetSyncCancelled(false);
- return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
+ {
+ SchedulerLock lock(kernel);
+ ResultCode signaling_result = thread->GetSignalingResult();
+ SynchronizationObject* signaling_object = thread->GetSignalingObject();
+ thread->SetSynchronizationObjects(nullptr);
+ auto shared_thread = SharedFrom(thread);
+ for (auto& obj : sync_objects) {
+ obj->RemoveWaitingThread(shared_thread);
+ }
+ if (signaling_object != nullptr) {
+ const auto itr = std::find_if(
+ sync_objects.begin(), sync_objects.end(),
+ [signaling_object](const std::shared_ptr<SynchronizationObject>& object) {
+ return object.get() == signaling_object;
+ });
+ ASSERT(itr != sync_objects.end());
+ signaling_object->Acquire(thread);
+ const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
+ return {signaling_result, index};
+ }
+ return {signaling_result, -1};
}
-
- for (auto& object : sync_objects) {
- object->AddWaitingThread(SharedFrom(thread));
- }
-
- thread->SetSynchronizationObjects(std::move(sync_objects));
- thread->SetStatus(ThreadStatus::WaitSynch);
-
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- thread->WakeAfterDelay(nano_seconds);
- thread->SetWakeupCallback(DefaultThreadWakeupCallback);
-
- system.PrepareReschedule(thread->GetProcessorID());
-
- return {RESULT_TIMEOUT, InvalidHandle};
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/synchronization_object.cpp b/src/core/hle/kernel/synchronization_object.cpp
index 43f3eef18..ba4d39157 100644
--- a/src/core/hle/kernel/synchronization_object.cpp
+++ b/src/core/hle/kernel/synchronization_object.cpp
@@ -38,68 +38,8 @@ void SynchronizationObject::RemoveWaitingThread(std::shared_ptr<Thread> thread)
waiting_threads.erase(itr);
}
-std::shared_ptr<Thread> SynchronizationObject::GetHighestPriorityReadyThread() const {
- Thread* candidate = nullptr;
- u32 candidate_priority = THREADPRIO_LOWEST + 1;
-
- for (const auto& thread : waiting_threads) {
- const ThreadStatus thread_status = thread->GetStatus();
-
- // The list of waiting threads must not contain threads that are not waiting to be awakened.
- ASSERT_MSG(thread_status == ThreadStatus::WaitSynch ||
- thread_status == ThreadStatus::WaitHLEEvent,
- "Inconsistent thread statuses in waiting_threads");
-
- if (thread->GetPriority() >= candidate_priority)
- continue;
-
- if (ShouldWait(thread.get()))
- continue;
-
- candidate = thread.get();
- candidate_priority = thread->GetPriority();
- }
-
- return SharedFrom(candidate);
-}
-
-void SynchronizationObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
- ASSERT(!ShouldWait(thread.get()));
-
- if (!thread) {
- return;
- }
-
- if (thread->IsSleepingOnWait()) {
- for (const auto& object : thread->GetSynchronizationObjects()) {
- ASSERT(!object->ShouldWait(thread.get()));
- object->Acquire(thread.get());
- }
- } else {
- Acquire(thread.get());
- }
-
- const std::size_t index = thread->GetSynchronizationObjectIndex(SharedFrom(this));
-
- thread->ClearSynchronizationObjects();
-
- thread->CancelWakeupTimer();
-
- bool resume = true;
- if (thread->HasWakeupCallback()) {
- resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, SharedFrom(this),
- index);
- }
- if (resume) {
- thread->ResumeFromWait();
- kernel.PrepareReschedule(thread->GetProcessorID());
- }
-}
-
-void SynchronizationObject::WakeupAllWaitingThreads() {
- while (auto thread = GetHighestPriorityReadyThread()) {
- WakeupWaitingThread(thread);
- }
+void SynchronizationObject::ClearWaitingThreads() {
+ waiting_threads.clear();
}
const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const {
diff --git a/src/core/hle/kernel/synchronization_object.h b/src/core/hle/kernel/synchronization_object.h
index 741c31faf..f89b24204 100644
--- a/src/core/hle/kernel/synchronization_object.h
+++ b/src/core/hle/kernel/synchronization_object.h
@@ -12,6 +12,7 @@
namespace Kernel {
class KernelCore;
+class Synchronization;
class Thread;
/// Class that represents a Kernel object that a thread can be waiting on
@@ -49,24 +50,11 @@ public:
*/
void RemoveWaitingThread(std::shared_ptr<Thread> thread);
- /**
- * Wake up all threads waiting on this object that can be awoken, in priority order,
- * and set the synchronization result and output of the thread.
- */
- void WakeupAllWaitingThreads();
-
- /**
- * Wakes up a single thread waiting on this object.
- * @param thread Thread that is waiting on this object to wakeup.
- */
- void WakeupWaitingThread(std::shared_ptr<Thread> thread);
-
- /// Obtains the highest priority thread that is ready to run from this object's waiting list.
- std::shared_ptr<Thread> GetHighestPriorityReadyThread() const;
-
/// Get a const reference to the waiting threads list for debug use
const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
+ void ClearWaitingThreads();
+
protected:
bool is_signaled{}; // Tells if this sync object is signalled;
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 83e956036..da0cb26b6 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -9,12 +9,12 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/fiber.h"
#include "common/logging/log.h"
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/core_timing_util.h"
+#include "core/cpu_manager.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
@@ -23,9 +23,15 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
+#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#endif
+
namespace Kernel {
bool Thread::ShouldWait(const Thread* thread) const {
@@ -44,47 +50,28 @@ Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {}
Thread::~Thread() = default;
void Thread::Stop() {
- // Cancel any outstanding wakeup events for this thread
- Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
- global_handle);
- kernel.GlobalHandleTable().Close(global_handle);
- global_handle = 0;
- SetStatus(ThreadStatus::Dead);
- Signal();
-
- // Clean up any dangling references in objects that this thread was waiting for
- for (auto& wait_object : wait_objects) {
- wait_object->RemoveWaitingThread(SharedFrom(this));
- }
- wait_objects.clear();
-
- owner_process->UnregisterThread(this);
-
- // Mark the TLS slot in the thread's page as free.
- owner_process->FreeTLSRegion(tls_address);
-}
+ {
+ SchedulerLock lock(kernel);
+ SetStatus(ThreadStatus::Dead);
+ Signal();
+ kernel.GlobalHandleTable().Close(global_handle);
-void Thread::WakeAfterDelay(s64 nanoseconds) {
- // Don't schedule a wakeup if the thread wants to wait forever
- if (nanoseconds == -1)
- return;
-
- // This function might be called from any thread so we have to be cautious and use the
- // thread-safe version of ScheduleEvent.
- const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
- Core::System::GetInstance().CoreTiming().ScheduleEvent(
- cycles, kernel.ThreadWakeupCallbackEventType(), global_handle);
-}
+ if (owner_process) {
+ owner_process->UnregisterThread(this);
-void Thread::CancelWakeupTimer() {
- Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
- global_handle);
+ // Mark the TLS slot in the thread's page as free.
+ owner_process->FreeTLSRegion(tls_address);
+ }
+ arm_interface.reset();
+ has_exited = true;
+ }
+ global_handle = 0;
}
void Thread::ResumeFromWait() {
- ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
-
+ SchedulerLock lock(kernel);
switch (status) {
+ case ThreadStatus::Paused:
case ThreadStatus::WaitSynch:
case ThreadStatus::WaitHLEEvent:
case ThreadStatus::WaitSleep:
@@ -92,12 +79,13 @@ void Thread::ResumeFromWait() {
case ThreadStatus::WaitMutex:
case ThreadStatus::WaitCondVar:
case ThreadStatus::WaitArb:
+ case ThreadStatus::Dormant:
break;
case ThreadStatus::Ready:
// The thread's wakeup callback must have already been cleared when the thread was first
// awoken.
- ASSERT(wakeup_callback == nullptr);
+ ASSERT(hle_callback == nullptr);
// If the thread is waiting on multiple wait objects, it might be awoken more than once
// before actually resuming. We can ignore subsequent wakeups if the thread status has
// already been set to ThreadStatus::Ready.
@@ -113,24 +101,31 @@ void Thread::ResumeFromWait() {
return;
}
- wakeup_callback = nullptr;
+ SetStatus(ThreadStatus::Ready);
+}
- if (activity == ThreadActivity::Paused) {
- SetStatus(ThreadStatus::Paused);
- return;
- }
+void Thread::OnWakeUp() {
+ SchedulerLock lock(kernel);
+
+ SetStatus(ThreadStatus::Ready);
+}
+ResultCode Thread::Start() {
+ SchedulerLock lock(kernel);
SetStatus(ThreadStatus::Ready);
+ return RESULT_SUCCESS;
}
void Thread::CancelWait() {
- if (GetSchedulingStatus() != ThreadSchedStatus::Paused) {
+ SchedulerLock lock(kernel);
+ if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) {
is_sync_cancelled = true;
return;
}
+ // TODO(Blinkhawk): Implement cancel of server session
is_sync_cancelled = false;
- SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED);
- ResumeFromWait();
+ SetSynchronizationResults(nullptr, ERR_SYNCHRONIZATION_CANCELED);
+ SetStatus(ThreadStatus::Ready);
}
static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top,
@@ -148,16 +143,32 @@ static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context,
context.pc = entry_point;
context.sp = stack_top;
// TODO(merry): Perform a hardware test to determine the below value.
- // AHP = 0, DN = 1, FTZ = 1, RMode = Round towards zero
- context.fpcr = 0x03C00000;
+ context.fpcr = 0;
+}
+
+std::shared_ptr<Common::Fiber>& Thread::GetHostContext() {
+ return host_context;
+}
+
+ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point, u32 priority,
+ u64 arg, s32 processor_id, VAddr stack_top,
+ Process* owner_process) {
+ std::function<void(void*)> init_func = Core::CpuManager::GetGuestThreadStartFunc();
+ void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
+ return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top,
+ owner_process, std::move(init_func), init_func_parameter);
}
-ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::string name,
- VAddr entry_point, u32 priority, u64 arg,
- s32 processor_id, VAddr stack_top,
- Process& owner_process) {
+ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point, u32 priority,
+ u64 arg, s32 processor_id, VAddr stack_top,
+ Process* owner_process,
+ std::function<void(void*)>&& thread_start_func,
+ void* thread_start_parameter) {
+ auto& kernel = system.Kernel();
// Check if priority is in ranged. Lowest priority -> highest priority id.
- if (priority > THREADPRIO_LOWEST) {
+ if (priority > THREADPRIO_LOWEST && ((type_flags & THREADTYPE_IDLE) == 0)) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
return ERR_INVALID_THREAD_PRIORITY;
}
@@ -167,11 +178,12 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::strin
return ERR_INVALID_PROCESSOR_ID;
}
- auto& system = Core::System::GetInstance();
- if (!system.Memory().IsValidVirtualAddress(owner_process, entry_point)) {
- LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
- // TODO (bunnei): Find the correct error code to use here
- return RESULT_UNKNOWN;
+ if (owner_process) {
+ if (!system.Memory().IsValidVirtualAddress(*owner_process, entry_point)) {
+ LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
+ // TODO (bunnei): Find the correct error code to use here
+ return RESULT_UNKNOWN;
+ }
}
std::shared_ptr<Thread> thread = std::make_shared<Thread>(kernel);
@@ -182,51 +194,72 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::strin
thread->stack_top = stack_top;
thread->tpidr_el0 = 0;
thread->nominal_priority = thread->current_priority = priority;
- thread->last_running_ticks = system.CoreTiming().GetTicks();
+ thread->last_running_ticks = 0;
thread->processor_id = processor_id;
thread->ideal_core = processor_id;
thread->affinity_mask = 1ULL << processor_id;
- thread->wait_objects.clear();
+ thread->wait_objects = nullptr;
thread->mutex_wait_address = 0;
thread->condvar_wait_address = 0;
thread->wait_handle = 0;
thread->name = std::move(name);
thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap();
- thread->owner_process = &owner_process;
- auto& scheduler = kernel.GlobalScheduler();
- scheduler.AddThread(thread);
- thread->tls_address = thread->owner_process->CreateTLSRegion();
+ thread->owner_process = owner_process;
+ thread->type = type_flags;
+ if ((type_flags & THREADTYPE_IDLE) == 0) {
+ auto& scheduler = kernel.GlobalScheduler();
+ scheduler.AddThread(thread);
+ }
+ if (owner_process) {
+ thread->tls_address = thread->owner_process->CreateTLSRegion();
+ thread->owner_process->RegisterThread(thread.get());
+ } else {
+ thread->tls_address = 0;
+ }
- thread->owner_process->RegisterThread(thread.get());
+ thread->arm_interface.reset();
+ if ((type_flags & THREADTYPE_HLE) == 0) {
+#ifdef ARCHITECTURE_x86_64
+ if (owner_process && !owner_process->Is64BitProcess()) {
+ thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
+ system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
+ processor_id);
+ } else {
+ thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
+ system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
+ processor_id);
+ }
+#else
+#error Platform not supported yet.
+#endif
- ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top),
- static_cast<u32>(entry_point), static_cast<u32>(arg));
- ResetThreadContext64(thread->context_64, stack_top, entry_point, arg);
+ ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top),
+ static_cast<u32>(entry_point), static_cast<u32>(arg));
+ ResetThreadContext64(thread->context_64, stack_top, entry_point, arg);
+ }
+ thread->host_context =
+ std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter);
return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
}
void Thread::SetPriority(u32 priority) {
+ SchedulerLock lock(kernel);
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
"Invalid priority value.");
nominal_priority = priority;
UpdatePriority();
}
-void Thread::SetWaitSynchronizationResult(ResultCode result) {
- context_32.cpu_registers[0] = result.raw;
- context_64.cpu_registers[0] = result.raw;
-}
-
-void Thread::SetWaitSynchronizationOutput(s32 output) {
- context_32.cpu_registers[1] = output;
- context_64.cpu_registers[1] = output;
+void Thread::SetSynchronizationResults(SynchronizationObject* object, ResultCode result) {
+ signaling_object = object;
+ signaling_result = result;
}
s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const {
- ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
- const auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object);
- return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1);
+ ASSERT_MSG(!wait_objects->empty(), "Thread is not waiting for anything");
+ const auto match = std::find(wait_objects->rbegin(), wait_objects->rend(), object);
+ return static_cast<s32>(std::distance(match, wait_objects->rend()) - 1);
}
VAddr Thread::GetCommandBufferAddress() const {
@@ -235,6 +268,14 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + command_header_offset;
}
+Core::ARM_Interface& Thread::ArmInterface() {
+ return *arm_interface;
+}
+
+const Core::ARM_Interface& Thread::ArmInterface() const {
+ return *arm_interface;
+}
+
void Thread::SetStatus(ThreadStatus new_status) {
if (new_status == status) {
return;
@@ -256,10 +297,6 @@ void Thread::SetStatus(ThreadStatus new_status) {
break;
}
- if (status == ThreadStatus::Running) {
- last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
- }
-
status = new_status;
}
@@ -340,75 +377,116 @@ void Thread::UpdatePriority() {
lock_owner->UpdatePriority();
}
-void Thread::ChangeCore(u32 core, u64 mask) {
- SetCoreAndAffinityMask(core, mask);
-}
-
bool Thread::AllSynchronizationObjectsReady() const {
- return std::none_of(wait_objects.begin(), wait_objects.end(),
+ return std::none_of(wait_objects->begin(), wait_objects->end(),
[this](const std::shared_ptr<SynchronizationObject>& object) {
return object->ShouldWait(this);
});
}
-bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
- std::shared_ptr<SynchronizationObject> object,
- std::size_t index) {
- ASSERT(wakeup_callback);
- return wakeup_callback(reason, std::move(thread), std::move(object), index);
+bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) {
+ ASSERT(hle_callback);
+ return hle_callback(std::move(thread));
}
-void Thread::SetActivity(ThreadActivity value) {
- activity = value;
+ResultCode Thread::SetActivity(ThreadActivity value) {
+ SchedulerLock lock(kernel);
+
+ auto sched_status = GetSchedulingStatus();
+
+ if (sched_status != ThreadSchedStatus::Runnable && sched_status != ThreadSchedStatus::Paused) {
+ return ERR_INVALID_STATE;
+ }
+
+ if (IsPendingTermination()) {
+ return RESULT_SUCCESS;
+ }
if (value == ThreadActivity::Paused) {
- // Set status if not waiting
- if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
- SetStatus(ThreadStatus::Paused);
- kernel.PrepareReschedule(processor_id);
+ if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) != 0) {
+ return ERR_INVALID_STATE;
+ }
+ AddSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
+ } else {
+ if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) == 0) {
+ return ERR_INVALID_STATE;
}
- } else if (status == ThreadStatus::Paused) {
- // Ready to reschedule
- ResumeFromWait();
+ RemoveSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
}
+ return RESULT_SUCCESS;
}
-void Thread::Sleep(s64 nanoseconds) {
- // Sleep current thread and check for next thread to schedule
- SetStatus(ThreadStatus::WaitSleep);
+ResultCode Thread::Sleep(s64 nanoseconds) {
+ Handle event_handle{};
+ {
+ SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
+ SetStatus(ThreadStatus::WaitSleep);
+ }
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- WakeAfterDelay(nanoseconds);
+ if (event_handle != InvalidHandle) {
+ auto& time_manager = kernel.TimeManager();
+ time_manager.UnscheduleTimeEvent(event_handle);
+ }
+ return RESULT_SUCCESS;
}
-bool Thread::YieldSimple() {
- auto& scheduler = kernel.GlobalScheduler();
- return scheduler.YieldThread(this);
+std::pair<ResultCode, bool> Thread::YieldSimple() {
+ bool is_redundant = false;
+ {
+ SchedulerLock lock(kernel);
+ is_redundant = kernel.GlobalScheduler().YieldThread(this);
+ }
+ return {RESULT_SUCCESS, is_redundant};
+}
+
+std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() {
+ bool is_redundant = false;
+ {
+ SchedulerLock lock(kernel);
+ is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this);
+ }
+ return {RESULT_SUCCESS, is_redundant};
+}
+
+std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() {
+ bool is_redundant = false;
+ {
+ SchedulerLock lock(kernel);
+ is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this);
+ }
+ return {RESULT_SUCCESS, is_redundant};
}
-bool Thread::YieldAndBalanceLoad() {
- auto& scheduler = kernel.GlobalScheduler();
- return scheduler.YieldThreadAndBalanceLoad(this);
+void Thread::AddSchedulingFlag(ThreadSchedFlags flag) {
+ const u32 old_state = scheduling_state;
+ pausing_state |= static_cast<u32>(flag);
+ const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
+ scheduling_state = base_scheduling | pausing_state;
+ kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
}
-bool Thread::YieldAndWaitForLoadBalancing() {
- auto& scheduler = kernel.GlobalScheduler();
- return scheduler.YieldThreadAndWaitForLoadBalancing(this);
+void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
+ const u32 old_state = scheduling_state;
+ pausing_state &= ~static_cast<u32>(flag);
+ const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
+ scheduling_state = base_scheduling | pausing_state;
+ kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
}
void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
- const u32 old_flags = scheduling_state;
+ const u32 old_state = scheduling_state;
scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
static_cast<u32>(new_status);
- AdjustSchedulingOnStatus(old_flags);
+ kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
}
void Thread::SetCurrentPriority(u32 new_priority) {
const u32 old_priority = std::exchange(current_priority, new_priority);
- AdjustSchedulingOnPriority(old_priority);
+ kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority);
}
ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
+ SchedulerLock lock(kernel);
const auto HighestSetCore = [](u64 mask, u32 max_cores) {
for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) {
if (((mask >> core) & 1) != 0) {
@@ -422,6 +500,8 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
if (new_core == THREADPROCESSORID_DONT_UPDATE) {
new_core = use_override ? ideal_core_override : ideal_core;
if ((new_affinity_mask & (1ULL << new_core)) == 0) {
+ LOG_ERROR(Kernel, "New affinity mask is incorrect! new_core={}, new_affinity_mask={}",
+ new_core, new_affinity_mask);
return ERR_INVALID_COMBINATION;
}
}
@@ -440,118 +520,10 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
processor_id = ideal_core;
}
}
- AdjustSchedulingOnAffinity(old_affinity_mask, old_core);
+ kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core);
}
}
return RESULT_SUCCESS;
}
-void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
- if (old_flags == scheduling_state) {
- return;
- }
-
- auto& scheduler = kernel.GlobalScheduler();
- if (static_cast<ThreadSchedStatus>(old_flags & static_cast<u32>(ThreadSchedMasks::LowMask)) ==
- ThreadSchedStatus::Runnable) {
- // In this case the thread was running, now it's pausing/exitting
- if (processor_id >= 0) {
- scheduler.Unschedule(current_priority, static_cast<u32>(processor_id), this);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
- scheduler.Unsuggest(current_priority, core, this);
- }
- }
- } else if (GetSchedulingStatus() == ThreadSchedStatus::Runnable) {
- // The thread is now set to running from being stopped
- if (processor_id >= 0) {
- scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
- scheduler.Suggest(current_priority, core, this);
- }
- }
- }
-
- scheduler.SetReselectionPending();
-}
-
-void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
- if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) {
- return;
- }
- auto& scheduler = kernel.GlobalScheduler();
- if (processor_id >= 0) {
- scheduler.Unschedule(old_priority, static_cast<u32>(processor_id), this);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
- scheduler.Unsuggest(old_priority, core, this);
- }
- }
-
- // Add thread to the new priority queues.
- Thread* current_thread = GetCurrentThread();
-
- if (processor_id >= 0) {
- if (current_thread == this) {
- scheduler.SchedulePrepend(current_priority, static_cast<u32>(processor_id), this);
- } else {
- scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this);
- }
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
- scheduler.Suggest(current_priority, core, this);
- }
- }
-
- scheduler.SetReselectionPending();
-}
-
-void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
- auto& scheduler = kernel.GlobalScheduler();
- if (GetSchedulingStatus() != ThreadSchedStatus::Runnable ||
- current_priority >= THREADPRIO_COUNT) {
- return;
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (((old_affinity_mask >> core) & 1) != 0) {
- if (core == static_cast<u32>(old_core)) {
- scheduler.Unschedule(current_priority, core, this);
- } else {
- scheduler.Unsuggest(current_priority, core, this);
- }
- }
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (((affinity_mask >> core) & 1) != 0) {
- if (core == static_cast<u32>(processor_id)) {
- scheduler.Schedule(current_priority, core, this);
- } else {
- scheduler.Suggest(current_priority, core, this);
- }
- }
- }
-
- scheduler.SetReselectionPending();
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Gets the current thread
- */
-Thread* GetCurrentThread() {
- return Core::System::GetInstance().CurrentScheduler().GetCurrentThread();
-}
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 23fdef8a4..8daf79fac 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -6,26 +6,47 @@
#include <functional>
#include <string>
+#include <utility>
#include <vector>
#include "common/common_types.h"
+#include "common/spin_lock.h"
#include "core/arm/arm_interface.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h"
+namespace Common {
+class Fiber;
+}
+
+namespace Core {
+class ARM_Interface;
+class System;
+} // namespace Core
+
namespace Kernel {
+class GlobalScheduler;
class KernelCore;
class Process;
class Scheduler;
enum ThreadPriority : u32 {
- THREADPRIO_HIGHEST = 0, ///< Highest thread priority
- THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
- THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
- THREADPRIO_LOWEST = 63, ///< Lowest thread priority
- THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
+ THREADPRIO_HIGHEST = 0, ///< Highest thread priority
+ THREADPRIO_MAX_CORE_MIGRATION = 2, ///< Highest priority for a core migration
+ THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
+ THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
+ THREADPRIO_LOWEST = 63, ///< Lowest thread priority
+ THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
+};
+
+enum ThreadType : u32 {
+ THREADTYPE_USER = 0x1,
+ THREADTYPE_KERNEL = 0x2,
+ THREADTYPE_HLE = 0x4,
+ THREADTYPE_IDLE = 0x8,
+ THREADTYPE_SUSPEND = 0x10,
};
enum ThreadProcessorId : s32 {
@@ -107,26 +128,45 @@ public:
using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>;
- using WakeupCallback =
- std::function<bool(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
- std::shared_ptr<SynchronizationObject> object, std::size_t index)>;
+ using HLECallback = std::function<bool(std::shared_ptr<Thread> thread)>;
+
+ /**
+ * Creates and returns a new thread. The new thread is immediately scheduled
+ * @param system The instance of the whole system
+ * @param name The friendly name desired for the thread
+ * @param entry_point The address at which the thread should start execution
+ * @param priority The thread's priority
+ * @param arg User data to pass to the thread
+ * @param processor_id The ID(s) of the processors on which the thread is desired to be run
+ * @param stack_top The address of the thread's stack top
+ * @param owner_process The parent process for the thread, if null, it's a kernel thread
+ * @return A shared pointer to the newly created thread
+ */
+ static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id,
+ VAddr stack_top, Process* owner_process);
/**
* Creates and returns a new thread. The new thread is immediately scheduled
- * @param kernel The kernel instance this thread will be created under.
+ * @param system The instance of the whole system
* @param name The friendly name desired for the thread
* @param entry_point The address at which the thread should start execution
* @param priority The thread's priority
* @param arg User data to pass to the thread
* @param processor_id The ID(s) of the processors on which the thread is desired to be run
* @param stack_top The address of the thread's stack top
- * @param owner_process The parent process for the thread
+ * @param owner_process The parent process for the thread, if null, it's a kernel thread
+ * @param thread_start_func The function where the host context will start.
+ * @param thread_start_parameter The parameter which will passed to host context on init
* @return A shared pointer to the newly created thread
*/
- static ResultVal<std::shared_ptr<Thread>> Create(KernelCore& kernel, std::string name,
- VAddr entry_point, u32 priority, u64 arg,
- s32 processor_id, VAddr stack_top,
- Process& owner_process);
+ static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id,
+ VAddr stack_top, Process* owner_process,
+ std::function<void(void*)>&& thread_start_func,
+ void* thread_start_parameter);
std::string GetName() const override {
return name;
@@ -181,7 +221,7 @@ public:
void UpdatePriority();
/// Changes the core that the thread is running or scheduled to run on.
- void ChangeCore(u32 core, u64 mask);
+ ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
/**
* Gets the thread's thread ID
@@ -194,6 +234,10 @@ public:
/// Resumes a thread from waiting
void ResumeFromWait();
+ void OnWakeUp();
+
+ ResultCode Start();
+
/// Cancels a waiting operation that this thread may or may not be within.
///
/// When the thread is within a waiting state, this will set the thread's
@@ -202,26 +246,19 @@ public:
///
void CancelWait();
- /**
- * Schedules an event to wake up the specified thread after the specified delay
- * @param nanoseconds The time this thread will be allowed to sleep for
- */
- void WakeAfterDelay(s64 nanoseconds);
+ void SetSynchronizationResults(SynchronizationObject* object, ResultCode result);
- /// Cancel any outstanding wakeup events for this thread
- void CancelWakeupTimer();
+ Core::ARM_Interface& ArmInterface();
- /**
- * Sets the result after the thread awakens (from svcWaitSynchronization)
- * @param result Value to set to the returned result
- */
- void SetWaitSynchronizationResult(ResultCode result);
+ const Core::ARM_Interface& ArmInterface() const;
- /**
- * Sets the output parameter value after the thread awakens (from svcWaitSynchronization)
- * @param output Value to set to the output parameter
- */
- void SetWaitSynchronizationOutput(s32 output);
+ SynchronizationObject* GetSignalingObject() const {
+ return signaling_object;
+ }
+
+ ResultCode GetSignalingResult() const {
+ return signaling_result;
+ }
/**
* Retrieves the index that this particular object occupies in the list of objects
@@ -269,11 +306,6 @@ public:
*/
VAddr GetCommandBufferAddress() const;
- /// Returns whether this thread is waiting on objects from a WaitSynchronization call.
- bool IsSleepingOnWait() const {
- return status == ThreadStatus::WaitSynch;
- }
-
ThreadContext32& GetContext32() {
return context_32;
}
@@ -290,6 +322,28 @@ public:
return context_64;
}
+ bool IsHLEThread() const {
+ return (type & THREADTYPE_HLE) != 0;
+ }
+
+ bool IsSuspendThread() const {
+ return (type & THREADTYPE_SUSPEND) != 0;
+ }
+
+ bool IsIdleThread() const {
+ return (type & THREADTYPE_IDLE) != 0;
+ }
+
+ bool WasRunning() const {
+ return was_running;
+ }
+
+ void SetWasRunning(bool value) {
+ was_running = value;
+ }
+
+ std::shared_ptr<Common::Fiber>& GetHostContext();
+
ThreadStatus GetStatus() const {
return status;
}
@@ -325,18 +379,18 @@ public:
}
const ThreadSynchronizationObjects& GetSynchronizationObjects() const {
- return wait_objects;
+ return *wait_objects;
}
- void SetSynchronizationObjects(ThreadSynchronizationObjects objects) {
- wait_objects = std::move(objects);
+ void SetSynchronizationObjects(ThreadSynchronizationObjects* objects) {
+ wait_objects = objects;
}
void ClearSynchronizationObjects() {
- for (const auto& waiting_object : wait_objects) {
+ for (const auto& waiting_object : *wait_objects) {
waiting_object->RemoveWaitingThread(SharedFrom(this));
}
- wait_objects.clear();
+ wait_objects->clear();
}
/// Determines whether all the objects this thread is waiting on are ready.
@@ -386,26 +440,35 @@ public:
arb_wait_address = address;
}
- bool HasWakeupCallback() const {
- return wakeup_callback != nullptr;
+ bool HasHLECallback() const {
+ return hle_callback != nullptr;
}
- void SetWakeupCallback(WakeupCallback callback) {
- wakeup_callback = std::move(callback);
+ void SetHLECallback(HLECallback callback) {
+ hle_callback = std::move(callback);
}
- void InvalidateWakeupCallback() {
- SetWakeupCallback(nullptr);
+ void SetHLETimeEvent(Handle time_event) {
+ hle_time_event = time_event;
}
- /**
- * Invokes the thread's wakeup callback.
- *
- * @pre A valid wakeup callback has been set. Violating this precondition
- * will cause an assertion to trigger.
- */
- bool InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
- std::shared_ptr<SynchronizationObject> object, std::size_t index);
+ void SetHLESyncObject(SynchronizationObject* object) {
+ hle_object = object;
+ }
+
+ Handle GetHLETimeEvent() const {
+ return hle_time_event;
+ }
+
+ SynchronizationObject* GetHLESyncObject() const {
+ return hle_object;
+ }
+
+ void InvalidateHLECallback() {
+ SetHLECallback(nullptr);
+ }
+
+ bool InvokeHLECallback(std::shared_ptr<Thread> thread);
u32 GetIdealCore() const {
return ideal_core;
@@ -415,23 +478,19 @@ public:
return affinity_mask;
}
- ThreadActivity GetActivity() const {
- return activity;
- }
-
- void SetActivity(ThreadActivity value);
+ ResultCode SetActivity(ThreadActivity value);
/// Sleeps this thread for the given amount of nanoseconds.
- void Sleep(s64 nanoseconds);
+ ResultCode Sleep(s64 nanoseconds);
/// Yields this thread without rebalancing loads.
- bool YieldSimple();
+ std::pair<ResultCode, bool> YieldSimple();
/// Yields this thread and does a load rebalancing.
- bool YieldAndBalanceLoad();
+ std::pair<ResultCode, bool> YieldAndBalanceLoad();
/// Yields this thread and if the core is left idle, loads are rebalanced
- bool YieldAndWaitForLoadBalancing();
+ std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing();
void IncrementYieldCount() {
yield_count++;
@@ -446,6 +505,10 @@ public:
static_cast<u32>(ThreadSchedMasks::LowMask));
}
+ bool IsRunnable() const {
+ return scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable);
+ }
+
bool IsRunning() const {
return is_running;
}
@@ -466,17 +529,65 @@ public:
return global_handle;
}
+ bool IsWaitingForArbitration() const {
+ return waiting_for_arbitration;
+ }
+
+ void WaitForArbitration(bool set) {
+ waiting_for_arbitration = set;
+ }
+
+ bool IsWaitingSync() const {
+ return is_waiting_on_sync;
+ }
+
+ void SetWaitingSync(bool is_waiting) {
+ is_waiting_on_sync = is_waiting;
+ }
+
+ bool IsPendingTermination() const {
+ return will_be_terminated || GetSchedulingStatus() == ThreadSchedStatus::Exited;
+ }
+
+ bool IsPaused() const {
+ return pausing_state != 0;
+ }
+
+ bool IsContinuousOnSVC() const {
+ return is_continuous_on_svc;
+ }
+
+ void SetContinuousOnSVC(bool is_continuous) {
+ is_continuous_on_svc = is_continuous;
+ }
+
+ bool IsPhantomMode() const {
+ return is_phantom_mode;
+ }
+
+ void SetPhantomMode(bool phantom) {
+ is_phantom_mode = phantom;
+ }
+
+ bool HasExited() const {
+ return has_exited;
+ }
+
private:
+ friend class GlobalScheduler;
+ friend class Scheduler;
+
void SetSchedulingStatus(ThreadSchedStatus new_status);
- void SetCurrentPriority(u32 new_priority);
- ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
+ void AddSchedulingFlag(ThreadSchedFlags flag);
+ void RemoveSchedulingFlag(ThreadSchedFlags flag);
- void AdjustSchedulingOnStatus(u32 old_flags);
- void AdjustSchedulingOnPriority(u32 old_priority);
- void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core);
+ void SetCurrentPriority(u32 new_priority);
+ Common::SpinLock context_guard{};
ThreadContext32 context_32{};
ThreadContext64 context_64{};
+ std::unique_ptr<Core::ARM_Interface> arm_interface{};
+ std::shared_ptr<Common::Fiber> host_context{};
u64 thread_id = 0;
@@ -485,6 +596,8 @@ private:
VAddr entry_point = 0;
VAddr stack_top = 0;
+ ThreadType type;
+
/// Nominal thread priority, as set by the emulated application.
/// The nominal priority is the thread priority without priority
/// inheritance taken into account.
@@ -509,7 +622,10 @@ private:
/// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization.
- ThreadSynchronizationObjects wait_objects;
+ ThreadSynchronizationObjects* wait_objects;
+
+ SynchronizationObject* signaling_object;
+ ResultCode signaling_result{RESULT_SUCCESS};
/// List of threads that are waiting for a mutex that is held by this thread.
MutexWaitingThreads wait_mutex_threads;
@@ -526,36 +642,40 @@ private:
/// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0};
+ bool waiting_for_arbitration{};
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle global_handle = 0;
- /// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
- /// was waiting via WaitSynchronization then the object will be the last object that became
- /// available. In case of a timeout, the object will be nullptr.
- WakeupCallback wakeup_callback;
+ /// Callback for HLE Events
+ HLECallback hle_callback;
+ Handle hle_time_event;
+ SynchronizationObject* hle_object;
Scheduler* scheduler = nullptr;
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
- ThreadActivity activity = ThreadActivity::Normal;
-
s32 ideal_core_override = -1;
u64 affinity_mask_override = 0x1;
u32 affinity_override_count = 0;
u32 scheduling_state = 0;
+ u32 pausing_state = 0;
bool is_running = false;
+ bool is_waiting_on_sync = false;
bool is_sync_cancelled = false;
+ bool is_continuous_on_svc = false;
+
+ bool will_be_terminated = false;
+ bool is_phantom_mode = false;
+ bool has_exited = false;
+
+ bool was_running = false;
+
std::string name;
};
-/**
- * Gets the current thread
- */
-Thread* GetCurrentThread();
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index 21b290468..95f2446c9 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -8,30 +8,38 @@
#include "core/core_timing_util.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
-TimeManager::TimeManager(Core::System& system) : system{system} {
+TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type = Core::Timing::CreateEvent(
- "Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
- Handle proper_handle = static_cast<Handle>(thread_handle);
- std::shared_ptr<Thread> thread =
- this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
- thread->ResumeFromWait();
+ "Kernel::TimeManagerCallback",
+ [this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
+ const SchedulerLock lock(system.Kernel());
+ const auto proper_handle = static_cast<Handle>(thread_handle);
+ if (cancelled_events[proper_handle]) {
+ return;
+ }
+ auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
+ thread->OnWakeUp();
});
}
void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) {
+ event_handle = timetask->GetGlobalHandle();
if (nanoseconds > 0) {
ASSERT(timetask);
- event_handle = timetask->GetGlobalHandle();
- const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
- system.CoreTiming().ScheduleEvent(cycles, time_manager_event_type, event_handle);
+ ASSERT(timetask->GetStatus() != ThreadStatus::Ready);
+ ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex);
+ system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds},
+ time_manager_event_type, event_handle);
} else {
event_handle = InvalidHandle;
}
+ cancelled_events[event_handle] = false;
}
void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
@@ -39,6 +47,12 @@ void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
return;
}
system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle);
+ cancelled_events[event_handle] = true;
+}
+
+void TimeManager::CancelTimeEvent(Thread* time_task) {
+ Handle event_handle = time_task->GetGlobalHandle();
+ UnscheduleTimeEvent(event_handle);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h
index eaec486d1..307a18765 100644
--- a/src/core/hle/kernel/time_manager.h
+++ b/src/core/hle/kernel/time_manager.h
@@ -5,6 +5,7 @@
#pragma once
#include <memory>
+#include <unordered_map>
#include "core/hle/kernel/object.h"
@@ -35,9 +36,12 @@ public:
/// Unschedule an existing time event
void UnscheduleTimeEvent(Handle event_handle);
+ void CancelTimeEvent(Thread* time_task);
+
private:
Core::System& system;
std::shared_ptr<Core::Timing::EventType> time_manager_event_type;
+ std::unordered_map<Handle, bool> cancelled_events;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp
index f2d3f8b49..765f408c3 100644
--- a/src/core/hle/kernel/transfer_memory.cpp
+++ b/src/core/hle/kernel/transfer_memory.cpp
@@ -2,17 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/transfer_memory.h"
#include "core/hle/result.h"
#include "core/memory.h"
namespace Kernel {
-TransferMemory::TransferMemory(KernelCore& kernel, Memory::Memory& memory)
+TransferMemory::TransferMemory(KernelCore& kernel, Core::Memory::Memory& memory)
: Object{kernel}, memory{memory} {}
TransferMemory::~TransferMemory() {
@@ -20,14 +19,15 @@ TransferMemory::~TransferMemory() {
Reset();
}
-std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, Memory::Memory& memory,
- VAddr base_address, u64 size,
- MemoryPermission permissions) {
+std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel,
+ Core::Memory::Memory& memory,
+ VAddr base_address, std::size_t size,
+ Memory::MemoryPermission permissions) {
std::shared_ptr<TransferMemory> transfer_memory{
std::make_shared<TransferMemory>(kernel, memory)};
transfer_memory->base_address = base_address;
- transfer_memory->memory_size = size;
+ transfer_memory->size = size;
transfer_memory->owner_permissions = permissions;
transfer_memory->owner_process = kernel.CurrentProcess();
@@ -38,98 +38,12 @@ const u8* TransferMemory::GetPointer() const {
return memory.GetPointer(base_address);
}
-u64 TransferMemory::GetSize() const {
- return memory_size;
-}
-
-ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission permissions) {
- if (memory_size != size) {
- return ERR_INVALID_SIZE;
- }
-
- if (owner_permissions != permissions) {
- return ERR_INVALID_STATE;
- }
-
- if (is_mapped) {
- return ERR_INVALID_STATE;
- }
-
- backing_block = std::make_shared<PhysicalMemory>(size);
-
- const auto map_state = owner_permissions == MemoryPermission::None
- ? MemoryState::TransferMemoryIsolated
- : MemoryState::TransferMemory;
- auto& vm_manager = owner_process->VMManager();
- const auto map_result = vm_manager.MapMemoryBlock(address, backing_block, 0, size, map_state);
- if (map_result.Failed()) {
- return map_result.Code();
- }
-
- is_mapped = true;
- return RESULT_SUCCESS;
-}
-
ResultCode TransferMemory::Reserve() {
- auto& vm_manager{owner_process->VMManager()};
- const auto check_range_result{vm_manager.CheckRangeState(
- base_address, memory_size, MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated,
- MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated, VMAPermission::All,
- VMAPermission::ReadWrite, MemoryAttribute::Mask, MemoryAttribute::None,
- MemoryAttribute::IpcAndDeviceMapped)};
-
- if (check_range_result.Failed()) {
- return check_range_result.Code();
- }
-
- auto [state_, permissions_, attribute] = *check_range_result;
-
- if (const auto result{vm_manager.ReprotectRange(
- base_address, memory_size, SharedMemory::ConvertPermissions(owner_permissions))};
- result.IsError()) {
- return result;
- }
-
- return vm_manager.SetMemoryAttribute(base_address, memory_size, MemoryAttribute::Mask,
- attribute | MemoryAttribute::Locked);
+ return owner_process->PageTable().ReserveTransferMemory(base_address, size, owner_permissions);
}
ResultCode TransferMemory::Reset() {
- auto& vm_manager{owner_process->VMManager()};
- if (const auto result{vm_manager.CheckRangeState(
- base_address, memory_size,
- MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated,
- MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated, VMAPermission::None,
- VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::Locked,
- MemoryAttribute::IpcAndDeviceMapped)};
- result.Failed()) {
- return result.Code();
- }
-
- if (const auto result{
- vm_manager.ReprotectRange(base_address, memory_size, VMAPermission::ReadWrite)};
- result.IsError()) {
- return result;
- }
-
- return vm_manager.SetMemoryAttribute(base_address, memory_size, MemoryAttribute::Mask,
- MemoryAttribute::None);
-}
-
-ResultCode TransferMemory::UnmapMemory(VAddr address, u64 size) {
- if (memory_size != size) {
- return ERR_INVALID_SIZE;
- }
-
- auto& vm_manager = owner_process->VMManager();
- const auto result = vm_manager.UnmapRange(address, size);
-
- if (result.IsError()) {
- return result;
- }
-
- is_mapped = false;
- return RESULT_SUCCESS;
+ return owner_process->PageTable().ResetTransferMemory(base_address, size);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h
index 6e388536a..05e9f7464 100644
--- a/src/core/hle/kernel/transfer_memory.h
+++ b/src/core/hle/kernel/transfer_memory.h
@@ -6,12 +6,13 @@
#include <memory>
+#include "core/hle/kernel/memory/memory_block.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/physical_memory.h"
union ResultCode;
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
@@ -20,8 +21,6 @@ namespace Kernel {
class KernelCore;
class Process;
-enum class MemoryPermission : u32;
-
/// Defines the interface for transfer memory objects.
///
/// Transfer memory is typically used for the purpose of
@@ -30,14 +29,14 @@ enum class MemoryPermission : u32;
///
class TransferMemory final : public Object {
public:
- explicit TransferMemory(KernelCore& kernel, Memory::Memory& memory);
+ explicit TransferMemory(KernelCore& kernel, Core::Memory::Memory& memory);
~TransferMemory() override;
static constexpr HandleType HANDLE_TYPE = HandleType::TransferMemory;
- static std::shared_ptr<TransferMemory> Create(KernelCore& kernel, Memory::Memory& memory,
- VAddr base_address, u64 size,
- MemoryPermission permissions);
+ static std::shared_ptr<TransferMemory> Create(KernelCore& kernel, Core::Memory::Memory& memory,
+ VAddr base_address, std::size_t size,
+ Memory::MemoryPermission permissions);
TransferMemory(const TransferMemory&) = delete;
TransferMemory& operator=(const TransferMemory&) = delete;
@@ -61,29 +60,9 @@ public:
const u8* GetPointer() const;
/// Gets the size of the memory backing this instance in bytes.
- u64 GetSize() const;
-
- /// Attempts to map transfer memory with the given range and memory permissions.
- ///
- /// @param address The base address to being mapping memory at.
- /// @param size The size of the memory to map, in bytes.
- /// @param permissions The memory permissions to check against when mapping memory.
- ///
- /// @pre The given address, size, and memory permissions must all match
- /// the same values that were given when creating the transfer memory
- /// instance.
- ///
- ResultCode MapMemory(VAddr address, u64 size, MemoryPermission permissions);
-
- /// Unmaps the transfer memory with the given range
- ///
- /// @param address The base address to begin unmapping memory at.
- /// @param size The size of the memory to unmap, in bytes.
- ///
- /// @pre The given address and size must be the same as the ones used
- /// to create the transfer memory instance.
- ///
- ResultCode UnmapMemory(VAddr address, u64 size);
+ constexpr std::size_t GetSize() const {
+ return size;
+ }
/// Reserves the region to be used for the transfer memory, called after the transfer memory is
/// created.
@@ -94,25 +73,19 @@ public:
ResultCode Reset();
private:
- /// Memory block backing this instance.
- std::shared_ptr<PhysicalMemory> backing_block;
-
/// The base address for the memory managed by this instance.
- VAddr base_address = 0;
+ VAddr base_address{};
/// Size of the memory, in bytes, that this instance manages.
- u64 memory_size = 0;
+ std::size_t size{};
/// The memory permissions that are applied to this instance.
- MemoryPermission owner_permissions{};
+ Memory::MemoryPermission owner_permissions{};
/// The process that this transfer memory instance was created under.
- Process* owner_process = nullptr;
-
- /// Whether or not this transfer memory instance has mapped memory.
- bool is_mapped = false;
+ Process* owner_process{};
- Memory::Memory& memory;
+ Core::Memory::Memory& memory;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
deleted file mode 100644
index 024c22901..000000000
--- a/src/core/hle/kernel/vm_manager.cpp
+++ /dev/null
@@ -1,1175 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstring>
-#include <iterator>
-#include <utility>
-#include "common/alignment.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/memory_hook.h"
-#include "core/core.h"
-#include "core/file_sys/program_metadata.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/resource_limit.h"
-#include "core/hle/kernel/vm_manager.h"
-#include "core/memory.h"
-
-namespace Kernel {
-namespace {
-const char* GetMemoryStateName(MemoryState state) {
- static constexpr const char* names[] = {
- "Unmapped", "Io",
- "Normal", "Code",
- "CodeData", "Heap",
- "Shared", "Unknown1",
- "ModuleCode", "ModuleCodeData",
- "IpcBuffer0", "Stack",
- "ThreadLocal", "TransferMemoryIsolated",
- "TransferMemory", "ProcessMemory",
- "Inaccessible", "IpcBuffer1",
- "IpcBuffer3", "KernelStack",
- };
-
- return names[ToSvcMemoryState(state)];
-}
-
-// Checks if a given address range lies within a larger address range.
-constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
- VAddr address_range_end) {
- const VAddr end_address = address + size - 1;
- return address_range_begin <= address && end_address <= address_range_end - 1;
-}
-} // Anonymous namespace
-
-bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
- ASSERT(base + size == next.base);
- if (permissions != next.permissions || state != next.state || attribute != next.attribute ||
- type != next.type) {
- return false;
- }
- if ((attribute & MemoryAttribute::DeviceMapped) == MemoryAttribute::DeviceMapped) {
- // TODO: Can device mapped memory be merged sanely?
- // Not merging it may cause inaccuracies versus hardware when memory layout is queried.
- return false;
- }
- if (type == VMAType::AllocatedMemoryBlock) {
- return true;
- }
- if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) {
- return false;
- }
- if (type == VMAType::MMIO && paddr + size != next.paddr) {
- return false;
- }
- return true;
-}
-
-VMManager::VMManager(Core::System& system) : system{system} {
- // Default to assuming a 39-bit address space. This way we have a sane
- // starting point with executables that don't provide metadata.
- Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
-}
-
-VMManager::~VMManager() = default;
-
-void VMManager::Reset(FileSys::ProgramAddressSpaceType type) {
- Clear();
-
- InitializeMemoryRegionRanges(type);
-
- page_table.Resize(address_space_width);
-
- // Initialize the map with a single free region covering the entire managed space.
- VirtualMemoryArea initial_vma;
- initial_vma.size = address_space_end;
- vma_map.emplace(initial_vma.base, initial_vma);
-
- UpdatePageTableForVMA(initial_vma);
-}
-
-VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
- if (target >= address_space_end) {
- return vma_map.end();
- } else {
- return std::prev(vma_map.upper_bound(target));
- }
-}
-
-bool VMManager::IsValidHandle(VMAHandle handle) const {
- return handle != vma_map.cend();
-}
-
-ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
- std::shared_ptr<PhysicalMemory> block,
- std::size_t offset, u64 size,
- MemoryState state, VMAPermission perm) {
- ASSERT(block != nullptr);
- ASSERT(offset + size <= block->size());
-
- // This is the appropriately sized VMA that will turn into our allocation.
- CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
- VirtualMemoryArea& final_vma = vma_handle->second;
- ASSERT(final_vma.size == size);
-
- final_vma.type = VMAType::AllocatedMemoryBlock;
- final_vma.permissions = perm;
- final_vma.state = state;
- final_vma.backing_block = std::move(block);
- final_vma.offset = offset;
- UpdatePageTableForVMA(final_vma);
-
- return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
-}
-
-ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* memory, u64 size,
- MemoryState state) {
- ASSERT(memory != nullptr);
-
- // This is the appropriately sized VMA that will turn into our allocation.
- CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
- VirtualMemoryArea& final_vma = vma_handle->second;
- ASSERT(final_vma.size == size);
-
- final_vma.type = VMAType::BackingMemory;
- final_vma.permissions = VMAPermission::ReadWrite;
- final_vma.state = state;
- final_vma.backing_memory = memory;
- UpdatePageTableForVMA(final_vma);
-
- return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
-}
-
-ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
- return FindFreeRegion(GetASLRRegionBaseAddress(), GetASLRRegionEndAddress(), size);
-}
-
-ResultVal<VAddr> VMManager::FindFreeRegion(VAddr begin, VAddr end, u64 size) const {
- ASSERT(begin < end);
- ASSERT(size <= end - begin);
-
- const VMAHandle vma_handle =
- std::find_if(vma_map.begin(), vma_map.end(), [begin, end, size](const auto& vma) {
- if (vma.second.type != VMAType::Free) {
- return false;
- }
- const VAddr vma_base = vma.second.base;
- const VAddr vma_end = vma_base + vma.second.size;
- const VAddr assumed_base = (begin < vma_base) ? vma_base : begin;
- const VAddr used_range = assumed_base + size;
-
- return vma_base <= assumed_base && assumed_base < used_range && used_range < end &&
- used_range <= vma_end;
- });
-
- if (vma_handle == vma_map.cend()) {
- // TODO(Subv): Find the correct error code here.
- return RESULT_UNKNOWN;
- }
-
- const VAddr target = std::max(begin, vma_handle->second.base);
- return MakeResult<VAddr>(target);
-}
-
-ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
- MemoryState state,
- Common::MemoryHookPointer mmio_handler) {
- // This is the appropriately sized VMA that will turn into our allocation.
- CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
- VirtualMemoryArea& final_vma = vma_handle->second;
- ASSERT(final_vma.size == size);
-
- final_vma.type = VMAType::MMIO;
- final_vma.permissions = VMAPermission::ReadWrite;
- final_vma.state = state;
- final_vma.paddr = paddr;
- final_vma.mmio_handler = std::move(mmio_handler);
- UpdatePageTableForVMA(final_vma);
-
- return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
-}
-
-VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
- VirtualMemoryArea& vma = vma_handle->second;
- vma.type = VMAType::Free;
- vma.permissions = VMAPermission::None;
- vma.state = MemoryState::Unmapped;
- vma.attribute = MemoryAttribute::None;
-
- vma.backing_block = nullptr;
- vma.offset = 0;
- vma.backing_memory = nullptr;
- vma.paddr = 0;
-
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(vma_handle);
-}
-
-ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
- CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size));
- const VAddr target_end = target + size;
-
- const VMAIter end = vma_map.end();
- // The comparison against the end of the range must be done using addresses since VMAs can be
- // merged during this process, causing invalidation of the iterators.
- while (vma != end && vma->second.base < target_end) {
- vma = std::next(Unmap(vma));
- }
-
- ASSERT(FindVMA(target)->second.size >= size);
-
- return RESULT_SUCCESS;
-}
-
-VMManager::VMAHandle VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) {
- VMAIter iter = StripIterConstness(vma_handle);
-
- VirtualMemoryArea& vma = iter->second;
- vma.permissions = new_perms;
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(iter);
-}
-
-ResultCode VMManager::ReprotectRange(VAddr target, u64 size, VMAPermission new_perms) {
- CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size));
- const VAddr target_end = target + size;
-
- const VMAIter end = vma_map.end();
- // The comparison against the end of the range must be done using addresses since VMAs can be
- // merged during this process, causing invalidation of the iterators.
- while (vma != end && vma->second.base < target_end) {
- vma = std::next(StripIterConstness(Reprotect(vma, new_perms)));
- }
-
- return RESULT_SUCCESS;
-}
-
-ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
- if (size > GetHeapRegionSize()) {
- return ERR_OUT_OF_MEMORY;
- }
-
- // No need to do any additional work if the heap is already the given size.
- if (size == GetCurrentHeapSize()) {
- return MakeResult(heap_region_base);
- }
-
- if (heap_memory == nullptr) {
- // Initialize heap
- heap_memory = std::make_shared<PhysicalMemory>(size);
- heap_end = heap_region_base + size;
- } else {
- UnmapRange(heap_region_base, GetCurrentHeapSize());
- }
-
- // If necessary, expand backing vector to cover new heap extents in
- // the case of allocating. Otherwise, shrink the backing memory,
- // if a smaller heap has been requested.
- heap_memory->resize(size);
- heap_memory->shrink_to_fit();
- RefreshMemoryBlockMappings(heap_memory.get());
-
- heap_end = heap_region_base + size;
- ASSERT(GetCurrentHeapSize() == heap_memory->size());
-
- const auto mapping_result =
- MapMemoryBlock(heap_region_base, heap_memory, 0, size, MemoryState::Heap);
- if (mapping_result.Failed()) {
- return mapping_result.Code();
- }
-
- return MakeResult<VAddr>(heap_region_base);
-}
-
-ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
- // Check how much memory we've already mapped.
- const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size);
- if (mapped_size_result.Failed()) {
- return mapped_size_result.Code();
- }
-
- // If we've already mapped the desired amount, return early.
- const std::size_t mapped_size = *mapped_size_result;
- if (mapped_size == size) {
- return RESULT_SUCCESS;
- }
-
- // Check that we can map the memory we want.
- const auto res_limit = system.CurrentProcess()->GetResourceLimit();
- const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) -
- res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory);
- if (physmem_remaining < (size - mapped_size)) {
- return ERR_RESOURCE_LIMIT_EXCEEDED;
- }
-
- // Keep track of the memory regions we unmap.
- std::vector<std::pair<u64, u64>> mapped_regions;
- ResultCode result = RESULT_SUCCESS;
-
- // Iterate, trying to map memory.
- {
- const auto end_addr = target + size;
- const auto last_addr = end_addr - 1;
- VAddr cur_addr = target;
-
- auto iter = FindVMA(target);
- ASSERT(iter != vma_map.end());
-
- while (true) {
- const auto& vma = iter->second;
- const auto vma_start = vma.base;
- const auto vma_end = vma_start + vma.size;
- const auto vma_last = vma_end - 1;
-
- // Map the memory block
- const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
- if (vma.state == MemoryState::Unmapped) {
- const auto map_res =
- MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0,
- map_size, MemoryState::Heap, VMAPermission::ReadWrite);
- result = map_res.Code();
- if (result.IsError()) {
- break;
- }
-
- mapped_regions.emplace_back(cur_addr, map_size);
- }
-
- // Break once we hit the end of the range.
- if (last_addr <= vma_last) {
- break;
- }
-
- // Advance to the next block.
- cur_addr = vma_end;
- iter = FindVMA(cur_addr);
- ASSERT(iter != vma_map.end());
- }
- }
-
- // If we failed, unmap memory.
- if (result.IsError()) {
- for (const auto [unmap_address, unmap_size] : mapped_regions) {
- ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(),
- "Failed to unmap memory range.");
- }
-
- return result;
- }
-
- // Update amount of mapped physical memory.
- physical_memory_mapped += size - mapped_size;
-
- return RESULT_SUCCESS;
-}
-
-ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
- // Check how much memory is currently mapped.
- const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size);
- if (mapped_size_result.Failed()) {
- return mapped_size_result.Code();
- }
-
- // If we've already unmapped all the memory, return early.
- const std::size_t mapped_size = *mapped_size_result;
- if (mapped_size == 0) {
- return RESULT_SUCCESS;
- }
-
- // Keep track of the memory regions we unmap.
- std::vector<std::pair<u64, u64>> unmapped_regions;
- ResultCode result = RESULT_SUCCESS;
-
- // Try to unmap regions.
- {
- const auto end_addr = target + size;
- const auto last_addr = end_addr - 1;
- VAddr cur_addr = target;
-
- auto iter = FindVMA(target);
- ASSERT(iter != vma_map.end());
-
- while (true) {
- const auto& vma = iter->second;
- const auto vma_start = vma.base;
- const auto vma_end = vma_start + vma.size;
- const auto vma_last = vma_end - 1;
-
- // Unmap the memory block
- const auto unmap_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
- if (vma.state == MemoryState::Heap) {
- result = UnmapRange(cur_addr, unmap_size);
- if (result.IsError()) {
- break;
- }
-
- unmapped_regions.emplace_back(cur_addr, unmap_size);
- }
-
- // Break once we hit the end of the range.
- if (last_addr <= vma_last) {
- break;
- }
-
- // Advance to the next block.
- cur_addr = vma_end;
- iter = FindVMA(cur_addr);
- ASSERT(iter != vma_map.end());
- }
- }
-
- // If we failed, re-map regions.
- // TODO: Preserve memory contents?
- if (result.IsError()) {
- for (const auto [map_address, map_size] : unmapped_regions) {
- const auto remap_res =
- MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size,
- MemoryState::Heap, VMAPermission::None);
- ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block.");
- }
-
- return result;
- }
-
- // Update mapped amount
- physical_memory_mapped -= mapped_size;
-
- return RESULT_SUCCESS;
-}
-
-ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
- constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
- const auto src_check_result = CheckRangeState(
- src_address, size, MemoryState::All, MemoryState::Heap, VMAPermission::All,
- VMAPermission::ReadWrite, MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
-
- if (src_check_result.Failed()) {
- return src_check_result.Code();
- }
-
- const auto mirror_result =
- MirrorMemory(dst_address, src_address, size, MemoryState::ModuleCode);
- if (mirror_result.IsError()) {
- return mirror_result;
- }
-
- // Ensure we lock the source memory region.
- const auto src_vma_result = CarveVMARange(src_address, size);
- if (src_vma_result.Failed()) {
- return src_vma_result.Code();
- }
- auto src_vma_iter = *src_vma_result;
- src_vma_iter->second.attribute = MemoryAttribute::Locked;
- Reprotect(src_vma_iter, VMAPermission::Read);
-
- // The destination memory region is fine as is, however we need to make it read-only.
- return ReprotectRange(dst_address, size, VMAPermission::Read);
-}
-
-ResultCode VMManager::UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
- constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
- const auto src_check_result = CheckRangeState(
- src_address, size, MemoryState::All, MemoryState::Heap, VMAPermission::None,
- VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::Locked, ignore_attribute);
-
- if (src_check_result.Failed()) {
- return src_check_result.Code();
- }
-
- // Yes, the kernel only checks the first page of the region.
- const auto dst_check_result =
- CheckRangeState(dst_address, Memory::PAGE_SIZE, MemoryState::FlagModule,
- MemoryState::FlagModule, VMAPermission::None, VMAPermission::None,
- MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
-
- if (dst_check_result.Failed()) {
- return dst_check_result.Code();
- }
-
- const auto dst_memory_state = std::get<MemoryState>(*dst_check_result);
- const auto dst_contiguous_check_result = CheckRangeState(
- dst_address, size, MemoryState::All, dst_memory_state, VMAPermission::None,
- VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
-
- if (dst_contiguous_check_result.Failed()) {
- return dst_contiguous_check_result.Code();
- }
-
- const auto unmap_result = UnmapRange(dst_address, size);
- if (unmap_result.IsError()) {
- return unmap_result;
- }
-
- // With the mirrored portion unmapped, restore the original region's traits.
- const auto src_vma_result = CarveVMARange(src_address, size);
- if (src_vma_result.Failed()) {
- return src_vma_result.Code();
- }
- auto src_vma_iter = *src_vma_result;
- src_vma_iter->second.state = MemoryState::Heap;
- src_vma_iter->second.attribute = MemoryAttribute::None;
- Reprotect(src_vma_iter, VMAPermission::ReadWrite);
-
- if (dst_memory_state == MemoryState::ModuleCode) {
- system.InvalidateCpuInstructionCaches();
- }
-
- return unmap_result;
-}
-
-MemoryInfo VMManager::QueryMemory(VAddr address) const {
- const auto vma = FindVMA(address);
- MemoryInfo memory_info{};
-
- if (IsValidHandle(vma)) {
- memory_info.base_address = vma->second.base;
- memory_info.attributes = ToSvcMemoryAttribute(vma->second.attribute);
- memory_info.permission = static_cast<u32>(vma->second.permissions);
- memory_info.size = vma->second.size;
- memory_info.state = ToSvcMemoryState(vma->second.state);
- } else {
- memory_info.base_address = address_space_end;
- memory_info.permission = static_cast<u32>(VMAPermission::None);
- memory_info.size = 0 - address_space_end;
- memory_info.state = static_cast<u32>(MemoryState::Inaccessible);
- }
-
- return memory_info;
-}
-
-ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
- MemoryAttribute attribute) {
- constexpr auto ignore_mask =
- MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped | MemoryAttribute::Locked;
- constexpr auto attribute_mask = ~ignore_mask;
-
- const auto result = CheckRangeState(
- address, size, MemoryState::FlagUncached, MemoryState::FlagUncached, VMAPermission::None,
- VMAPermission::None, attribute_mask, MemoryAttribute::None, ignore_mask);
-
- if (result.Failed()) {
- return result.Code();
- }
-
- const auto [prev_state, prev_permissions, prev_attributes] = *result;
- const auto new_attribute = (prev_attributes & ~mask) | (mask & attribute);
-
- const auto carve_result = CarveVMARange(address, size);
- if (carve_result.Failed()) {
- return carve_result.Code();
- }
-
- auto vma_iter = *carve_result;
- vma_iter->second.attribute = new_attribute;
-
- MergeAdjacent(vma_iter);
- return RESULT_SUCCESS;
-}
-
-ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
- const auto vma = FindVMA(src_addr);
-
- ASSERT_MSG(vma != vma_map.end(), "Invalid memory address");
- ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
-
- // The returned VMA might be a bigger one encompassing the desired address.
- const auto vma_offset = src_addr - vma->first;
- ASSERT_MSG(vma_offset + size <= vma->second.size,
- "Shared memory exceeds bounds of mapped block");
-
- const std::shared_ptr<PhysicalMemory>& backing_block = vma->second.backing_block;
- const std::size_t backing_block_offset = vma->second.offset + vma_offset;
-
- CASCADE_RESULT(auto new_vma,
- MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, state));
- // Protect mirror with permissions from old region
- Reprotect(new_vma, vma->second.permissions);
- // Remove permissions from old region
- ReprotectRange(src_addr, size, VMAPermission::None);
-
- return RESULT_SUCCESS;
-}
-
-void VMManager::RefreshMemoryBlockMappings(const PhysicalMemory* block) {
- // If this ever proves to have a noticeable performance impact, allow users of the function to
- // specify a specific range of addresses to limit the scan to.
- for (const auto& p : vma_map) {
- const VirtualMemoryArea& vma = p.second;
- if (block == vma.backing_block.get()) {
- UpdatePageTableForVMA(vma);
- }
- }
-}
-
-void VMManager::LogLayout() const {
- for (const auto& p : vma_map) {
- const VirtualMemoryArea& vma = p.second;
- LOG_DEBUG(Kernel, "{:016X} - {:016X} size: {:016X} {}{}{} {}", vma.base,
- vma.base + vma.size, vma.size,
- (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
- (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
- (u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
- GetMemoryStateName(vma.state));
- }
-}
-
-VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) {
- // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
- // non-const access to its container.
- return vma_map.erase(iter, iter); // Erases an empty range of elements
-}
-
-ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", base);
-
- VMAIter vma_handle = StripIterConstness(FindVMA(base));
- if (vma_handle == vma_map.end()) {
- // Target address is outside the range managed by the kernel
- return ERR_INVALID_ADDRESS;
- }
-
- const VirtualMemoryArea& vma = vma_handle->second;
- if (vma.type != VMAType::Free) {
- // Region is already allocated
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const VAddr start_in_vma = base - vma.base;
- const VAddr end_in_vma = start_in_vma + size;
-
- if (end_in_vma > vma.size) {
- // Requested allocation doesn't fit inside VMA
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if (end_in_vma != vma.size) {
- // Split VMA at the end of the allocated region
- SplitVMA(vma_handle, end_in_vma);
- }
- if (start_in_vma != 0) {
- // Split VMA at the start of the allocated region
- vma_handle = SplitVMA(vma_handle, start_in_vma);
- }
-
- return MakeResult<VMAIter>(vma_handle);
-}
-
-ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", target);
-
- const VAddr target_end = target + size;
- ASSERT(target_end >= target);
- ASSERT(target_end <= address_space_end);
- ASSERT(size > 0);
-
- VMAIter begin_vma = StripIterConstness(FindVMA(target));
- const VMAIter i_end = vma_map.lower_bound(target_end);
- if (std::any_of(begin_vma, i_end,
- [](const auto& entry) { return entry.second.type == VMAType::Free; })) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if (target != begin_vma->second.base) {
- begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
- }
-
- VMAIter end_vma = StripIterConstness(FindVMA(target_end));
- if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
- end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
- }
-
- return MakeResult<VMAIter>(begin_vma);
-}
-
-VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
- VirtualMemoryArea& old_vma = vma_handle->second;
- VirtualMemoryArea new_vma = old_vma; // Make a copy of the VMA
-
- // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
- // a bug. This restriction might be removed later.
- ASSERT(offset_in_vma < old_vma.size);
- ASSERT(offset_in_vma > 0);
-
- old_vma.size = offset_in_vma;
- new_vma.base += offset_in_vma;
- new_vma.size -= offset_in_vma;
-
- switch (new_vma.type) {
- case VMAType::Free:
- break;
- case VMAType::AllocatedMemoryBlock:
- new_vma.offset += offset_in_vma;
- break;
- case VMAType::BackingMemory:
- new_vma.backing_memory += offset_in_vma;
- break;
- case VMAType::MMIO:
- new_vma.paddr += offset_in_vma;
- break;
- }
-
- ASSERT(old_vma.CanBeMergedWith(new_vma));
-
- return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
-}
-
-VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
- const VMAIter next_vma = std::next(iter);
- if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
- MergeAdjacentVMA(iter->second, next_vma->second);
- vma_map.erase(next_vma);
- }
-
- if (iter != vma_map.begin()) {
- VMAIter prev_vma = std::prev(iter);
- if (prev_vma->second.CanBeMergedWith(iter->second)) {
- MergeAdjacentVMA(prev_vma->second, iter->second);
- vma_map.erase(iter);
- iter = prev_vma;
- }
- }
-
- return iter;
-}
-
-void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) {
- ASSERT(left.CanBeMergedWith(right));
-
- // Always merge allocated memory blocks, even when they don't share the same backing block.
- if (left.type == VMAType::AllocatedMemoryBlock &&
- (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
-
- // Check if we can save work.
- if (left.offset == 0 && left.size == left.backing_block->size()) {
- // Fast case: left is an entire backing block.
- left.backing_block->resize(left.size + right.size);
- std::memcpy(left.backing_block->data() + left.size,
- right.backing_block->data() + right.offset, right.size);
- } else {
- // Slow case: make a new memory block for left and right.
- auto new_memory = std::make_shared<PhysicalMemory>();
- new_memory->resize(left.size + right.size);
- std::memcpy(new_memory->data(), left.backing_block->data() + left.offset, left.size);
- std::memcpy(new_memory->data() + left.size, right.backing_block->data() + right.offset,
- right.size);
-
- left.backing_block = std::move(new_memory);
- left.offset = 0;
- }
-
- // Page table update is needed, because backing memory changed.
- left.size += right.size;
- UpdatePageTableForVMA(left);
- } else {
- // Just update the size.
- left.size += right.size;
- }
-}
-
-void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
- auto& memory = system.Memory();
-
- switch (vma.type) {
- case VMAType::Free:
- memory.UnmapRegion(page_table, vma.base, vma.size);
- break;
- case VMAType::AllocatedMemoryBlock:
- memory.MapMemoryRegion(page_table, vma.base, vma.size, *vma.backing_block, vma.offset);
- break;
- case VMAType::BackingMemory:
- memory.MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory);
- break;
- case VMAType::MMIO:
- memory.MapIoRegion(page_table, vma.base, vma.size, vma.mmio_handler);
- break;
- }
-}
-
-void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type) {
- u64 map_region_size = 0;
- u64 heap_region_size = 0;
- u64 stack_region_size = 0;
- u64 tls_io_region_size = 0;
-
- u64 stack_and_tls_io_end = 0;
-
- switch (type) {
- case FileSys::ProgramAddressSpaceType::Is32Bit:
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- address_space_width = 32;
- code_region_base = 0x200000;
- code_region_end = code_region_base + 0x3FE00000;
- aslr_region_base = 0x200000;
- aslr_region_end = aslr_region_base + 0xFFE00000;
- if (type == FileSys::ProgramAddressSpaceType::Is32Bit) {
- map_region_size = 0x40000000;
- heap_region_size = 0x40000000;
- } else {
- map_region_size = 0;
- heap_region_size = 0x80000000;
- }
- stack_and_tls_io_end = 0x40000000;
- break;
- case FileSys::ProgramAddressSpaceType::Is36Bit:
- address_space_width = 36;
- code_region_base = 0x8000000;
- code_region_end = code_region_base + 0x78000000;
- aslr_region_base = 0x8000000;
- aslr_region_end = aslr_region_base + 0xFF8000000;
- map_region_size = 0x180000000;
- heap_region_size = 0x180000000;
- stack_and_tls_io_end = 0x80000000;
- break;
- case FileSys::ProgramAddressSpaceType::Is39Bit:
- address_space_width = 39;
- code_region_base = 0x8000000;
- code_region_end = code_region_base + 0x80000000;
- aslr_region_base = 0x8000000;
- aslr_region_end = aslr_region_base + 0x7FF8000000;
- map_region_size = 0x1000000000;
- heap_region_size = 0x180000000;
- stack_region_size = 0x80000000;
- tls_io_region_size = 0x1000000000;
- break;
- default:
- UNREACHABLE_MSG("Invalid address space type specified: {}", static_cast<u32>(type));
- return;
- }
-
- const u64 stack_and_tls_io_begin = aslr_region_base;
-
- address_space_base = 0;
- address_space_end = 1ULL << address_space_width;
-
- map_region_base = code_region_end;
- map_region_end = map_region_base + map_region_size;
-
- heap_region_base = map_region_end;
- heap_region_end = heap_region_base + heap_region_size;
- heap_end = heap_region_base;
-
- stack_region_base = heap_region_end;
- stack_region_end = stack_region_base + stack_region_size;
-
- tls_io_region_base = stack_region_end;
- tls_io_region_end = tls_io_region_base + tls_io_region_size;
-
- if (stack_region_size == 0) {
- stack_region_base = stack_and_tls_io_begin;
- stack_region_end = stack_and_tls_io_end;
- }
-
- if (tls_io_region_size == 0) {
- tls_io_region_base = stack_and_tls_io_begin;
- tls_io_region_end = stack_and_tls_io_end;
- }
-}
-
-void VMManager::Clear() {
- ClearVMAMap();
- ClearPageTable();
-}
-
-void VMManager::ClearVMAMap() {
- vma_map.clear();
-}
-
-void VMManager::ClearPageTable() {
- std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
- page_table.special_regions.clear();
- std::fill(page_table.attributes.begin(), page_table.attributes.end(),
- Common::PageType::Unmapped);
-}
-
-VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
- MemoryState state, VMAPermission permission_mask,
- VMAPermission permissions,
- MemoryAttribute attribute_mask,
- MemoryAttribute attribute,
- MemoryAttribute ignore_mask) const {
- auto iter = FindVMA(address);
-
- // If we don't have a valid VMA handle at this point, then it means this is
- // being called with an address outside of the address space, which is definitely
- // indicative of a bug, as this function only operates on mapped memory regions.
- DEBUG_ASSERT(IsValidHandle(iter));
-
- const VAddr end_address = address + size - 1;
- const MemoryAttribute initial_attributes = iter->second.attribute;
- const VMAPermission initial_permissions = iter->second.permissions;
- const MemoryState initial_state = iter->second.state;
-
- while (true) {
- // The iterator should be valid throughout the traversal. Hitting the end of
- // the mapped VMA regions is unquestionably indicative of a bug.
- DEBUG_ASSERT(IsValidHandle(iter));
-
- const auto& vma = iter->second;
-
- if (vma.state != initial_state) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if ((vma.state & state_mask) != state) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if (vma.permissions != initial_permissions) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if ((vma.permissions & permission_mask) != permissions) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if ((vma.attribute | ignore_mask) != (initial_attributes | ignore_mask)) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if ((vma.attribute & attribute_mask) != attribute) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if (end_address <= vma.EndAddress()) {
- break;
- }
-
- ++iter;
- }
-
- return MakeResult(
- std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
-}
-
-ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
- std::size_t size) const {
- const VAddr end_addr = address + size;
- const VAddr last_addr = end_addr - 1;
- std::size_t mapped_size = 0;
-
- VAddr cur_addr = address;
- auto iter = FindVMA(cur_addr);
- ASSERT(iter != vma_map.end());
-
- while (true) {
- const auto& vma = iter->second;
- const VAddr vma_start = vma.base;
- const VAddr vma_end = vma_start + vma.size;
- const VAddr vma_last = vma_end - 1;
-
- // Add size if relevant.
- if (vma.state != MemoryState::Unmapped) {
- mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
- }
-
- // Break once we hit the end of the range.
- if (last_addr <= vma_last) {
- break;
- }
-
- // Advance to the next block.
- cur_addr = vma_end;
- iter = std::next(iter);
- ASSERT(iter != vma_map.end());
- }
-
- return MakeResult(mapped_size);
-}
-
-ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
- std::size_t size) const {
- const VAddr end_addr = address + size;
- const VAddr last_addr = end_addr - 1;
- std::size_t mapped_size = 0;
-
- VAddr cur_addr = address;
- auto iter = FindVMA(cur_addr);
- ASSERT(iter != vma_map.end());
-
- while (true) {
- const auto& vma = iter->second;
- const auto vma_start = vma.base;
- const auto vma_end = vma_start + vma.size;
- const auto vma_last = vma_end - 1;
- const auto state = vma.state;
- const auto attr = vma.attribute;
-
- // Memory within region must be free or mapped heap.
- if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) ||
- (state == MemoryState::Unmapped))) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- // Add size if relevant.
- if (state != MemoryState::Unmapped) {
- mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
- }
-
- // Break once we hit the end of the range.
- if (last_addr <= vma_last) {
- break;
- }
-
- // Advance to the next block.
- cur_addr = vma_end;
- iter = std::next(iter);
- ASSERT(iter != vma_map.end());
- }
-
- return MakeResult(mapped_size);
-}
-
-u64 VMManager::GetTotalPhysicalMemoryAvailable() const {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0xF8000000;
-}
-
-VAddr VMManager::GetAddressSpaceBaseAddress() const {
- return address_space_base;
-}
-
-VAddr VMManager::GetAddressSpaceEndAddress() const {
- return address_space_end;
-}
-
-u64 VMManager::GetAddressSpaceSize() const {
- return address_space_end - address_space_base;
-}
-
-u64 VMManager::GetAddressSpaceWidth() const {
- return address_space_width;
-}
-
-bool VMManager::IsWithinAddressSpace(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetAddressSpaceBaseAddress(),
- GetAddressSpaceEndAddress());
-}
-
-VAddr VMManager::GetASLRRegionBaseAddress() const {
- return aslr_region_base;
-}
-
-VAddr VMManager::GetASLRRegionEndAddress() const {
- return aslr_region_end;
-}
-
-u64 VMManager::GetASLRRegionSize() const {
- return aslr_region_end - aslr_region_base;
-}
-
-bool VMManager::IsWithinASLRRegion(VAddr begin, u64 size) const {
- const VAddr range_end = begin + size;
- const VAddr aslr_start = GetASLRRegionBaseAddress();
- const VAddr aslr_end = GetASLRRegionEndAddress();
-
- if (aslr_start > begin || begin > range_end || range_end - 1 > aslr_end - 1) {
- return false;
- }
-
- if (range_end > heap_region_base && heap_region_end > begin) {
- return false;
- }
-
- if (range_end > map_region_base && map_region_end > begin) {
- return false;
- }
-
- return true;
-}
-
-VAddr VMManager::GetCodeRegionBaseAddress() const {
- return code_region_base;
-}
-
-VAddr VMManager::GetCodeRegionEndAddress() const {
- return code_region_end;
-}
-
-u64 VMManager::GetCodeRegionSize() const {
- return code_region_end - code_region_base;
-}
-
-bool VMManager::IsWithinCodeRegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetCodeRegionBaseAddress(),
- GetCodeRegionEndAddress());
-}
-
-VAddr VMManager::GetHeapRegionBaseAddress() const {
- return heap_region_base;
-}
-
-VAddr VMManager::GetHeapRegionEndAddress() const {
- return heap_region_end;
-}
-
-u64 VMManager::GetHeapRegionSize() const {
- return heap_region_end - heap_region_base;
-}
-
-u64 VMManager::GetCurrentHeapSize() const {
- return heap_end - heap_region_base;
-}
-
-bool VMManager::IsWithinHeapRegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetHeapRegionBaseAddress(),
- GetHeapRegionEndAddress());
-}
-
-VAddr VMManager::GetMapRegionBaseAddress() const {
- return map_region_base;
-}
-
-VAddr VMManager::GetMapRegionEndAddress() const {
- return map_region_end;
-}
-
-u64 VMManager::GetMapRegionSize() const {
- return map_region_end - map_region_base;
-}
-
-bool VMManager::IsWithinMapRegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetMapRegionBaseAddress(), GetMapRegionEndAddress());
-}
-
-VAddr VMManager::GetStackRegionBaseAddress() const {
- return stack_region_base;
-}
-
-VAddr VMManager::GetStackRegionEndAddress() const {
- return stack_region_end;
-}
-
-u64 VMManager::GetStackRegionSize() const {
- return stack_region_end - stack_region_base;
-}
-
-bool VMManager::IsWithinStackRegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetStackRegionBaseAddress(),
- GetStackRegionEndAddress());
-}
-
-VAddr VMManager::GetTLSIORegionBaseAddress() const {
- return tls_io_region_base;
-}
-
-VAddr VMManager::GetTLSIORegionEndAddress() const {
- return tls_io_region_end;
-}
-
-u64 VMManager::GetTLSIORegionSize() const {
- return tls_io_region_end - tls_io_region_base;
-}
-
-bool VMManager::IsWithinTLSIORegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetTLSIORegionBaseAddress(),
- GetTLSIORegionEndAddress());
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
deleted file mode 100644
index 90b4b006a..000000000
--- a/src/core/hle/kernel/vm_manager.h
+++ /dev/null
@@ -1,796 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <map>
-#include <memory>
-#include <tuple>
-#include <vector>
-#include "common/common_types.h"
-#include "common/memory_hook.h"
-#include "common/page_table.h"
-#include "core/hle/kernel/physical_memory.h"
-#include "core/hle/result.h"
-#include "core/memory.h"
-
-namespace Core {
-class System;
-}
-
-namespace FileSys {
-enum class ProgramAddressSpaceType : u8;
-}
-
-namespace Kernel {
-
-enum class VMAType : u8 {
- /// VMA represents an unmapped region of the address space.
- Free,
- /// VMA is backed by a ref-counted allocate memory block.
- AllocatedMemoryBlock,
- /// VMA is backed by a raw, unmanaged pointer.
- BackingMemory,
- /// VMA is mapped to MMIO registers at a fixed PAddr.
- MMIO,
- // TODO(yuriks): Implement MemoryAlias to support MAP/UNMAP
-};
-
-/// Permissions for mapped memory blocks
-enum class VMAPermission : u8 {
- None = 0,
- Read = 1,
- Write = 2,
- Execute = 4,
-
- ReadWrite = Read | Write,
- ReadExecute = Read | Execute,
- WriteExecute = Write | Execute,
- ReadWriteExecute = Read | Write | Execute,
-
- // Used as a wildcard when checking permissions across memory ranges
- All = 0xFF,
-};
-
-constexpr VMAPermission operator|(VMAPermission lhs, VMAPermission rhs) {
- return static_cast<VMAPermission>(u32(lhs) | u32(rhs));
-}
-
-constexpr VMAPermission operator&(VMAPermission lhs, VMAPermission rhs) {
- return static_cast<VMAPermission>(u32(lhs) & u32(rhs));
-}
-
-constexpr VMAPermission operator^(VMAPermission lhs, VMAPermission rhs) {
- return static_cast<VMAPermission>(u32(lhs) ^ u32(rhs));
-}
-
-constexpr VMAPermission operator~(VMAPermission permission) {
- return static_cast<VMAPermission>(~u32(permission));
-}
-
-constexpr VMAPermission& operator|=(VMAPermission& lhs, VMAPermission rhs) {
- lhs = lhs | rhs;
- return lhs;
-}
-
-constexpr VMAPermission& operator&=(VMAPermission& lhs, VMAPermission rhs) {
- lhs = lhs & rhs;
- return lhs;
-}
-
-constexpr VMAPermission& operator^=(VMAPermission& lhs, VMAPermission rhs) {
- lhs = lhs ^ rhs;
- return lhs;
-}
-
-/// Attribute flags that can be applied to a VMA
-enum class MemoryAttribute : u32 {
- Mask = 0xFF,
-
- /// No particular qualities
- None = 0,
- /// Memory locked/borrowed for use. e.g. This would be used by transfer memory.
- Locked = 1,
- /// Memory locked for use by IPC-related internals.
- LockedForIPC = 2,
- /// Mapped as part of the device address space.
- DeviceMapped = 4,
- /// Uncached memory
- Uncached = 8,
-
- IpcAndDeviceMapped = LockedForIPC | DeviceMapped,
-};
-
-constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) {
- return static_cast<MemoryAttribute>(u32(lhs) | u32(rhs));
-}
-
-constexpr MemoryAttribute operator&(MemoryAttribute lhs, MemoryAttribute rhs) {
- return static_cast<MemoryAttribute>(u32(lhs) & u32(rhs));
-}
-
-constexpr MemoryAttribute operator^(MemoryAttribute lhs, MemoryAttribute rhs) {
- return static_cast<MemoryAttribute>(u32(lhs) ^ u32(rhs));
-}
-
-constexpr MemoryAttribute operator~(MemoryAttribute attribute) {
- return static_cast<MemoryAttribute>(~u32(attribute));
-}
-
-constexpr MemoryAttribute& operator|=(MemoryAttribute& lhs, MemoryAttribute rhs) {
- lhs = lhs | rhs;
- return lhs;
-}
-
-constexpr MemoryAttribute& operator&=(MemoryAttribute& lhs, MemoryAttribute rhs) {
- lhs = lhs & rhs;
- return lhs;
-}
-
-constexpr MemoryAttribute& operator^=(MemoryAttribute& lhs, MemoryAttribute rhs) {
- lhs = lhs ^ rhs;
- return lhs;
-}
-
-constexpr u32 ToSvcMemoryAttribute(MemoryAttribute attribute) {
- return static_cast<u32>(attribute & MemoryAttribute::Mask);
-}
-
-// clang-format off
-/// Represents memory states and any relevant flags, as used by the kernel.
-/// svcQueryMemory interprets these by masking away all but the first eight
-/// bits when storing memory state into a MemoryInfo instance.
-enum class MemoryState : u32 {
- Mask = 0xFF,
- FlagProtect = 1U << 8,
- FlagDebug = 1U << 9,
- FlagIPC0 = 1U << 10,
- FlagIPC3 = 1U << 11,
- FlagIPC1 = 1U << 12,
- FlagMapped = 1U << 13,
- FlagCode = 1U << 14,
- FlagAlias = 1U << 15,
- FlagModule = 1U << 16,
- FlagTransfer = 1U << 17,
- FlagQueryPhysicalAddressAllowed = 1U << 18,
- FlagSharedDevice = 1U << 19,
- FlagSharedDeviceAligned = 1U << 20,
- FlagIPCBuffer = 1U << 21,
- FlagMemoryPoolAllocated = 1U << 22,
- FlagMapProcess = 1U << 23,
- FlagUncached = 1U << 24,
- FlagCodeMemory = 1U << 25,
-
- // Wildcard used in range checking to indicate all states.
- All = 0xFFFFFFFF,
-
- // Convenience flag sets to reduce repetition
- IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1,
-
- CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed |
- FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
-
- DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer |
- FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned |
- FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached,
-
- Unmapped = 0x00,
- Io = 0x01 | FlagMapped,
- Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
- Code = 0x03 | CodeFlags | FlagMapProcess,
- CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory,
- Heap = 0x05 | DataFlags | FlagCodeMemory,
- Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
- ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
- ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
-
- IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
- IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
-
- Stack = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed |
- FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
-
- ThreadLocal = 0x0C | FlagMapped | FlagMemoryPoolAllocated,
-
- TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed |
- FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated |
- FlagUncached,
-
- TransferMemory = 0x0E | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
- FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
-
- ProcessMemory = 0x0F | FlagIPC3 | FlagIPC1 | FlagMapped | FlagMemoryPoolAllocated,
-
- // Used to signify an inaccessible or invalid memory region with memory queries
- Inaccessible = 0x10,
-
- IpcBuffer1 = 0x11 | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
- FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
-
- IpcBuffer3 = 0x12 | FlagIPC3 | FlagMapped | FlagQueryPhysicalAddressAllowed |
- FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
-
- KernelStack = 0x13 | FlagMapped,
-};
-// clang-format on
-
-constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) {
- return static_cast<MemoryState>(u32(lhs) | u32(rhs));
-}
-
-constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) {
- return static_cast<MemoryState>(u32(lhs) & u32(rhs));
-}
-
-constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) {
- return static_cast<MemoryState>(u32(lhs) ^ u32(rhs));
-}
-
-constexpr MemoryState operator~(MemoryState lhs) {
- return static_cast<MemoryState>(~u32(lhs));
-}
-
-constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) {
- lhs = lhs | rhs;
- return lhs;
-}
-
-constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) {
- lhs = lhs & rhs;
- return lhs;
-}
-
-constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) {
- lhs = lhs ^ rhs;
- return lhs;
-}
-
-constexpr u32 ToSvcMemoryState(MemoryState state) {
- return static_cast<u32>(state & MemoryState::Mask);
-}
-
-struct MemoryInfo {
- u64 base_address;
- u64 size;
- u32 state;
- u32 attributes;
- u32 permission;
- u32 ipc_ref_count;
- u32 device_ref_count;
-};
-static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
-
-struct PageInfo {
- u32 flags;
-};
-
-/**
- * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
- * with homogeneous attributes across its extents. In this particular implementation each VMA is
- * also backed by a single host memory allocation.
- */
-struct VirtualMemoryArea {
- /// Gets the starting (base) address of this VMA.
- VAddr StartAddress() const {
- return base;
- }
-
- /// Gets the ending address of this VMA.
- VAddr EndAddress() const {
- return base + size - 1;
- }
-
- /// Virtual base address of the region.
- VAddr base = 0;
- /// Size of the region.
- u64 size = 0;
-
- VMAType type = VMAType::Free;
- VMAPermission permissions = VMAPermission::None;
- MemoryState state = MemoryState::Unmapped;
- MemoryAttribute attribute = MemoryAttribute::None;
-
- // Settings for type = AllocatedMemoryBlock
- /// Memory block backing this VMA.
- std::shared_ptr<PhysicalMemory> backing_block = nullptr;
- /// Offset into the backing_memory the mapping starts from.
- std::size_t offset = 0;
-
- // Settings for type = BackingMemory
- /// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed.
- u8* backing_memory = nullptr;
-
- // Settings for type = MMIO
- /// Physical address of the register area this VMA maps to.
- PAddr paddr = 0;
- Common::MemoryHookPointer mmio_handler = nullptr;
-
- /// Tests if this area can be merged to the right with `next`.
- bool CanBeMergedWith(const VirtualMemoryArea& next) const;
-};
-
-/**
- * Manages a process' virtual addressing space. This class maintains a list of allocated and free
- * regions in the address space, along with their attributes, and allows kernel clients to
- * manipulate it, adjusting the page table to match.
- *
- * This is similar in idea and purpose to the VM manager present in operating system kernels, with
- * the main difference being that it doesn't have to support swapping or memory mapping of files.
- * The implementation is also simplified by not having to allocate page frames. See these articles
- * about the Linux kernel for an explantion of the concept and implementation:
- * - http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory/
- * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
- */
-class VMManager final {
- using VMAMap = std::map<VAddr, VirtualMemoryArea>;
-
-public:
- using VMAHandle = VMAMap::const_iterator;
-
- explicit VMManager(Core::System& system);
- ~VMManager();
-
- /// Clears the address space map, re-initializing with a single free area.
- void Reset(FileSys::ProgramAddressSpaceType type);
-
- /// Finds the VMA in which the given address is included in, or `vma_map.end()`.
- VMAHandle FindVMA(VAddr target) const;
-
- /// Indicates whether or not the given handle is within the VMA map.
- bool IsValidHandle(VMAHandle handle) const;
-
- // TODO(yuriks): Should these functions actually return the handle?
-
- /**
- * Maps part of a ref-counted block of memory at a given address.
- *
- * @param target The guest address to start the mapping at.
- * @param block The block to be mapped.
- * @param offset Offset into `block` to map from.
- * @param size Size of the mapping.
- * @param state MemoryState tag to attach to the VMA.
- */
- ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<PhysicalMemory> block,
- std::size_t offset, u64 size, MemoryState state,
- VMAPermission perm = VMAPermission::ReadWrite);
-
- /**
- * Maps an unmanaged host memory pointer at a given address.
- *
- * @param target The guest address to start the mapping at.
- * @param memory The memory to be mapped.
- * @param size Size of the mapping.
- * @param state MemoryState tag to attach to the VMA.
- */
- ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
-
- /**
- * Finds the first free memory region of the given size within
- * the user-addressable ASLR memory region.
- *
- * @param size The size of the desired region in bytes.
- *
- * @returns If successful, the base address of the free region with
- * the given size.
- */
- ResultVal<VAddr> FindFreeRegion(u64 size) const;
-
- /**
- * Finds the first free address range that can hold a region of the desired size
- *
- * @param begin The starting address of the range.
- * This is treated as an inclusive beginning address.
- *
- * @param end The ending address of the range.
- * This is treated as an exclusive ending address.
- *
- * @param size The size of the free region to attempt to locate,
- * in bytes.
- *
- * @returns If successful, the base address of the free region with
- * the given size.
- *
- * @returns If unsuccessful, a result containing an error code.
- *
- * @pre The starting address must be less than the ending address.
- * @pre The size must not exceed the address range itself.
- */
- ResultVal<VAddr> FindFreeRegion(VAddr begin, VAddr end, u64 size) const;
-
- /**
- * Maps a memory-mapped IO region at a given address.
- *
- * @param target The guest address to start the mapping at.
- * @param paddr The physical address where the registers are present.
- * @param size Size of the mapping.
- * @param state MemoryState tag to attach to the VMA.
- * @param mmio_handler The handler that will implement read and write for this MMIO region.
- */
- ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
- Common::MemoryHookPointer mmio_handler);
-
- /// Unmaps a range of addresses, splitting VMAs as necessary.
- ResultCode UnmapRange(VAddr target, u64 size);
-
- /// Changes the permissions of the given VMA.
- VMAHandle Reprotect(VMAHandle vma, VMAPermission new_perms);
-
- /// Changes the permissions of a range of addresses, splitting VMAs as necessary.
- ResultCode ReprotectRange(VAddr target, u64 size, VMAPermission new_perms);
-
- ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
-
- /// Attempts to allocate a heap with the given size.
- ///
- /// @param size The size of the heap to allocate in bytes.
- ///
- /// @note If a heap is currently allocated, and this is called
- /// with a size that is equal to the size of the current heap,
- /// then this function will do nothing and return the current
- /// heap's starting address, as there's no need to perform
- /// any additional heap allocation work.
- ///
- /// @note If a heap is currently allocated, and this is called
- /// with a size less than the current heap's size, then
- /// this function will attempt to shrink the heap.
- ///
- /// @note If a heap is currently allocated, and this is called
- /// with a size larger than the current heap's size, then
- /// this function will attempt to extend the size of the heap.
- ///
- /// @returns A result indicating either success or failure.
- /// <p>
- /// If successful, this function will return a result
- /// containing the starting address to the allocated heap.
- /// <p>
- /// If unsuccessful, this function will return a result
- /// containing an error code.
- ///
- /// @pre The given size must lie within the allowable heap
- /// memory region managed by this VMManager instance.
- /// Failure to abide by this will result in ERR_OUT_OF_MEMORY
- /// being returned as the result.
- ///
- ResultVal<VAddr> SetHeapSize(u64 size);
-
- /// Maps memory at a given address.
- ///
- /// @param target The virtual address to map memory at.
- /// @param size The amount of memory to map.
- ///
- /// @note The destination address must lie within the Map region.
- ///
- /// @note This function requires that SystemResourceSize be non-zero,
- /// however, this is just because if it were not then the
- /// resulting page tables could be exploited on hardware by
- /// a malicious program. SystemResource usage does not need
- /// to be explicitly checked or updated here.
- ResultCode MapPhysicalMemory(VAddr target, u64 size);
-
- /// Unmaps memory at a given address.
- ///
- /// @param target The virtual address to unmap memory at.
- /// @param size The amount of memory to unmap.
- ///
- /// @note The destination address must lie within the Map region.
- ///
- /// @note This function requires that SystemResourceSize be non-zero,
- /// however, this is just because if it were not then the
- /// resulting page tables could be exploited on hardware by
- /// a malicious program. SystemResource usage does not need
- /// to be explicitly checked or updated here.
- ResultCode UnmapPhysicalMemory(VAddr target, u64 size);
-
- /// Maps a region of memory as code memory.
- ///
- /// @param dst_address The base address of the region to create the aliasing memory region.
- /// @param src_address The base address of the region to be aliased.
- /// @param size The total amount of memory to map in bytes.
- ///
- /// @pre Both memory regions lie within the actual addressable address space.
- ///
- /// @post After this function finishes execution, assuming success, then the address range
- /// [dst_address, dst_address+size) will alias the memory region,
- /// [src_address, src_address+size).
- /// <p>
- /// What this also entails is as follows:
- /// 1. The aliased region gains the Locked memory attribute.
- /// 2. The aliased region becomes read-only.
- /// 3. The aliasing region becomes read-only.
- /// 4. The aliasing region is created with a memory state of MemoryState::CodeModule.
- ///
- ResultCode MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
-
- /// Unmaps a region of memory designated as code module memory.
- ///
- /// @param dst_address The base address of the memory region aliasing the source memory region.
- /// @param src_address The base address of the memory region being aliased.
- /// @param size The size of the memory region to unmap in bytes.
- ///
- /// @pre Both memory ranges lie within the actual addressable address space.
- ///
- /// @pre The memory region being unmapped has been previously been mapped
- /// by a call to MapCodeMemory.
- ///
- /// @post After execution of the function, if successful. the aliasing memory region
- /// will be unmapped and the aliased region will have various traits about it
- /// restored to what they were prior to the original mapping call preceding
- /// this function call.
- /// <p>
- /// What this also entails is as follows:
- /// 1. The state of the memory region will now indicate a general heap region.
- /// 2. All memory attributes for the memory region are cleared.
- /// 3. Memory permissions for the region are restored to user read/write.
- ///
- ResultCode UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
-
- /// Queries the memory manager for information about the given address.
- ///
- /// @param address The address to query the memory manager about for information.
- ///
- /// @return A MemoryInfo instance containing information about the given address.
- ///
- MemoryInfo QueryMemory(VAddr address) const;
-
- /// Sets an attribute across the given address range.
- ///
- /// @param address The starting address
- /// @param size The size of the range to set the attribute on.
- /// @param mask The attribute mask
- /// @param attribute The attribute to set across the given address range
- ///
- /// @returns RESULT_SUCCESS if successful
- /// @returns ERR_INVALID_ADDRESS_STATE if the attribute could not be set.
- ///
- ResultCode SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
- MemoryAttribute attribute);
-
- /**
- * Scans all VMAs and updates the page table range of any that use the given vector as backing
- * memory. This should be called after any operation that causes reallocation of the vector.
- */
- void RefreshMemoryBlockMappings(const PhysicalMemory* block);
-
- /// Dumps the address space layout to the log, for debugging
- void LogLayout() const;
-
- /// Gets the total memory usage, used by svcGetInfo
- u64 GetTotalPhysicalMemoryAvailable() const;
-
- /// Gets the address space base address
- VAddr GetAddressSpaceBaseAddress() const;
-
- /// Gets the address space end address
- VAddr GetAddressSpaceEndAddress() const;
-
- /// Gets the total address space address size in bytes
- u64 GetAddressSpaceSize() const;
-
- /// Gets the address space width in bits.
- u64 GetAddressSpaceWidth() const;
-
- /// Determines whether or not the given address range lies within the address space.
- bool IsWithinAddressSpace(VAddr address, u64 size) const;
-
- /// Gets the base address of the ASLR region.
- VAddr GetASLRRegionBaseAddress() const;
-
- /// Gets the end address of the ASLR region.
- VAddr GetASLRRegionEndAddress() const;
-
- /// Gets the size of the ASLR region
- u64 GetASLRRegionSize() const;
-
- /// Determines whether or not the specified address range is within the ASLR region.
- bool IsWithinASLRRegion(VAddr address, u64 size) const;
-
- /// Gets the base address of the code region.
- VAddr GetCodeRegionBaseAddress() const;
-
- /// Gets the end address of the code region.
- VAddr GetCodeRegionEndAddress() const;
-
- /// Gets the total size of the code region in bytes.
- u64 GetCodeRegionSize() const;
-
- /// Determines whether or not the specified range is within the code region.
- bool IsWithinCodeRegion(VAddr address, u64 size) const;
-
- /// Gets the base address of the heap region.
- VAddr GetHeapRegionBaseAddress() const;
-
- /// Gets the end address of the heap region;
- VAddr GetHeapRegionEndAddress() const;
-
- /// Gets the total size of the heap region in bytes.
- u64 GetHeapRegionSize() const;
-
- /// Gets the total size of the current heap in bytes.
- ///
- /// @note This is the current allocated heap size, not the size
- /// of the region it's allowed to exist within.
- ///
- u64 GetCurrentHeapSize() const;
-
- /// Determines whether or not the specified range is within the heap region.
- bool IsWithinHeapRegion(VAddr address, u64 size) const;
-
- /// Gets the base address of the map region.
- VAddr GetMapRegionBaseAddress() const;
-
- /// Gets the end address of the map region.
- VAddr GetMapRegionEndAddress() const;
-
- /// Gets the total size of the map region in bytes.
- u64 GetMapRegionSize() const;
-
- /// Determines whether or not the specified range is within the map region.
- bool IsWithinMapRegion(VAddr address, u64 size) const;
-
- /// Gets the base address of the stack region.
- VAddr GetStackRegionBaseAddress() const;
-
- /// Gets the end address of the stack region.
- VAddr GetStackRegionEndAddress() const;
-
- /// Gets the total size of the stack region in bytes.
- u64 GetStackRegionSize() const;
-
- /// Determines whether or not the given address range is within the stack region
- bool IsWithinStackRegion(VAddr address, u64 size) const;
-
- /// Gets the base address of the TLS IO region.
- VAddr GetTLSIORegionBaseAddress() const;
-
- /// Gets the end address of the TLS IO region.
- VAddr GetTLSIORegionEndAddress() const;
-
- /// Gets the total size of the TLS IO region in bytes.
- u64 GetTLSIORegionSize() const;
-
- /// Determines if the given address range is within the TLS IO region.
- bool IsWithinTLSIORegion(VAddr address, u64 size) const;
-
- /// Each VMManager has its own page table, which is set as the main one when the owning process
- /// is scheduled.
- Common::PageTable page_table{Memory::PAGE_BITS};
-
- using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>;
-
- /// Checks if an address range adheres to the specified states provided.
- ///
- /// @param address The starting address of the address range.
- /// @param size The size of the address range.
- /// @param state_mask The memory state mask.
- /// @param state The state to compare the individual VMA states against,
- /// which is done in the form of: (vma.state & state_mask) != state.
- /// @param permission_mask The memory permissions mask.
- /// @param permissions The permission to compare the individual VMA permissions against,
- /// which is done in the form of:
- /// (vma.permission & permission_mask) != permission.
- /// @param attribute_mask The memory attribute mask.
- /// @param attribute The memory attributes to compare the individual VMA attributes
- /// against, which is done in the form of:
- /// (vma.attributes & attribute_mask) != attribute.
- /// @param ignore_mask The memory attributes to ignore during the check.
- ///
- /// @returns If successful, returns a tuple containing the memory attributes
- /// (with ignored bits specified by ignore_mask unset), memory permissions, and
- /// memory state across the memory range.
- /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE.
- ///
- CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state,
- VMAPermission permission_mask, VMAPermission permissions,
- MemoryAttribute attribute_mask, MemoryAttribute attribute,
- MemoryAttribute ignore_mask) const;
-
-private:
- using VMAIter = VMAMap::iterator;
-
- /// Converts a VMAHandle to a mutable VMAIter.
- VMAIter StripIterConstness(const VMAHandle& iter);
-
- /// Unmaps the given VMA.
- VMAIter Unmap(VMAIter vma);
-
- /**
- * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
- * the appropriate error checking.
- */
- ResultVal<VMAIter> CarveVMA(VAddr base, u64 size);
-
- /**
- * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
- * end of the range.
- */
- ResultVal<VMAIter> CarveVMARange(VAddr base, u64 size);
-
- /**
- * Splits a VMA in two, at the specified offset.
- * @returns the right side of the split, with the original iterator becoming the left side.
- */
- VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma);
-
- /**
- * Checks for and merges the specified VMA with adjacent ones if possible.
- * @returns the merged VMA or the original if no merging was possible.
- */
- VMAIter MergeAdjacent(VMAIter vma);
-
- /**
- * Merges two adjacent VMAs.
- */
- void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right);
-
- /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
- void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
-
- /// Initializes memory region ranges to adhere to a given address space type.
- void InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type);
-
- /// Clears the underlying map and page table.
- void Clear();
-
- /// Clears out the VMA map, unmapping any previously mapped ranges.
- void ClearVMAMap();
-
- /// Clears out the page table
- void ClearPageTable();
-
- /// Gets the amount of memory currently mapped (state != Unmapped) in a range.
- ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const;
-
- /// Gets the amount of memory unmappable by UnmapPhysicalMemory in a range.
- ResultVal<std::size_t> SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
- std::size_t size) const;
-
- /**
- * A map covering the entirety of the managed address space, keyed by the `base` field of each
- * VMA. It must always be modified by splitting or merging VMAs, so that the invariant
- * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
- * merged when possible so that no two similar and adjacent regions exist that have not been
- * merged.
- */
- VMAMap vma_map;
-
- u32 address_space_width = 0;
- VAddr address_space_base = 0;
- VAddr address_space_end = 0;
-
- VAddr aslr_region_base = 0;
- VAddr aslr_region_end = 0;
-
- VAddr code_region_base = 0;
- VAddr code_region_end = 0;
-
- VAddr heap_region_base = 0;
- VAddr heap_region_end = 0;
-
- VAddr map_region_base = 0;
- VAddr map_region_end = 0;
-
- VAddr stack_region_base = 0;
- VAddr stack_region_end = 0;
-
- VAddr tls_io_region_base = 0;
- VAddr tls_io_region_end = 0;
-
- // Memory used to back the allocations in the regular heap. A single vector is used to cover
- // the entire virtual address space extents that bound the allocations, including any holes.
- // This makes deallocation and reallocation of holes fast and keeps process memory contiguous
- // in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
- std::shared_ptr<PhysicalMemory> heap_memory;
-
- // The end of the currently allocated heap. This is not an inclusive
- // end of the range. This is essentially 'base_address + current_size'.
- VAddr heap_end = 0;
-
- // The current amount of memory mapped via MapPhysicalMemory.
- // This is used here (and in Nintendo's kernel) only for debugging, and does not impact
- // any behavior.
- u64 physical_memory_mapped = 0;
-
- Core::System& system;
-};
-} // namespace Kernel
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 450f61fea..b6bdbd988 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -342,8 +342,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) {
*/
#define CASCADE_RESULT(target, source) \
auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).Failed()) \
+ if (CONCAT2(check_result_L, __LINE__).Failed()) { \
return CONCAT2(check_result_L, __LINE__).Code(); \
+ } \
target = std::move(*CONCAT2(check_result_L, __LINE__))
/**
@@ -351,6 +352,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) {
* non-success, or discarded otherwise.
*/
#define CASCADE_CODE(source) \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).IsError()) \
- return CONCAT2(check_result_L, __LINE__);
+ do { \
+ auto CONCAT2(check_result_L, __LINE__) = source; \
+ if (CONCAT2(check_result_L, __LINE__).IsError()) { \
+ return CONCAT2(check_result_L, __LINE__); \
+ } \
+ } while (false)
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index cfac8ca9a..c2c11dbcb 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -11,6 +11,7 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/constants.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -35,7 +36,7 @@ constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
static std::string GetImagePath(Common::UUID uuid) {
- return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
}
@@ -44,6 +45,218 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
return static_cast<u32>(std::min(size, max_jpeg_image_size));
}
+class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> {
+public:
+ explicit IManagerForSystemService(Common::UUID user_id)
+ : ServiceFramework("IManagerForSystemService") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CheckAvailability"},
+ {1, nullptr, "GetAccountId"},
+ {2, nullptr, "EnsureIdTokenCacheAsync"},
+ {3, nullptr, "LoadIdTokenCache"},
+ {100, nullptr, "SetSystemProgramIdentification"},
+ {101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
+ {110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
+ {111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
+ {112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
+ {113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
+ {120, nullptr, "GetNintendoAccountId"},
+ {121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
+ {130, nullptr, "GetNintendoAccountUserResourceCache"},
+ {131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
+ {132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
+ {133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
+ {134, nullptr, "RefreshNintendoAccountVerificationUrlCache"}, // 9.0.0+
+ {135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
+ {140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
+ {141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
+ {142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
+ {150, nullptr, "CreateAuthorizationRequest"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+// 3.0.0+
+class IFloatingRegistrationRequest final : public ServiceFramework<IFloatingRegistrationRequest> {
+public:
+ explicit IFloatingRegistrationRequest(Common::UUID user_id)
+ : ServiceFramework("IFloatingRegistrationRequest") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSessionId"},
+ {12, nullptr, "GetAccountId"},
+ {13, nullptr, "GetLinkedNintendoAccountId"},
+ {14, nullptr, "GetNickname"},
+ {15, nullptr, "GetProfileImage"},
+ {21, nullptr, "LoadIdTokenCache"},
+ {100, nullptr, "RegisterUser"}, // [1.0.0-3.0.2] RegisterAsync
+ {101, nullptr, "RegisterUserWithUid"}, // [1.0.0-3.0.2] RegisterWithUidAsync
+ {102, nullptr, "RegisterNetworkServiceAccountAsync"}, // 4.0.0+
+ {103, nullptr, "RegisterNetworkServiceAccountWithUidAsync"}, // 4.0.0+
+ {110, nullptr, "SetSystemProgramIdentification"},
+ {111, nullptr, "EnsureIdTokenCacheAsync"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IAdministrator final : public ServiceFramework<IAdministrator> {
+public:
+ explicit IAdministrator(Common::UUID user_id) : ServiceFramework("IAdministrator") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CheckAvailability"},
+ {1, nullptr, "GetAccountId"},
+ {2, nullptr, "EnsureIdTokenCacheAsync"},
+ {3, nullptr, "LoadIdTokenCache"},
+ {100, nullptr, "SetSystemProgramIdentification"},
+ {101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
+ {110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
+ {111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
+ {112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
+ {113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
+ {120, nullptr, "GetNintendoAccountId"},
+ {121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
+ {130, nullptr, "GetNintendoAccountUserResourceCache"},
+ {131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
+ {132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
+ {133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
+ {134, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsync"}, // 9.0.0+
+ {135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
+ {140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
+ {141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
+ {142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
+ {150, nullptr, "CreateAuthorizationRequest"},
+ {200, nullptr, "IsRegistered"},
+ {201, nullptr, "RegisterAsync"},
+ {202, nullptr, "UnregisterAsync"},
+ {203, nullptr, "DeleteRegistrationInfoLocally"},
+ {220, nullptr, "SynchronizeProfileAsync"},
+ {221, nullptr, "UploadProfileAsync"},
+ {222, nullptr, "SynchronizaProfileAsyncIfSecondsElapsed"},
+ {250, nullptr, "IsLinkedWithNintendoAccount"},
+ {251, nullptr, "CreateProcedureToLinkWithNintendoAccount"},
+ {252, nullptr, "ResumeProcedureToLinkWithNintendoAccount"},
+ {255, nullptr, "CreateProcedureToUpdateLinkageStateOfNintendoAccount"},
+ {256, nullptr, "ResumeProcedureToUpdateLinkageStateOfNintendoAccount"},
+ {260, nullptr, "CreateProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
+ {261, nullptr, "ResumeProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
+ {280, nullptr, "ProxyProcedureToAcquireApplicationAuthorizationForNintendoAccount"},
+ {290, nullptr, "GetRequestForNintendoAccountUserResourceView"}, // 8.0.0+
+ {300, nullptr, "TryRecoverNintendoAccountUserStateAsync"}, // 6.0.0+
+ {400, nullptr, "IsServiceEntryRequirementCacheRefreshRequiredForOnlinePlay"}, // 6.1.0+
+ {401, nullptr, "RefreshServiceEntryRequirementCacheForOnlinePlayAsync"}, // 6.1.0+
+ {900, nullptr, "GetAuthenticationInfoForWin"}, // 9.0.0+
+ {901, nullptr, "ImportAsyncForWin"}, // 9.0.0+
+ {997, nullptr, "DebugUnlinkNintendoAccountAsync"},
+ {998, nullptr, "DebugSetAvailabilityErrorDetail"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IAuthorizationRequest final : public ServiceFramework<IAuthorizationRequest> {
+public:
+ explicit IAuthorizationRequest(Common::UUID user_id)
+ : ServiceFramework("IAuthorizationRequest") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSessionId"},
+ {10, nullptr, "InvokeWithoutInteractionAsync"},
+ {19, nullptr, "IsAuthorized"},
+ {20, nullptr, "GetAuthorizationCode"},
+ {21, nullptr, "GetIdToken"},
+ {22, nullptr, "GetState"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IOAuthProcedure final : public ServiceFramework<IOAuthProcedure> {
+public:
+ explicit IOAuthProcedure(Common::UUID user_id) : ServiceFramework("IOAuthProcedure") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PrepareAsync"},
+ {1, nullptr, "GetRequest"},
+ {2, nullptr, "ApplyResponse"},
+ {3, nullptr, "ApplyResponseAsync"},
+ {10, nullptr, "Suspend"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+// 3.0.0+
+class IOAuthProcedureForExternalNsa final : public ServiceFramework<IOAuthProcedureForExternalNsa> {
+public:
+ explicit IOAuthProcedureForExternalNsa(Common::UUID user_id)
+ : ServiceFramework("IOAuthProcedureForExternalNsa") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PrepareAsync"},
+ {1, nullptr, "GetRequest"},
+ {2, nullptr, "ApplyResponse"},
+ {3, nullptr, "ApplyResponseAsync"},
+ {10, nullptr, "Suspend"},
+ {100, nullptr, "GetAccountId"},
+ {101, nullptr, "GetLinkedNintendoAccountId"},
+ {102, nullptr, "GetNickname"},
+ {103, nullptr, "GetProfileImage"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IOAuthProcedureForNintendoAccountLinkage final
+ : public ServiceFramework<IOAuthProcedureForNintendoAccountLinkage> {
+public:
+ explicit IOAuthProcedureForNintendoAccountLinkage(Common::UUID user_id)
+ : ServiceFramework("IOAuthProcedureForNintendoAccountLinkage") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PrepareAsync"},
+ {1, nullptr, "GetRequest"},
+ {2, nullptr, "ApplyResponse"},
+ {3, nullptr, "ApplyResponseAsync"},
+ {10, nullptr, "Suspend"},
+ {100, nullptr, "GetRequestWithTheme"},
+ {101, nullptr, "IsNetworkServiceAccountReplaced"},
+ {199, nullptr, "GetUrlForIntroductionOfExtraMembership"}, // 2.0.0 - 5.1.0
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class INotifier final : public ServiceFramework<INotifier> {
+public:
+ explicit INotifier(Common::UUID user_id) : ServiceFramework("INotifier") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSystemEvent"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
class IProfileCommon : public ServiceFramework<IProfileCommon> {
public:
explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id,
@@ -74,9 +287,7 @@ protected:
ProfileBase profile_base{};
ProfileData data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
- std::array<u8, sizeof(ProfileData)> raw_data;
- std::memcpy(raw_data.data(), &data, sizeof(ProfileData));
- ctx.WriteBuffer(raw_data);
+ ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
@@ -108,7 +319,7 @@ protected:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+ const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
@@ -121,7 +332,7 @@ protected:
std::vector<u8> buffer(size);
image.ReadBytes(buffer.data(), buffer.size());
- ctx.WriteBuffer(buffer.data(), buffer.size());
+ ctx.WriteBuffer(buffer);
rb.Push<u32>(size);
}
@@ -130,7 +341,7 @@ protected:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+ const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
@@ -195,7 +406,7 @@ protected:
ProfileData data;
std::memcpy(&data, user_data.data(), sizeof(ProfileData));
- FileUtil::IOFile image(GetImagePath(user_id), "wb");
+ Common::FS::IOFile image(GetImagePath(user_id), "wb");
if (!image.IsOpen() || !image.Resize(image_data.size()) ||
image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
@@ -226,9 +437,58 @@ public:
: IProfileCommon("IProfileEditor", true, user_id, profile_manager) {}
};
+class IAsyncContext final : public ServiceFramework<IAsyncContext> {
+public:
+ explicit IAsyncContext(Common::UUID user_id) : ServiceFramework("IAsyncContext") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSystemEvent"},
+ {1, nullptr, "Cancel"},
+ {2, nullptr, "HasDone"},
+ {3, nullptr, "GetResult"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class ISessionObject final : public ServiceFramework<ISessionObject> {
+public:
+ explicit ISessionObject(Common::UUID user_id) : ServiceFramework("ISessionObject") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {999, nullptr, "Dummy"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IGuestLoginRequest final : public ServiceFramework<IGuestLoginRequest> {
+public:
+ explicit IGuestLoginRequest(Common::UUID) : ServiceFramework("IGuestLoginRequest") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSessionId"},
+ {11, nullptr, "Unknown"}, // 1.0.0 - 2.3.0 (the name is blank on Switchbrew)
+ {12, nullptr, "GetAccountId"},
+ {13, nullptr, "GetLinkedNintendoAccountId"},
+ {14, nullptr, "GetNickname"},
+ {15, nullptr, "GetProfileImage"},
+ {21, nullptr, "LoadIdTokenCache"}, // 3.0.0+
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
- IManagerForApplication() : ServiceFramework("IManagerForApplication") {
+ explicit IManagerForApplication(Common::UUID user_id)
+ : ServiceFramework("IManagerForApplication"), user_id(user_id) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
@@ -237,7 +497,7 @@ public:
{3, nullptr, "LoadIdTokenCache"},
{130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"},
{150, nullptr, "CreateAuthorizationRequest"},
- {160, nullptr, "StoreOpenContext"},
+ {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
};
// clang-format on
@@ -254,11 +514,100 @@ private:
}
void GetAccountId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- // Should return a nintendo account ID
+ LOG_DEBUG(Service_ACC, "called");
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(1);
+ rb.PushRaw<u64>(user_id.GetNintendoID());
+ }
+
+ void StoreOpenContext(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Common::UUID user_id;
+};
+
+// 6.0.0+
+class IAsyncNetworkServiceLicenseKindContext final
+ : public ServiceFramework<IAsyncNetworkServiceLicenseKindContext> {
+public:
+ explicit IAsyncNetworkServiceLicenseKindContext(Common::UUID user_id)
+ : ServiceFramework("IAsyncNetworkServiceLicenseKindContext") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetSystemEvent"},
+ {1, nullptr, "Cancel"},
+ {2, nullptr, "HasDone"},
+ {3, nullptr, "GetResult"},
+ {4, nullptr, "GetNetworkServiceLicenseKind"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+// 8.0.0+
+class IOAuthProcedureForUserRegistration final
+ : public ServiceFramework<IOAuthProcedureForUserRegistration> {
+public:
+ explicit IOAuthProcedureForUserRegistration(Common::UUID user_id)
+ : ServiceFramework("IOAuthProcedureForUserRegistration") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PrepareAsync"},
+ {1, nullptr, "GetRequest"},
+ {2, nullptr, "ApplyResponse"},
+ {3, nullptr, "ApplyResponseAsync"},
+ {10, nullptr, "Suspend"},
+ {100, nullptr, "GetAccountId"},
+ {101, nullptr, "GetLinkedNintendoAccountId"},
+ {102, nullptr, "GetNickname"},
+ {103, nullptr, "GetProfileImage"},
+ {110, nullptr, "RegisterUserAsync"},
+ {111, nullptr, "GetUid"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class DAUTH_O final : public ServiceFramework<DAUTH_O> {
+public:
+ explicit DAUTH_O(Common::UUID) : ServiceFramework("dauth:o") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData
+ {1, nullptr, "LoadAuthenticationTokenCache"}, // 6.0.0+
+ {2, nullptr, "InvalidateAuthenticationTokenCache"}, // 6.0.0+
+ {10, nullptr, "EnsureEdgeTokenCacheAsync"}, // 6.0.0+
+ {11, nullptr, "LoadEdgeTokenCache"}, // 6.0.0+
+ {12, nullptr, "InvalidateEdgeTokenCache"}, // 6.0.0+
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+// 6.0.0+
+class IAsyncResult final : public ServiceFramework<IAsyncResult> {
+public:
+ explicit IAsyncResult(Common::UUID user_id) : ServiceFramework("IAsyncResult") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetResult"},
+ {1, nullptr, "Cancel"},
+ {2, nullptr, "IsAvailable"},
+ {3, nullptr, "GetSystemEvent"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
}
};
@@ -319,46 +668,37 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto pid = rp.Pop<u64>();
- LOG_DEBUG(Service_ACC, "called, process_id={}", pid);
+ LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InitializeApplicationInfoBase(pid));
+ rb.Push(InitializeApplicationInfoBase());
}
void Module::Interface::InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto pid = rp.Pop<u64>();
- LOG_WARNING(Service_ACC, "(Partial implementation) called, process_id={}", pid);
+ LOG_WARNING(Service_ACC, "(Partial implementation) called");
// TODO(ogniK): We require checking if the user actually owns the title and what not. As of
// currently, we assume the user owns the title. InitializeApplicationInfoBase SHOULD be called
// first then we do extra checks if the game is a digital copy.
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InitializeApplicationInfoBase(pid));
+ rb.Push(InitializeApplicationInfoBase());
}
-ResultCode Module::Interface::InitializeApplicationInfoBase(u64 process_id) {
+ResultCode Module::Interface::InitializeApplicationInfoBase() {
if (application_info) {
LOG_ERROR(Service_ACC, "Application already initialized");
return ERR_ACCOUNTINFO_ALREADY_INITIALIZED;
}
- const auto& list = system.Kernel().GetProcessList();
- const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
- return process->GetProcessID() == process_id;
- });
-
- if (iter == list.end()) {
- LOG_ERROR(Service_ACC, "Failed to find process ID");
- application_info.application_type = ApplicationType::Unknown;
-
- return ERR_ACCOUNTINFO_BAD_APPLICATION;
- }
-
- const auto launch_property = system.GetARPManager().GetLaunchProperty((*iter)->GetTitleID());
+ // TODO(ogniK): This should be changed to reflect the target process for when we have multiple
+ // processes emulated. As we don't actually have pid support we should assume we're just using
+ // our own process
+ const auto& current_process = system.Kernel().CurrentProcess();
+ const auto launch_property =
+ system.GetARPManager().GetLaunchProperty(current_process->GetTitleID());
if (launch_property.Failed()) {
LOG_ERROR(Service_ACC, "Failed to get launch property");
@@ -372,10 +712,12 @@ ResultCode Module::Interface::InitializeApplicationInfoBase(u64 process_id) {
case FileSys::StorageId::Host:
case FileSys::StorageId::NandUser:
case FileSys::StorageId::SdCard:
+ case FileSys::StorageId::None: // Yuzu specific, differs from hardware
application_info.application_type = ApplicationType::Digital;
break;
default:
- LOG_ERROR(Service_ACC, "Invalid game storage ID");
+ LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}",
+ launch_property->base_game_storage_id);
return ERR_ACCOUNTINFO_BAD_APPLICATION;
}
@@ -389,7 +731,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerForApplication>();
+ rb.PushIpcInterface<IManagerForApplication>(profile_manager->GetLastOpenedUser());
}
void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
@@ -400,8 +742,10 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx
bool is_locked = false;
if (res != Loader::ResultStatus::Success) {
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
- auto nacp_unique = pm.GetControlMetadata().first;
+ const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto nacp_unique = pm.GetControlMetadata().first;
if (nacp_unique != nullptr) {
is_locked = nacp_unique->GetUserAccountSwitchLock();
@@ -428,6 +772,37 @@ void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager);
}
+void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+
+ // All users should be qualified. We don't actually have parental control or anything to do with
+ // nintendo online currently. We're just going to assume the user running the game has access to
+ // the game regardless of parental control settings.
+ ctx.WriteBuffer(profile_manager->GetAllUsers());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+
+ // This is similar to GetBaasAccountManagerForApplication
+ // This command is used concurrently with ListOpenContextStoredUsers
+ // TODO: Find the differences between this and GetBaasAccountManagerForApplication
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerForApplication>(profile_manager->GetLastOpenedUser());
+}
+
+void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+
+ // TODO(ogniK): Handle open contexts
+ ctx.WriteBuffer(profile_manager->GetOpenUsers());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 7a7dc9ec6..c611efd89 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -33,9 +33,12 @@ public:
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
+ void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
+ void LoadOpenContext(Kernel::HLERequestContext& ctx);
+ void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
private:
- ResultCode InitializeApplicationInfoBase(u64 process_id);
+ ResultCode InitializeApplicationInfoBase();
enum class ApplicationType : u32_le {
GameCard = 0,
diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp
index 3bac6bcd1..51f119b12 100644
--- a/src/core/hle/service/acc/acc_aa.cpp
+++ b/src/core/hle/service/acc/acc_aa.cpp
@@ -13,8 +13,8 @@ ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"},
{2, nullptr, "GetDeviceAccountId"},
- {50, nullptr, "RegisterNotificationTokenAsync"},
- {51, nullptr, "UnregisterNotificationTokenAsync"},
+ {50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
+ {51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index b941c260b..d2bb8c2c8 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -17,26 +17,28 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_SU::GetProfile, "GetProfile"},
- {6, nullptr, "GetProfileDigest"},
+ {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"},
- {99, nullptr, "DebugActivateOpenContextRetention"},
+ {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "GetProfileUpdateNotifier"},
- {105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
- {106, nullptr, "GetProfileSyncNotifier"},
+ {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
+ {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
- {113, nullptr, "GetSaveDataThumbnailExistence"},
- {130, nullptr, "ActivateOpenContextRetention"},
- {140, nullptr, "ListQualifiedUsers"},
- {190, nullptr, "GetUserLastOpenedApplication"},
- {191, nullptr, "ActivateOpenContextHolder"},
+ {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
+ {120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
+ {130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
+ {140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
+ {150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
+ {190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
+ {191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
{200, nullptr, "BeginUserRegistration"},
{201, nullptr, "CompleteUserRegistration"},
{202, nullptr, "CancelUserRegistration"},
@@ -44,15 +46,15 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{204, nullptr, "SetUserPosition"},
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
{206, nullptr, "CompleteUserRegistrationForcibly"},
- {210, nullptr, "CreateFloatingRegistrationRequest"},
- {211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"},
- {212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"},
+ {210, nullptr, "CreateFloatingRegistrationRequest"}, // 3.0.0+
+ {211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
+ {212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
{230, nullptr, "AuthenticateServiceAsync"},
{250, nullptr, "GetBaasAccountAdministrator"},
{290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
- {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
+ {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, // 3.0.0+
{299, nullptr, "SuspendBackgroundDaemon"},
- {997, nullptr, "DebugInvalidateTokenCacheForUser"},
+ {997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 0ac19f4ff..75a24f8f5 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -17,23 +17,23 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U0::GetProfile, "GetProfile"},
- {6, nullptr, "GetProfileDigest"},
+ {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"},
- {99, nullptr, "DebugActivateOpenContextRetention"},
+ {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
{102, nullptr, "AuthenticateApplicationAsync"},
- {103, nullptr, "CheckNetworkServiceAvailabilityAsync"},
+ {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
- {130, nullptr, "LoadOpenContext"},
- {131, nullptr, "ListOpenContextStoredUsers"},
- {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"},
- {141, nullptr, "ListQualifiedUsers"},
- {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"},
+ {130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+
+ {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
+ {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
+ {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
+ {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
};
// clang-format on
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index 858e91dde..a4aa5316a 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -17,27 +17,29 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U1::GetProfile, "GetProfile"},
- {6, nullptr, "GetProfileDigest"},
+ {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"},
- {99, nullptr, "DebugActivateOpenContextRetention"},
+ {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
- {103, nullptr, "GetProfileUpdateNotifier"},
- {104, nullptr, "CheckNetworkServiceAvailabilityAsync"},
- {105, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
- {106, nullptr, "GetProfileSyncNotifier"},
+ {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
+ {104, nullptr, "GetProfileUpdateNotifier"},
+ {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
+ {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
- {113, nullptr, "GetSaveDataThumbnailExistence"},
- {130, nullptr, "ActivateOpenContextRetention"},
- {140, nullptr, "ListQualifiedUsers"},
- {190, nullptr, "GetUserLastOpenedApplication"},
- {191, nullptr, "ActivateOpenContextHolder"},
- {997, nullptr, "DebugInvalidateTokenCacheForUser"},
+ {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
+ {120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
+ {130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
+ {140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
+ {150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
+ {190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
+ {191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
+ {997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index eb8c81645..9b829e957 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -13,6 +13,7 @@
namespace Service::Account {
+namespace FS = Common::FS;
using Common::UUID;
struct UserRaw {
@@ -58,7 +59,7 @@ ProfileManager::~ProfileManager() {
/// internal management of the users profiles
std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
if (user_count >= MAX_USERS) {
- return {};
+ return std::nullopt;
}
profiles[user_count] = profile;
return user_count++;
@@ -101,13 +102,14 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& usern
[&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
return ERROR_USER_ALREADY_EXISTS;
}
- ProfileInfo profile;
- profile.user_uuid = uuid;
- profile.username = username;
- profile.data = {};
- profile.creation_time = 0x0;
- profile.is_open = false;
- return AddUser(profile);
+
+ return AddUser({
+ .user_uuid = uuid,
+ .username = username,
+ .creation_time = 0,
+ .data = {},
+ .is_open = false,
+ });
}
/// Creates a new user on the system. This function allows a much simpler method of registration
@@ -126,7 +128,7 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
if (index >= MAX_USERS) {
- return {};
+ return std::nullopt;
}
return profiles[index].user_uuid;
@@ -135,13 +137,13 @@ std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
/// Returns a users profile index based on their user id.
std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
- return {};
+ return std::nullopt;
}
const auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
- return {};
+ return std::nullopt;
}
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
@@ -317,9 +319,8 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
}
void ProfileManager::ParseUserSaveFile() {
- FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
- "rb");
+ const FS::IOFile save(
+ FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
@@ -339,7 +340,13 @@ void ProfileManager::ParseUserSaveFile() {
continue;
}
- AddUser({user.uuid, user.username, user.timestamp, user.extra_data, false});
+ AddUser({
+ .user_uuid = user.uuid,
+ .username = user.username,
+ .creation_time = user.timestamp,
+ .data = user.extra_data,
+ .is_open = false,
+ });
}
std::stable_partition(profiles.begin(), profiles.end(),
@@ -350,29 +357,31 @@ void ProfileManager::WriteUserSaveFile() {
ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) {
- raw.users[i].username = profiles[i].username;
- raw.users[i].uuid2 = profiles[i].user_uuid;
- raw.users[i].uuid = profiles[i].user_uuid;
- raw.users[i].timestamp = profiles[i].creation_time;
- raw.users[i].extra_data = profiles[i].data;
+ raw.users[i] = {
+ .uuid = profiles[i].user_uuid,
+ .uuid2 = profiles[i].user_uuid,
+ .timestamp = profiles[i].creation_time,
+ .username = profiles[i].username,
+ .extra_data = profiles[i].data,
+ };
}
- const auto raw_path =
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
- if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
- FileUtil::Delete(raw_path);
+ const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010";
+ if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) {
+ FS::Delete(raw_path);
+ }
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
+ const auto path =
+ FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
- if (!FileUtil::CreateFullPath(path)) {
+ if (!FS::CreateFullPath(path)) {
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
"nand/system/save/8000000000000010/su/avators to mitigate this "
"issue.");
return;
}
- FileUtil::IOFile save(path, "wb");
+ FS::IOFile save(path, "wb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 557608e76..63421b963 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -10,6 +10,7 @@
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
@@ -43,20 +44,15 @@
namespace Service::AM {
-constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
-constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
-constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
+constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 2};
+constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 3};
+constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 {
ApplicationSpecific = 1,
AccountPreselectedUser = 2,
};
-enum class VrMode : u8 {
- Disabled = 0,
- Enabled = 1,
-};
-
constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA;
struct LaunchParameterAccountPreselectedUser {
@@ -73,6 +69,7 @@ IWindowController::IWindowController(Core::System& system_)
static const FunctionInfo functions[] = {
{0, nullptr, "CreateWindow"},
{1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
+ {2, nullptr, "GetAppletResourceUserIdOfCallerApplet"},
{10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
{11, nullptr, "ReleaseForegroundRights"},
{12, nullptr, "RejectToChangeIntoBackground"},
@@ -194,8 +191,8 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
{5, nullptr, "GetLastForegroundCaptureImageEx"},
{6, nullptr, "GetLastApplicationCaptureImageEx"},
{7, nullptr, "GetCallerAppletCaptureImageEx"},
- {8, nullptr, "TakeScreenShotOfOwnLayer"}, // 2.0.0+
- {9, nullptr, "CopyBetweenCaptureBuffers"}, // 5.0.0+
+ {8, nullptr, "TakeScreenShotOfOwnLayer"},
+ {9, nullptr, "CopyBetweenCaptureBuffers"},
{10, nullptr, "AcquireLastApplicationCaptureBuffer"},
{11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
{12, nullptr, "AcquireLastForegroundCaptureBuffer"},
@@ -205,17 +202,14 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
{16, nullptr, "AcquireLastApplicationCaptureBufferEx"},
{17, nullptr, "AcquireLastForegroundCaptureBufferEx"},
{18, nullptr, "AcquireCallerAppletCaptureBufferEx"},
- // 2.0.0+
{20, nullptr, "ClearCaptureBuffer"},
{21, nullptr, "ClearAppletTransitionBuffer"},
- // 4.0.0+
{22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
{23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
{24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
{25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
{26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
{27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
- // 6.0.0+
{28, nullptr, "TakeScreenShotOfOwnLayerEx"},
};
// clang-format on
@@ -230,11 +224,12 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
static const FunctionInfo functions[] = {
{0, nullptr, "NotifyMessageToHomeMenuForDebug"},
{1, nullptr, "OpenMainApplication"},
- {10, nullptr, "EmulateButtonEvent"},
+ {10, nullptr, "PerformSystemButtonPressing"},
{20, nullptr, "InvalidateTransitionLayer"},
{30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"},
{40, nullptr, "GetAppletResourceUsageInfo"},
{100, nullptr, "SetCpuBoostModeForApplet"},
+ {101, nullptr, "CancelCpuBoostModeForApplet"},
{110, nullptr, "PushToAppletBoundChannelForDebug"},
{111, nullptr, "TryPopFromAppletBoundChannelForDebug"},
{120, nullptr, "AlarmSettingNotificationEnableAppEventReserve"},
@@ -271,12 +266,14 @@ ISelfController::ISelfController(Core::System& system,
{16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"},
{17, nullptr, "SetControllerFirmwareUpdateSection"},
{18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"},
- {19, &ISelfController::SetScreenShotImageOrientation, "SetScreenShotImageOrientation"},
+ {19, &ISelfController::SetAlbumImageOrientation, "SetAlbumImageOrientation"},
{20, nullptr, "SetDesirableKeyboardLayout"},
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
{41, nullptr, "IsSystemBufferSharingEnabled"},
{42, nullptr, "GetSystemSharedLayerHandle"},
{43, nullptr, "GetSystemSharedBufferHandle"},
+ {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
+ {45, nullptr, "SetManagedDisplayLayerSeparationMode"},
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
{51, nullptr, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
@@ -381,7 +378,11 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext&
}
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto permission = rp.PopEnum<ScreenshotPermission>();
+ LOG_DEBUG(Service_AM, "called, permission={}", permission);
+
+ screenshot_permission = permission;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -445,7 +446,7 @@ void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext&
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx) {
+void ISelfController::SetAlbumImageOrientation(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
@@ -465,6 +466,24 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx)
rb.Push(*layer_id);
}
+void ISelfController::CreateManagedDisplaySeparableLayer(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ // TODO(Subv): Find out how AM determines the display to use, for now just
+ // create the layer in the Default display.
+ // This calls nn::vi::CreateRecordingLayer() which creates another layer.
+ // Currently we do not support more than 1 layer per display, output 1 layer id for now.
+ // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
+ // side effects.
+ // TODO: Support multiple layers
+ const auto display_id = nvflinger->OpenDisplay("Default");
+ const auto layer_id = nvflinger->CreateLayer(*display_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(*layer_id);
+}
+
void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -609,6 +628,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system,
{20, nullptr, "PushToGeneralChannel"},
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
{31, nullptr, "GetReaderLockAccessorEx"},
+ {32, nullptr, "GetWriterLockAccessorEx"},
{40, nullptr, "GetCradleFwVersion"},
{50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
{51, &ICommonStateGetter::SetVrModeEnabled, "SetVrModeEnabled"},
@@ -623,11 +643,15 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system,
{64, nullptr, "SetTvPowerStateMatchingMode"},
{65, nullptr, "GetApplicationIdByContentActionName"},
{66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
+ {67, nullptr, "CancelCpuBoostMode"},
{80, nullptr, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
+ {100, nullptr, "SetHandlingHomeButtonShortPressedEnabled"},
{200, nullptr, "GetOperationModeSystemInfo"},
{300, nullptr, "GetSettingsPlatformRegion"},
+ {400, nullptr, "ActivateMigrationService"},
+ {401, nullptr, "DeactivateMigrationService"},
};
// clang-format on
@@ -678,27 +702,21 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
}
void ICommonStateGetter::IsVrModeEnabled(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushEnum(VrMode::Disabled);
+ rb.Push(vr_mode_state);
}
void ICommonStateGetter::SetVrModeEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto is_vr_mode_enabled = rp.Pop<bool>();
+ vr_mode_state = rp.Pop<bool>();
- LOG_WARNING(Service_AM, "(STUBBED) called. is_vr_mode_enabled={}", is_vr_mode_enabled);
+ LOG_WARNING(Service_AM, "VR Mode is {}", vr_mode_state ? "on" : "off");
IPC::ResponseBuilder rb{ctx, 2};
- if (!is_vr_mode_enabled) {
- rb.Push(RESULT_SUCCESS);
- } else {
- // TODO: Find better error code for this
- UNIMPLEMENTED_MSG("is_vr_mode_enabled={}", is_vr_mode_enabled);
- rb.Push(RESULT_UNKNOWN);
- }
+ rb.Push(RESULT_SUCCESS);
}
void ICommonStateGetter::SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx) {
@@ -733,16 +751,16 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
} else {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
}
}
@@ -806,7 +824,7 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) {
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode};
+ const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};
@@ -835,6 +853,7 @@ public:
{25, nullptr, "Terminate"},
{30, &ILibraryAppletAccessor::GetResult, "GetResult"},
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
+ {60, nullptr, "PresetLibraryAppletGpuTimeSliceZero"},
{100, &ILibraryAppletAccessor::PushInData, "PushInData"},
{101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
{102, nullptr, "PushExtraStorage"},
@@ -845,7 +864,7 @@ public:
{110, nullptr, "NeedsToExitProcess"},
{120, nullptr, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"},
- {160, nullptr, "GetIndirectLayerConsumerHandle"},
+ {160, &ILibraryAppletAccessor::GetIndirectLayerConsumerHandle, "GetIndirectLayerConsumerHandle"},
};
// clang-format on
@@ -903,7 +922,7 @@ private:
void PopOutData(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- const auto storage = applet->GetBroker().PopNormalDataToGame();
+ auto storage = applet->GetBroker().PopNormalDataToGame();
if (storage == nullptr) {
LOG_ERROR(Service_AM,
"storage is a nullptr. There is no data in the current normal channel");
@@ -934,7 +953,7 @@ private:
void PopInteractiveOutData(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- const auto storage = applet->GetBroker().PopInteractiveDataToGame();
+ auto storage = applet->GetBroker().PopInteractiveDataToGame();
if (storage == nullptr) {
LOG_ERROR(Service_AM,
"storage is a nullptr. There is no data in the current interactive channel");
@@ -964,6 +983,18 @@ private:
rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent());
}
+ void GetIndirectLayerConsumerHandle(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ // We require a non-zero handle to be valid. Using 0xdeadbeef allows us to trace if this is
+ // actually used anywhere
+ constexpr u64 handle = 0xdeadbeef;
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(handle);
+ }
+
std::shared_ptr<Applets::Applet> applet;
};
@@ -1135,10 +1166,12 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{24, nullptr, "GetLaunchStorageInfoForDebug"},
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
+ {27, nullptr, "CreateCacheStorage"},
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
{31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
{32, &IApplicationFunctions::BeginBlockingHomeButton, "BeginBlockingHomeButton"},
{33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"},
+ {34, nullptr, "SelectApplicationLicense"},
{40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
{50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
{60, nullptr, "SetMediaPlaybackStateForApplication"},
@@ -1148,6 +1181,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{68, nullptr, "RequestFlushGamePlayingMovieForDebug"},
{70, nullptr, "RequestToShutdown"},
{71, nullptr, "RequestToReboot"},
+ {72, nullptr, "RequestToSleep"},
{80, nullptr, "ExitAndRequestToShowThanksMessage"},
{90, &IApplicationFunctions::EnableApplicationCrashReport, "EnableApplicationCrashReport"},
{100, &IApplicationFunctions::InitializeApplicationCopyrightFrameBuffer, "InitializeApplicationCopyrightFrameBuffer"},
@@ -1158,13 +1192,17 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{120, nullptr, "ExecuteProgram"},
{121, nullptr, "ClearUserChannel"},
{122, nullptr, "UnpopToUserChannel"},
+ {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"},
+ {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
- {140, nullptr, "GetFriendInvitationStorageChannelEvent"},
+ {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"},
{141, nullptr, "TryPopFromFriendInvitationStorageChannel"},
{150, nullptr, "GetNotificationStorageChannelEvent"},
{151, nullptr, "TryPopFromNotificationStorageChannel"},
{160, nullptr, "GetHealthWarningDisappearedSystemEvent"},
{170, nullptr, "SetHdcpAuthenticationActivated"},
+ {180, nullptr, "GetLaunchRequiredVersion"},
+ {181, nullptr, "UpgradeLaunchRequiredVersion"},
{500, nullptr, "StartContinuousRecordingFlushForDebug"},
{1000, nullptr, "CreateMovieMaker"},
{1001, nullptr, "PrepareForJit"},
@@ -1176,6 +1214,9 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
auto& kernel = system.Kernel();
gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
kernel, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
+
+ friend_invitation_storage_channel_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, "IApplicationFunctions:FriendInvitationStorageChannelEvent");
}
IApplicationFunctions::~IApplicationFunctions() = default;
@@ -1307,12 +1348,12 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
- FileSys::SaveDataDescriptor descriptor{};
- descriptor.title_id = system.CurrentProcess()->GetTitleID();
- descriptor.user_id = user_id;
- descriptor.type = FileSys::SaveDataType::SaveData;
+ FileSys::SaveDataAttribute attribute{};
+ attribute.title_id = system.CurrentProcess()->GetTitleID();
+ attribute.user_id = user_id;
+ attribute.type = FileSys::SaveDataType::SaveData;
const auto res = system.GetFileSystemController().CreateSaveData(
- FileSys::SaveDataSpaceId::NandUser, descriptor);
+ FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(res.Code());
@@ -1333,12 +1374,37 @@ void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
}
void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ std::array<u8, 0x10> version_string{};
+
+ const auto res = [this] {
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ auto res = pm.GetControlMetadata();
+ if (res.first != nullptr) {
+ return res;
+ }
+
+ const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
+ return pm_update.GetControlMetadata();
+ }();
+
+ if (res.first != nullptr) {
+ const auto& version = res.first->GetVersionString();
+ std::copy(version.begin(), version.end(), version_string.begin());
+ } else {
+ constexpr char default_version[]{"1.0.0"};
+ std::memcpy(version_string.data(), default_version, sizeof(default_version));
+ }
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(1);
- rb.Push<u64>(0);
+ rb.PushRaw(version_string);
}
void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
@@ -1348,9 +1414,23 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
// Get supported languages from NACP, if possible
// Default to 0 (all languages supported)
u32 supported_languages = 0;
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
- const auto res = pm.GetControlMetadata();
+ const auto res = [this] {
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ auto res = pm.GetControlMetadata();
+ if (res.first != nullptr) {
+ return res;
+ }
+
+ const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
+ return pm_update.GetControlMetadata();
+ }();
+
if (res.first != nullptr) {
supported_languages = res.first->GetSupportedLanguages();
}
@@ -1482,6 +1562,14 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque
rb.Push<u32>(0);
}
+void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(previous_program_index);
+}
+
void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1490,6 +1578,14 @@ void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestCon
rb.PushCopyObjects(gpu_error_detected_event.readable);
}
+void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(friend_invitation_storage_channel_event.readable);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel());
@@ -1504,14 +1600,15 @@ void InstallInterfaces(SM::ServiceManager& service_manager,
std::make_shared<TCAP>()->InstallAsService(service_manager);
}
-IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") {
+IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel)
+ : ServiceFramework("IHomeMenuFunctions"), kernel(kernel) {
// clang-format off
static const FunctionInfo functions[] = {
{10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
{11, nullptr, "LockForeground"},
{12, nullptr, "UnlockForeground"},
{20, nullptr, "PopFromGeneralChannel"},
- {21, nullptr, "GetPopFromGeneralChannelEvent"},
+ {21, &IHomeMenuFunctions::GetPopFromGeneralChannelEvent, "GetPopFromGeneralChannelEvent"},
{30, nullptr, "GetHomeButtonWriterLockAccessor"},
{31, nullptr, "GetWriterLockAccessorEx"},
{100, nullptr, "PopRequestLaunchApplicationForDebug"},
@@ -1521,6 +1618,9 @@ IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions"
// clang-format on
RegisterHandlers(functions);
+
+ pop_from_general_channel_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, "IHomeMenuFunctions:PopFromGeneralChannelEvent");
}
IHomeMenuFunctions::~IHomeMenuFunctions() = default;
@@ -1532,6 +1632,14 @@ void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx)
rb.Push(RESULT_SUCCESS);
}
+void IHomeMenuFunctions::GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(pop_from_general_channel_event.readable);
+}
+
IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
// clang-format off
static const FunctionInfo functions[] = {
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 53cfce10f..bcc06affe 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -138,8 +138,9 @@ private:
void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx);
void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx);
- void SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx);
+ void SetAlbumImageOrientation(Kernel::HLERequestContext& ctx);
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
+ void CreateManagedDisplaySeparableLayer(Kernel::HLERequestContext& ctx);
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
@@ -148,6 +149,12 @@ private:
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
+ enum class ScreenshotPermission : u32 {
+ Inherit = 0,
+ Enable = 1,
+ Disable = 2,
+ };
+
Core::System& system;
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::EventPair launchable_event;
@@ -156,6 +163,7 @@ private:
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
bool is_auto_sleep_disabled = false;
+ ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
@@ -191,6 +199,7 @@ private:
Core::System& system;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ bool vr_mode_state{};
};
class IStorageImpl {
@@ -279,21 +288,29 @@ private:
void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
+ void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
+ void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false;
+ s32 previous_program_index{-1};
Kernel::EventPair gpu_error_detected_event;
+ Kernel::EventPair friend_invitation_storage_channel_event;
Core::System& system;
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
public:
- IHomeMenuFunctions();
+ explicit IHomeMenuFunctions(Kernel::KernelCore& kernel);
~IHomeMenuFunctions() override;
private:
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
+ void GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx);
+
+ Kernel::EventPair pop_from_general_channel_event;
+ Kernel::KernelCore& kernel;
};
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index e454b77d8..be23ca747 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -3,8 +3,8 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -202,7 +202,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHomeMenuFunctions>();
+ rb.PushIpcInterface<IHomeMenuFunctions>(system.Kernel());
}
void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index c3261f3e6..2b626bb40 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -5,6 +5,7 @@
#include <cstring>
#include "common/assert.h"
#include "core/core.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/profile_select.h"
@@ -15,6 +16,7 @@
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/am/applets/controller.h"
#include "core/hle/service/am/applets/error.h"
#include "core/hle/service/am/applets/general_backend.h"
#include "core/hle/service/am/applets/profile_select.h"
@@ -140,14 +142,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
-AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
+AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce,
+ ErrorApplet error, ParentalControlsApplet parental_controls,
PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce)
- : parental_controls{std::move(parental_controls)}, error{std::move(error)},
- photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
- software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
- e_commerce{std::move(e_commerce)} {}
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser)
+ : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)},
+ parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)},
+ profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)},
+ web_browser{std::move(web_browser)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
}
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
- if (set.parental_controls != nullptr)
- frontend.parental_controls = std::move(set.parental_controls);
- if (set.error != nullptr)
+ if (set.controller != nullptr) {
+ frontend.controller = std::move(set.controller);
+ }
+
+ if (set.e_commerce != nullptr) {
+ frontend.e_commerce = std::move(set.e_commerce);
+ }
+
+ if (set.error != nullptr) {
frontend.error = std::move(set.error);
- if (set.photo_viewer != nullptr)
+ }
+
+ if (set.parental_controls != nullptr) {
+ frontend.parental_controls = std::move(set.parental_controls);
+ }
+
+ if (set.photo_viewer != nullptr) {
frontend.photo_viewer = std::move(set.photo_viewer);
- if (set.profile_select != nullptr)
+ }
+
+ if (set.profile_select != nullptr) {
frontend.profile_select = std::move(set.profile_select);
- if (set.software_keyboard != nullptr)
+ }
+
+ if (set.software_keyboard != nullptr) {
frontend.software_keyboard = std::move(set.software_keyboard);
- if (set.web_browser != nullptr)
+ }
+
+ if (set.web_browser != nullptr) {
frontend.web_browser = std::move(set.web_browser);
- if (set.e_commerce != nullptr)
- frontend.e_commerce = std::move(set.e_commerce);
+ }
}
void AppletManager::SetDefaultAppletFrontendSet() {
@@ -186,15 +205,24 @@ void AppletManager::SetDefaultAppletFrontendSet() {
}
void AppletManager::SetDefaultAppletsIfMissing() {
- if (frontend.parental_controls == nullptr) {
- frontend.parental_controls =
- std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ if (frontend.controller == nullptr) {
+ frontend.controller =
+ std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
+ }
+
+ if (frontend.e_commerce == nullptr) {
+ frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
+ if (frontend.parental_controls == nullptr) {
+ frontend.parental_controls =
+ std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ }
+
if (frontend.photo_viewer == nullptr) {
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
}
@@ -211,10 +239,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
-
- if (frontend.e_commerce == nullptr) {
- frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
- }
}
void AppletManager::ClearAll() {
@@ -225,6 +249,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(system, *frontend.parental_controls);
+ case AppletId::Controller:
+ return std::make_shared<Controller>(system, *frontend.controller);
case AppletId::Error:
return std::make_shared<Error>(system, *frontend.error);
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;
}
namespace Core::Frontend {
+class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
class ParentalControlsApplet;
@@ -155,19 +156,20 @@ protected:
};
struct AppletFrontendSet {
- using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
+ using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
+ using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
+ using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
- using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
AppletFrontendSet();
- AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
- PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce);
+ AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error,
+ ParentalControlsApplet parental_controls, PhotoViewer photo_viewer,
+ ProfileSelect profile_select, SoftwareKeyboard software_keyboard,
+ WebBrowser web_browser);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -176,13 +178,14 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
- ParentalControlsApplet parental_controls;
+ ControllerApplet controller;
+ ECommerceApplet e_commerce;
ErrorApplet error;
+ ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
- ECommerceApplet e_commerce;
};
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..3ca63f020
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.cpp
@@ -0,0 +1,252 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+
+namespace Service::AM::Applets {
+
+// This error code (0x183ACA) is thrown when the applet fails to initialize.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
+// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
+
+static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
+ ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
+ std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
+ HID::Controller_NPad::NpadStyleSet npad_style_set;
+ npad_style_set.raw = private_arg.style_set;
+
+ return {
+ .min_players = std::max(s8(1), header.player_count_min),
+ .max_players = header.player_count_max,
+ .keep_controllers_connected = header.enable_take_over_connection,
+ .enable_single_mode = header.enable_single_mode,
+ .enable_border_color = header.enable_identification_color,
+ .border_colors = identification_colors,
+ .enable_explain_text = enable_text,
+ .explain_text = text,
+ .allow_pro_controller = npad_style_set.pro_controller == 1,
+ .allow_handheld = npad_style_set.handheld == 1,
+ .allow_dual_joycons = npad_style_set.joycon_dual == 1,
+ .allow_left_joycon = npad_style_set.joycon_left == 1,
+ .allow_right_joycon = npad_style_set.joycon_right == 1,
+ };
+}
+
+Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_) {}
+
+Controller::~Controller() = default;
+
+void Controller::Initialize() {
+ Applet::Initialize();
+
+ LOG_INFO(Service_HID, "Initializing Controller Applet.");
+
+ LOG_DEBUG(Service_HID,
+ "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+ "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+ common_args.arguments_version, common_args.library_version,
+ common_args.play_startup_sound, common_args.size, common_args.system_tick,
+ common_args.theme_color);
+
+ controller_applet_version = ControllerAppletVersion{common_args.library_version};
+
+ const auto private_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(private_arg_storage != nullptr);
+
+ const auto& private_arg = private_arg_storage->GetData();
+ ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
+
+ std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size());
+ ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
+ "Unknown ControllerSupportArgPrivate revision={} with size={}",
+ controller_applet_version, controller_private_arg.arg_private_size);
+
+ // Some games such as Cave Story+ set invalid values for the ControllerSupportMode.
+ // Defer to arg_size to set the ControllerSupportMode.
+ if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) {
+ switch (controller_private_arg.arg_size) {
+ case sizeof(ControllerSupportArgOld):
+ case sizeof(ControllerSupportArgNew):
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
+ break;
+ case sizeof(ControllerUpdateFirmwareArg):
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}",
+ controller_private_arg.mode, controller_private_arg.arg_size);
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
+ break;
+ }
+ }
+
+ // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller.
+ // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem.
+ if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) {
+ if (controller_private_arg.flag_1 &&
+ controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) {
+ controller_private_arg.caller = ControllerSupportCaller::System;
+ } else {
+ controller_private_arg.caller = ControllerSupportCaller::Application;
+ }
+ }
+
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport:
+ case ControllerSupportMode::ShowControllerStrapGuide: {
+ const auto user_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(user_arg_storage != nullptr);
+
+ const auto& user_arg = user_arg_storage->GetData();
+ switch (controller_applet_version) {
+ case ControllerAppletVersion::Version3:
+ case ControllerAppletVersion::Version4:
+ case ControllerAppletVersion::Version5:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
+ std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size());
+ break;
+ case ControllerAppletVersion::Version7:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size());
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
+ controller_applet_version, controller_private_arg.arg_size);
+ ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
+ break;
+ }
+ break;
+ }
+ case ControllerSupportMode::ShowControllerFirmwareUpdate: {
+ const auto update_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(update_arg_storage != nullptr);
+
+ const auto& update_arg = update_arg_storage->GetData();
+ ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg));
+
+ std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size());
+ break;
+ }
+ default: {
+ UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
+ break;
+ }
+ }
+}
+
+bool Controller::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode Controller::GetStatus() const {
+ return status;
+}
+
+void Controller::ExecuteInteractive() {
+ UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+}
+
+void Controller::Execute() {
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport: {
+ const auto parameters = [this] {
+ switch (controller_applet_version) {
+ case ControllerAppletVersion::Version3:
+ case ControllerAppletVersion::Version4:
+ case ControllerAppletVersion::Version5:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_old.header,
+ controller_user_arg_old.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_old.identification_colors.begin(),
+ controller_user_arg_old.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
+ controller_user_arg_old.explain_text.end()));
+ case ControllerAppletVersion::Version7:
+ default:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_new.header,
+ controller_user_arg_new.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_new.identification_colors.begin(),
+ controller_user_arg_new.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
+ controller_user_arg_new.explain_text.end()));
+ }
+ }();
+
+ is_single_mode = parameters.enable_single_mode;
+
+ LOG_DEBUG(Service_HID,
+ "Controller Parameters: min_players={}, max_players={}, "
+ "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
+ "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
+ "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
+ parameters.min_players, parameters.max_players,
+ parameters.keep_controllers_connected, parameters.enable_single_mode,
+ parameters.enable_border_color, parameters.enable_explain_text,
+ parameters.allow_pro_controller, parameters.allow_handheld,
+ parameters.allow_dual_joycons, parameters.allow_left_joycon,
+ parameters.allow_right_joycon);
+
+ frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
+ break;
+ }
+ case ControllerSupportMode::ShowControllerStrapGuide:
+ case ControllerSupportMode::ShowControllerFirmwareUpdate:
+ UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented",
+ controller_private_arg.mode);
+ [[fallthrough]];
+ default: {
+ ConfigurationComplete();
+ break;
+ }
+ }
+}
+
+void Controller::ConfigurationComplete() {
+ ControllerSupportResultInfo result_info{};
+
+ const auto& players = Settings::values.players.GetValue();
+
+ // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
+ // Otherwise, only count connected players from P1-P8.
+ result_info.player_count =
+ is_single_mode ? 1
+ : static_cast<s8>(std::count_if(
+ players.begin(), players.end() - 2,
+ [](Settings::PlayerInput player) { return player.connected; }));
+
+ result_info.selected_id = HID::Controller_NPad::IndexToNPad(
+ std::distance(players.begin(),
+ std::find_if(players.begin(), players.end(),
+ [](Settings::PlayerInput player) { return player.connected; })));
+
+ result_info.result = 0;
+
+ LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
+ result_info.player_count, result_info.selected_id, result_info.result);
+
+ complete = true;
+ out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
+ std::memcpy(out_data.data(), &result_info, out_data.size());
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out_data)));
+ broker.SignalStateChanged();
+}
+
+} // 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..a7a1f2b65
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.h
@@ -0,0 +1,135 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::AM::Applets {
+
+using IdentificationColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+enum class ControllerAppletVersion : u32_le {
+ Version3 = 0x3, // 1.0.0 - 2.3.0
+ Version4 = 0x4, // 3.0.0 - 5.1.0
+ Version5 = 0x5, // 6.0.0 - 7.0.1
+ Version7 = 0x7, // 8.0.0+
+};
+
+enum class ControllerSupportMode : u8 {
+ ShowControllerSupport,
+ ShowControllerStrapGuide,
+ ShowControllerFirmwareUpdate,
+
+ MaxControllerSupportMode,
+};
+
+enum class ControllerSupportCaller : u8 {
+ Application,
+ System,
+
+ MaxControllerSupportCaller,
+};
+
+struct ControllerSupportArgPrivate {
+ u32 arg_private_size{};
+ u32 arg_size{};
+ bool flag_0{};
+ bool flag_1{};
+ ControllerSupportMode mode{};
+ ControllerSupportCaller caller{};
+ u32 style_set{};
+ u32 joy_hold_type{};
+};
+static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
+ "ControllerSupportArgPrivate has incorrect size.");
+
+struct ControllerSupportArgHeader {
+ s8 player_count_min{};
+ s8 player_count_max{};
+ bool enable_take_over_connection{};
+ bool enable_left_justify{};
+ bool enable_permit_joy_dual{};
+ bool enable_single_mode{};
+ bool enable_identification_color{};
+};
+static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
+ "ControllerSupportArgHeader has incorrect size.");
+
+// LibraryAppletVersion 0x3, 0x4, 0x5
+struct ControllerSupportArgOld {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 4> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 4> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
+ "ControllerSupportArgOld has incorrect size.");
+
+// LibraryAppletVersion 0x7
+struct ControllerSupportArgNew {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 8> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 8> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgNew) == 0x430,
+ "ControllerSupportArgNew has incorrect size.");
+
+struct ControllerUpdateFirmwareArg {
+ bool enable_force_update{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4,
+ "ControllerUpdateFirmwareArg has incorrect size.");
+
+struct ControllerSupportResultInfo {
+ s8 player_count{};
+ INSERT_PADDING_BYTES(3);
+ u32 selected_id{};
+ u32 result{};
+};
+static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
+ "ControllerSupportResultInfo has incorrect size.");
+
+class Controller final : public Applet {
+public:
+ explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
+ ~Controller() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void ConfigurationComplete();
+
+private:
+ const Core::Frontend::ControllerApplet& frontend;
+
+ ControllerAppletVersion controller_applet_version;
+ ControllerSupportArgPrivate controller_private_arg;
+ ControllerSupportArgOld controller_user_arg_old;
+ ControllerSupportArgNew controller_user_arg_new;
+ ControllerUpdateFirmwareArg controller_update_arg;
+ bool complete{false};
+ ResultCode status{RESULT_SUCCESS};
+ bool is_single_mode{false};
+ std::vector<u8> out_data;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index 54e63c138..bdeb0737a 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -13,11 +13,23 @@
namespace Service::AM::Applets {
+namespace {
+enum class Request : u32 {
+ Finalize = 0x4,
+ SetUserWordInfo = 0x6,
+ SetCustomizeDic = 0x7,
+ Calc = 0xa,
+ SetCustomizedDictionaries = 0xb,
+ UnsetCustomizedDictionaries = 0xc,
+ UnknownD = 0xd,
+ UnknownE = 0xe,
+};
+constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8;
constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8;
constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4;
constexpr std::size_t DEFAULT_MAX_LENGTH = 500;
constexpr bool INTERACTIVE_STATUS_OK = false;
-
+} // Anonymous namespace
static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
KeyboardConfig config, std::u16string initial_text) {
Core::Frontend::SoftwareKeyboardParameters params{};
@@ -30,7 +42,7 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
config.sub_text.size());
params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(),
config.guide_text.size());
- params.initial_text = initial_text;
+ params.initial_text = std::move(initial_text);
params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit;
params.password = static_cast<bool>(config.is_password);
params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position);
@@ -47,6 +59,7 @@ SoftwareKeyboard::~SoftwareKeyboard() = default;
void SoftwareKeyboard::Initialize() {
complete = false;
+ is_inline = false;
initial_text.clear();
final_data.clear();
@@ -56,11 +69,16 @@ void SoftwareKeyboard::Initialize() {
ASSERT(keyboard_config_storage != nullptr);
const auto& keyboard_config = keyboard_config_storage->GetData();
+ if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) {
+ is_inline = true;
+ return;
+ }
+
ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig));
std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig));
const auto work_buffer_storage = broker.PopNormalDataToApplet();
- ASSERT(work_buffer_storage != nullptr);
+ ASSERT_OR_EXECUTE(work_buffer_storage != nullptr, { return; });
const auto& work_buffer = work_buffer_storage->GetData();
if (config.initial_string_size == 0)
@@ -87,16 +105,31 @@ void SoftwareKeyboard::ExecuteInteractive() {
const auto storage = broker.PopInteractiveDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
- const auto status = static_cast<bool>(data[0]);
-
- if (status == INTERACTIVE_STATUS_OK) {
- complete = true;
+ if (!is_inline) {
+ const auto status = static_cast<bool>(data[0]);
+ if (status == INTERACTIVE_STATUS_OK) {
+ complete = true;
+ } else {
+ std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
+ std::memcpy(string.data(), data.data() + 4, string.size() * 2);
+ frontend.SendTextCheckDialog(
+ Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
+ [this] { broker.SignalStateChanged(); });
+ }
} else {
- std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
- std::memcpy(string.data(), data.data() + 4, string.size() * 2);
- frontend.SendTextCheckDialog(
- Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
- [this] { broker.SignalStateChanged(); });
+ Request request{};
+ std::memcpy(&request, data.data(), sizeof(Request));
+
+ switch (request) {
+ case Request::Calc: {
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{1}));
+ broker.SignalStateChanged();
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Request {:X} is not implemented", request);
+ break;
+ }
}
}
@@ -108,9 +141,10 @@ void SoftwareKeyboard::Execute() {
}
const auto parameters = ConvertToFrontendParameters(config, initial_text);
-
- frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); },
- parameters);
+ if (!is_inline) {
+ frontend.RequestText(
+ [this](std::optional<std::u16string> text) { WriteText(std::move(text)); }, parameters);
+ }
}
void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index ef4801fc6..5a3824b5a 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -78,6 +78,7 @@ private:
KeyboardConfig config;
std::u16string initial_text;
bool complete = false;
+ bool is_inline = false;
std::vector<u8> final_data;
};
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 9f30e167d..efe595c4f 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -293,8 +293,8 @@ void WebBrowser::Finalize() {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data)));
broker.SignalStateChanged();
- if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
- FileUtil::DeleteDirRecursively(temporary_dir);
+ if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
+ Common::FS::DeleteDirRecursively(temporary_dir);
}
}
@@ -452,10 +452,10 @@ void WebBrowser::InitializeOffline() {
};
temporary_dir =
- FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
- WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
- FileUtil::DirectorySeparator::PlatformDefault);
- FileUtil::DeleteDirRecursively(temporary_dir);
+ Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ "web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
+ Common::FS::DirectorySeparator::PlatformDefault);
+ Common::FS::DeleteDirRecursively(temporary_dir);
u64 title_id = 0; // 0 corresponds to current process
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
@@ -492,8 +492,8 @@ void WebBrowser::InitializeOffline() {
}
filename =
- FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
- FileUtil::DirectorySeparator::PlatformDefault);
+ Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
+ Common::FS::DirectorySeparator::PlatformDefault);
}
void WebBrowser::ExecuteShop() {
@@ -551,7 +551,8 @@ void WebBrowser::ExecuteShop() {
}
void WebBrowser::ExecuteOffline() {
- frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
+ frontend.OpenPageLocal(
+ filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp
index 003ee8667..f27729ce7 100644
--- a/src/core/hle/service/am/spsm.cpp
+++ b/src/core/hle/service/am/spsm.cpp
@@ -10,17 +10,17 @@ SPSM::SPSM() : ServiceFramework{"spsm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
- {1, nullptr, "SleepSystemAndWaitAwake"},
- {2, nullptr, "Unknown1"},
- {3, nullptr, "Unknown2"},
+ {1, nullptr, "EnterSleep"},
+ {2, nullptr, "GetLastWakeReason"},
+ {3, nullptr, "Shutdown"},
{4, nullptr, "GetNotificationMessageEventHandle"},
- {5, nullptr, "Unknown3"},
- {6, nullptr, "Unknown4"},
- {7, nullptr, "Unknown5"},
+ {5, nullptr, "ReceiveNotificationMessage"},
+ {6, nullptr, "AnalyzeLogForLastSleepWakeSequence"},
+ {7, nullptr, "ResetEventLog"},
{8, nullptr, "AnalyzePerformanceLogForLastSleepWakeSequence"},
{9, nullptr, "ChangeHomeButtonLongPressingTime"},
- {10, nullptr, "Unknown6"},
- {11, nullptr, "Unknown7"},
+ {10, nullptr, "PutErrorState"},
+ {11, nullptr, "InvalidateCurrentHomeButtonPressing"},
};
// clang-format on
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 4227a4adf..173b36da4 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -6,6 +6,7 @@
#include <numeric>
#include <vector>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
@@ -60,6 +61,7 @@ AOC_U::AOC_U(Core::System& system)
{6, nullptr, "PrepareAddOnContentByApplicationId"},
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
+ {9, nullptr, "GetAddOnContentLostErrorCode"},
{100, nullptr, "CreateEcPurchasedEventManager"},
{101, nullptr, "CreatePermanentEcPurchasedEventManager"},
};
@@ -162,7 +164,8 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
const auto title_id = system.CurrentProcess()->GetTitleID();
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto res = pm.GetControlMetadata();
if (res.first == nullptr) {
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index 85bbf5988..e2d8f0027 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp
index 25a886238..ce993bad3 100644
--- a/src/core/hle/service/apm/controller.cpp
+++ b/src/core/hle/service/apm/controller.cpp
@@ -69,7 +69,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld;
+ return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
+ : PerformanceMode::Handheld;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index 9e08e5346..6ddb547fb 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -39,6 +39,8 @@ AudCtl::AudCtl() : ServiceFramework{"audctl"} {
{25, nullptr, "GetAudioVolumeDataForPlayReport"},
{26, nullptr, "UpdateHeadphoneSettings"},
{27, nullptr, "SetVolumeMappingTableForDev"},
+ {28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
+ {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
};
// clang-format on
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index d7f1d348d..3e2299426 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audin_u.h"
namespace Service::Audio {
@@ -36,11 +39,12 @@ public:
AudInU::AudInU() : ServiceFramework("audin:u") {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "ListAudioIns"},
- {1, nullptr, "OpenAudioIn"},
- {2, nullptr, "Unknown"},
- {3, nullptr, "OpenAudioInAuto"},
- {4, nullptr, "ListAudioInsAuto"},
+ {0, &AudInU::ListAudioIns, "ListAudioIns"},
+ {1, &AudInU::OpenAudioIn, "OpenAudioIn"},
+ {2, &AudInU::ListAudioIns, "ListAudioInsAuto"},
+ {3, &AudInU::OpenAudioIn, "OpenAudioInAuto"},
+ {4, &AudInU::ListAudioInsAutoFiltered, "ListAudioInsAutoFiltered"},
+ {5, &AudInU::OpenAudioInProtocolSpecified, "OpenAudioInProtocolSpecified"},
};
// clang-format on
@@ -49,4 +53,60 @@ AudInU::AudInU() : ServiceFramework("audin:u") {
AudInU::~AudInU() = default;
+void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+ const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName);
+
+ const std::size_t device_count = std::min(count, audio_device_names.size());
+ std::vector<AudioInDeviceName> device_names;
+ device_names.reserve(device_count);
+
+ for (std::size_t i = 0; i < device_count; i++) {
+ const auto& device_name = audio_device_names[i];
+ auto& entry = device_names.emplace_back();
+ device_name.copy(entry.data(), device_name.size());
+ }
+
+ ctx.WriteBuffer(device_names);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(device_names.size()));
+}
+
+void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+ constexpr u32 device_count = 0;
+
+ // Since we don't actually use any other audio input devices, we return 0 devices. Filtered
+ // device listing just omits the default input device
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(device_count));
+}
+
+void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) {
+ AudInOutParams params{};
+ params.channel_count = 2;
+ params.sample_format = SampleFormat::PCM16;
+ params.sample_rate = 48000;
+ params.state = State::Started;
+
+ IPC::ResponseBuilder rb{ctx, 6, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<AudInOutParams>(params);
+ rb.PushIpcInterface<IAudioIn>();
+}
+
+void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ OpenInOutImpl(ctx);
+}
+
+void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ OpenInOutImpl(ctx);
+}
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
index 0538b9560..a599f4a64 100644
--- a/src/core/hle/service/audio/audin_u.h
+++ b/src/core/hle/service/audio/audin_u.h
@@ -16,6 +16,35 @@ class AudInU final : public ServiceFramework<AudInU> {
public:
explicit AudInU();
~AudInU() override;
+
+private:
+ enum class SampleFormat : u32_le {
+ PCM16 = 2,
+ };
+
+ enum class State : u32_le {
+ Started = 0,
+ Stopped = 1,
+ };
+
+ struct AudInOutParams {
+ u32_le sample_rate{};
+ u32_le channel_count{};
+ SampleFormat sample_format{};
+ State state{};
+ };
+ static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size");
+
+ using AudioInDeviceName = std::array<char, 256>;
+ static constexpr std::array<std::string_view, 1> audio_device_names{{
+ "BuiltInHeadset",
+ }};
+
+ void ListAudioIns(Kernel::HLERequestContext& ctx);
+ void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx);
+ void OpenInOutImpl(Kernel::HLERequestContext& ctx);
+ void OpenAudioIn(Kernel::HLERequestContext& ctx);
+ void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 4fb2cbc4b..9b4910e53 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -71,7 +71,7 @@ public:
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
audio_params.channel_count, std::move(unique_name),
- [=]() { buffer_event.writable->Signal(); });
+ [this] { buffer_event.writable->Signal(); });
}
private:
@@ -206,11 +206,11 @@ private:
AudioCore::StreamPtr stream;
std::string device_name;
- [[maybe_unused]] AudoutParams audio_params {};
+ [[maybe_unused]] AudoutParams audio_params{};
/// This is the event handle used to check if the audio buffer was released
Kernel::EventPair buffer_event;
- Memory::Memory& main_memory;
+ Core::Memory::Memory& main_memory;
};
AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} {
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 82a5dbf14..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 {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
- explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
+ explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
const std::size_t instance_number)
: ServiceFramework("IAudioRenderer") {
// clang-format off
@@ -92,11 +92,17 @@ private:
}
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "(STUBBED) called");
+
+ std::vector<u8> output_params(ctx.GetWriteBufferSize());
+ auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(output_params);
+ }
- ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ rb.Push(result);
}
void Start(Kernel::HLERequestContext& ctx) {
@@ -129,7 +135,7 @@ private:
LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}",
rendering_time_limit_percent);
- ASSERT(rendering_time_limit_percent >= 0 && rendering_time_limit_percent <= 100);
+ ASSERT(rendering_time_limit_percent <= 100);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -252,8 +258,6 @@ private:
}
void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
const auto device_name_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(device_name_buffer);
@@ -343,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
OpenAudioRendererImpl(ctx);
}
-static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
+static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
// +1 represents the final mix.
return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
1;
@@ -372,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
constexpr u64 upsampler_manager_size = 0x48;
// Calculates the part of the size that relates to mix buffers.
- const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
// As of 8.0.0 this is the maximum on voice channels.
constexpr u64 max_voice_channels = 6;
@@ -394,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the portion of the size related to the mix data (and the sorting thereof).
- const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
// The size of the mixing info data structure.
constexpr u64 mix_info_size = 0x940;
@@ -444,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to voice channel info.
- const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 voice_info_size = 0x220;
constexpr u64 voice_resource_size = 0xD0;
@@ -458,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to memory pools.
- const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
const u64 memory_pool_info_size = 0x20;
return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
@@ -466,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
// Calculates the part of the size related to the splitter context.
const auto calculate_splitter_context_size =
- [](const AudioCore::AudioRendererParameter& params) -> u64 {
+ [](const AudioCommon::AudioRendererParameter& params) -> u64 {
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
return 0;
}
@@ -485,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to the upsampler info.
- const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
- constexpr u64 upsampler_info_size = 0x280;
- // Yes, using the buffer size over info alignment size is intentional here.
- return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
- buffer_alignment_size);
- };
+ const auto calculate_upsampler_info_size =
+ [](const AudioCommon::AudioRendererParameter& params) {
+ constexpr u64 upsampler_info_size = 0x280;
+ // Yes, using the buffer size over info alignment size is intentional here.
+ return Common::AlignUp(upsampler_info_size *
+ (u64{params.submix_count} + params.sink_count),
+ buffer_alignment_size);
+ };
// Calculates the part of the size related to effect info.
- const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 effect_info_size = 0x2B0;
return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
};
// Calculates the part of the size related to audio sink info.
- const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 sink_info_size = 0x170;
return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
};
// Calculates the part of the size related to voice state info.
- const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 voice_state_size = 0x100;
const u64 additional_size = buffer_alignment_size - 1;
return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
@@ -513,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to performance statistics.
- const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
// Extra size value appended to the end of the calculation.
constexpr u64 appended = 128;
@@ -540,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size that relates to the audio command buffer.
- const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
- constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
+ const auto calculate_command_buffer_size =
+ [](const AudioCommon::AudioRendererParameter& params) {
+ constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
- if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
- constexpr u64 command_buffer_size = 0x18000;
+ if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
+ constexpr u64 command_buffer_size = 0x18000;
- return command_buffer_size + alignment;
- }
+ return command_buffer_size + alignment;
+ }
- // When the variadic command buffer is supported, this means
- // the command generator for the audio renderer can issue commands
- // that are (as one would expect), variable in size. So what we need to do
- // is determine the maximum possible size for a few command data structures
- // then multiply them by the amount of present commands indicated by the given
- // respective audio parameters.
+ // When the variadic command buffer is supported, this means
+ // the command generator for the audio renderer can issue commands
+ // that are (as one would expect), variable in size. So what we need to do
+ // is determine the maximum possible size for a few command data structures
+ // then multiply them by the amount of present commands indicated by the given
+ // respective audio parameters.
- constexpr u64 max_biquad_filters = 2;
- constexpr u64 max_mix_buffers = 24;
+ constexpr u64 max_biquad_filters = 2;
+ constexpr u64 max_mix_buffers = 24;
- constexpr u64 biquad_filter_command_size = 0x2C;
+ constexpr u64 biquad_filter_command_size = 0x2C;
- constexpr u64 depop_mix_command_size = 0x24;
- constexpr u64 depop_setup_command_size = 0x50;
+ constexpr u64 depop_mix_command_size = 0x24;
+ constexpr u64 depop_setup_command_size = 0x50;
- constexpr u64 effect_command_max_size = 0x540;
+ constexpr u64 effect_command_max_size = 0x540;
- constexpr u64 mix_command_size = 0x1C;
- constexpr u64 mix_ramp_command_size = 0x24;
- constexpr u64 mix_ramp_grouped_command_size = 0x13C;
+ constexpr u64 mix_command_size = 0x1C;
+ constexpr u64 mix_ramp_command_size = 0x24;
+ constexpr u64 mix_ramp_grouped_command_size = 0x13C;
- constexpr u64 perf_command_size = 0x28;
+ constexpr u64 perf_command_size = 0x28;
- constexpr u64 sink_command_size = 0x130;
+ constexpr u64 sink_command_size = 0x130;
- constexpr u64 submix_command_max_size =
- depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
+ constexpr u64 submix_command_max_size =
+ depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
- constexpr u64 volume_command_size = 0x1C;
- constexpr u64 volume_ramp_command_size = 0x20;
+ constexpr u64 volume_command_size = 0x1C;
+ constexpr u64 volume_ramp_command_size = 0x20;
- constexpr u64 voice_biquad_filter_command_size =
- biquad_filter_command_size * max_biquad_filters;
- constexpr u64 voice_data_command_size = 0x9C;
- const u64 voice_command_max_size =
- (params.splitter_count * depop_setup_command_size) +
- (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
- mix_ramp_grouped_command_size);
+ constexpr u64 voice_biquad_filter_command_size =
+ biquad_filter_command_size * max_biquad_filters;
+ constexpr u64 voice_data_command_size = 0x9C;
+ const u64 voice_command_max_size =
+ (params.splitter_count * depop_setup_command_size) +
+ (voice_data_command_size + voice_biquad_filter_command_size +
+ volume_ramp_command_size + mix_ramp_grouped_command_size);
- // Now calculate the individual elements that comprise the size and add them together.
- const u64 effect_commands_size = params.effect_count * effect_command_max_size;
+ // Now calculate the individual elements that comprise the size and add them together.
+ const u64 effect_commands_size = params.effect_count * effect_command_max_size;
- const u64 final_mix_commands_size =
- depop_mix_command_size + volume_command_size * max_mix_buffers;
+ const u64 final_mix_commands_size =
+ depop_mix_command_size + volume_command_size * max_mix_buffers;
- const u64 perf_commands_size =
- perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
+ const u64 perf_commands_size =
+ perf_command_size *
+ (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
- const u64 sink_commands_size = params.sink_count * sink_command_size;
+ const u64 sink_commands_size = params.sink_count * sink_command_size;
- const u64 splitter_commands_size =
- params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
+ const u64 splitter_commands_size =
+ params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
- const u64 submix_commands_size = params.submix_count * submix_command_max_size;
+ const u64 submix_commands_size = params.submix_count * submix_command_max_size;
- const u64 voice_commands_size = params.voice_count * voice_command_max_size;
+ const u64 voice_commands_size = params.voice_count * voice_command_max_size;
- return effect_commands_size + final_mix_commands_size + perf_commands_size +
- sink_commands_size + splitter_commands_size + submix_commands_size +
- voice_commands_size + alignment;
- };
+ return effect_commands_size + final_mix_commands_size + perf_commands_size +
+ sink_commands_size + splitter_commands_size + submix_commands_size +
+ voice_commands_size + alignment;
+ };
IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
+ const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
u64 size = 0;
size += calculate_mix_buffer_sizes(params);
@@ -678,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
+ const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index d19513cbb..f1d81602c 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -92,7 +92,7 @@ private:
if (performance) {
rb.Push<u64>(*performance);
}
- ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
+ ctx.WriteBuffer(samples);
}
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index def3410cc..174388445 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -84,7 +84,7 @@ void ProgressServiceBackend::FinishDownload(ResultCode result) {
void ProgressServiceBackend::SignalUpdate() const {
if (need_hle_lock) {
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock(HLE::g_hle_lock);
event.writable->Signal();
} else {
event.writable->Signal();
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index f589864ee..3b6f7498e 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -4,8 +4,8 @@
#include <fmt/ostream.h>
#include <httplib.h>
-#include <json.hpp>
#include <mbedtls/sha256.h>
+#include <nlohmann/json.hpp>
#include "common/hex_util.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
@@ -18,6 +18,7 @@
#include "core/hle/service/bcat/backend/boxcat.h"
#include "core/settings.h"
+namespace Service::BCAT {
namespace {
// Prevents conflicts with windows macro called CreateFile
@@ -30,10 +31,6 @@ bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
return dir->DeleteFile(name);
}
-} // Anonymous namespace
-
-namespace Service::BCAT {
-
constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
@@ -90,16 +87,14 @@ constexpr u32 PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30;
[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
-namespace {
-
std::string GetBINFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/launchparam.bin",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
std::string GetZIPFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/data.zip",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
// If the error is something the user should know about (build ID mismatch, bad client version),
@@ -201,7 +196,9 @@ private:
const std::string& content_type_name) {
if (client == nullptr) {
client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT);
- client->set_timeout_sec(timeout_seconds);
+ client->set_connection_timeout(timeout_seconds);
+ client->set_read_timeout(timeout_seconds);
+ client->set_write_timeout(timeout_seconds);
}
httplib::Headers headers{
@@ -210,8 +207,8 @@ private:
{std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
};
- if (FileUtil::Exists(path)) {
- FileUtil::IOFile file{path, "rb"};
+ if (Common::FS::Exists(path)) {
+ Common::FS::IOFile file{path, "rb"};
if (file.IsOpen()) {
std::vector<u8> bytes(file.GetSize());
file.ReadBytes(bytes.data(), bytes.size());
@@ -241,8 +238,8 @@ private:
return DownloadResult::InvalidContentType;
}
- FileUtil::CreateFullPath(path);
- FileUtil::IOFile file{path, "wb"};
+ Common::FS::CreateFullPath(path);
+ Common::FS::IOFile file{path, "wb"};
if (!file.IsOpen())
return DownloadResult::GeneralFSError;
if (!file.Resize(response->body.size()))
@@ -260,7 +257,7 @@ private:
return out;
}
- std::unique_ptr<httplib::Client> client;
+ std::unique_ptr<httplib::SSLClient> client;
std::string path;
u64 title_id;
u64 build_id;
@@ -295,7 +292,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
- FileUtil::Delete(zip_path);
+ Common::FS::Delete(zip_path);
}
HandleDownloadDisplayResult(applet_manager, res);
@@ -305,7 +302,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
progress.StartProcessingDataList();
- FileUtil::IOFile zip{zip_path, "rb"};
+ Common::FS::IOFile zip{zip_path, "rb"};
const auto size = zip.GetSize();
std::vector<u8> bytes(size);
if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
@@ -370,8 +367,7 @@ bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress)
std::thread([this, title, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress);
- })
- .detach();
+ }).detach();
return true;
}
@@ -382,8 +378,7 @@ bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
std::thread([this, title, name, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress, name);
- })
- .detach();
+ }).detach();
return true;
}
@@ -427,7 +422,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
- FileUtil::Delete(path);
+ Common::FS::Delete(path);
}
HandleDownloadDisplayResult(applet_manager, res);
@@ -435,7 +430,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
}
}
- FileUtil::IOFile bin{path, "rb"};
+ Common::FS::IOFile bin{path, "rb"};
const auto size = bin.GetSize();
std::vector<u8> bytes(size);
if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
@@ -450,13 +445,25 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
std::map<std::string, EventStatus>& games) {
httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)};
- client.set_timeout_sec(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_connection_timeout(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_read_timeout(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_write_timeout(static_cast<int>(TIMEOUT_SECONDS));
httplib::Headers headers{
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
};
+ if (!client.is_valid()) {
+ LOG_ERROR(Service_BCAT, "Client is invalid, going offline!");
+ return StatusResult::Offline;
+ }
+
+ if (!client.is_socket_open()) {
+ LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!");
+ return StatusResult::Offline;
+ }
+
const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
if (response == nullptr)
return StatusResult::Offline;
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 8bb2528c9..b31766212 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -14,6 +14,8 @@ BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module,
{0, &BCAT::CreateBcatService, "CreateBcatService"},
{1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
{2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
+ {3, nullptr, "CreateDeliveryCacheProgressService"},
+ {4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"},
};
// clang-format on
RegisterHandlers(functions);
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 7ada67130..68deb0600 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -8,6 +8,7 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
@@ -112,7 +113,7 @@ private:
void GetImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
- ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
+ ctx.WriteBuffer(impl);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -141,11 +142,15 @@ public:
{20301, nullptr, "RequestSuspendDeliveryTask"},
{20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
{20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
+ {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
{30100, &IBcatService::SetPassphrase, "SetPassphrase"},
+ {30101, nullptr, "Unknown"},
+ {30102, nullptr, "Unknown2"},
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
{30202, nullptr, "BlockDeliveryTask"},
{30203, nullptr, "UnblockDeliveryTask"},
+ {30210, nullptr, "SetDeliveryTaskTimer"},
{30300, nullptr, "RegisterSystemApplicationDeliveryTasks"},
{90100, nullptr, "EnumerateBackgroundDeliveryTask"},
{90200, nullptr, "GetDeliveryList"},
diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp
index 1c1ecdb60..fac6b2f9c 100644
--- a/src/core/hle/service/bpc/bpc.cpp
+++ b/src/core/hle/service/bpc/bpc.cpp
@@ -23,9 +23,14 @@ public:
{5, nullptr, "GetBoardPowerControlEvent"},
{6, nullptr, "GetSleepButtonState"},
{7, nullptr, "GetPowerEvent"},
- {8, nullptr, "Unknown1"},
- {9, nullptr, "Unknown2"},
- {10, nullptr, "Unknown3"},
+ {8, nullptr, "CreateWakeupTimer"},
+ {9, nullptr, "CancelWakeupTimer"},
+ {10, nullptr, "EnableWakeupTimerOnDevice"},
+ {11, nullptr, "CreateWakeupTimerEx"},
+ {12, nullptr, "GetLastEnabledWakeupTimerType"},
+ {13, nullptr, "CleanAllWakeupTimers"},
+ {14, nullptr, "Unknown"},
+ {15, nullptr, "Unknown2"},
};
// clang-format on
@@ -38,10 +43,11 @@ public:
explicit BPC_R() : ServiceFramework{"bpc:r"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetExternalRtcValue"},
- {1, nullptr, "SetExternalRtcValue"},
- {2, nullptr, "ReadExternalRtcResetFlag"},
- {3, nullptr, "ClearExternalRtcResetFlag"},
+ {0, nullptr, "GetRtcTime"},
+ {1, nullptr, "SetRtcTime"},
+ {2, nullptr, "GetRtcResetDetected"},
+ {3, nullptr, "ClearRtcResetDetected"},
+ {4, nullptr, "SetUpRtcResetOnShutdown"},
};
// clang-format on
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index 40a06c9fd..d4f0dd1ab 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
@@ -58,102 +59,103 @@ public:
{1, nullptr, "InitializeBluetooth"},
{2, nullptr, "EnableBluetooth"},
{3, nullptr, "DisableBluetooth"},
- {4, nullptr, "CleanupBluetooth"},
+ {4, nullptr, "FinalizeBluetooth"},
{5, nullptr, "GetAdapterProperties"},
{6, nullptr, "GetAdapterProperty"},
{7, nullptr, "SetAdapterProperty"},
- {8, nullptr, "StartDiscovery"},
- {9, nullptr, "CancelDiscovery"},
+ {8, nullptr, "StartInquiry"},
+ {9, nullptr, "StopInquiry"},
{10, nullptr, "CreateBond"},
{11, nullptr, "RemoveBond"},
{12, nullptr, "CancelBond"},
- {13, nullptr, "PinReply"},
- {14, nullptr, "SspReply"},
+ {13, nullptr, "RespondToPinRequest"},
+ {14, nullptr, "RespondToSspRequest"},
{15, nullptr, "GetEventInfo"},
{16, nullptr, "InitializeHid"},
- {17, nullptr, "HidConnect"},
- {18, nullptr, "HidDisconnect"},
- {19, nullptr, "HidSendData"},
- {20, nullptr, "HidSendData2"},
- {21, nullptr, "HidSetReport"},
- {22, nullptr, "HidGetReport"},
- {23, nullptr, "HidWakeController"},
- {24, nullptr, "HidAddPairedDevice"},
- {25, nullptr, "HidGetPairedDevice"},
- {26, nullptr, "CleanupHid"},
- {27, nullptr, "HidGetEventInfo"},
- {28, nullptr, "ExtSetTsi"},
- {29, nullptr, "ExtSetBurstMode"},
- {30, nullptr, "ExtSetZeroRetran"},
- {31, nullptr, "ExtSetMcMode"},
- {32, nullptr, "ExtStartLlrMode"},
- {33, nullptr, "ExtExitLlrMode"},
- {34, nullptr, "ExtSetRadio"},
- {35, nullptr, "ExtSetVisibility"},
- {36, nullptr, "ExtSetTbfcScan"},
+ {17, nullptr, "OpenHidConnection"},
+ {18, nullptr, "CloseHidConnection"},
+ {19, nullptr, "WriteHidData"},
+ {20, nullptr, "WriteHidData2"},
+ {21, nullptr, "SetHidReport"},
+ {22, nullptr, "GetHidReport"},
+ {23, nullptr, "TriggerConnection"},
+ {24, nullptr, "AddPairedDeviceInfo"},
+ {25, nullptr, "GetPairedDeviceInfo"},
+ {26, nullptr, "FinalizeHid"},
+ {27, nullptr, "GetHidEventInfo"},
+ {28, nullptr, "SetTsi"},
+ {29, nullptr, "EnableBurstMode"},
+ {30, nullptr, "SetZeroRetransmission"},
+ {31, nullptr, "EnableMcMode"},
+ {32, nullptr, "EnableLlrScan"},
+ {33, nullptr, "DisableLlrScan"},
+ {34, nullptr, "EnableRadio"},
+ {35, nullptr, "SetVisibility"},
+ {36, nullptr, "EnableTbfcScan"},
{37, nullptr, "RegisterHidReportEvent"},
- {38, nullptr, "HidGetReportEventInfo"},
+ {38, nullptr, "GetHidReportEventInfo"},
{39, nullptr, "GetLatestPlr"},
- {40, nullptr, "ExtGetPendingConnections"},
+ {40, nullptr, "GetPendingConnections"},
{41, nullptr, "GetChannelMap"},
- {42, nullptr, "EnableBluetoothBoostSetting"},
- {43, nullptr, "IsBluetoothBoostSettingEnabled"},
- {44, nullptr, "EnableBluetoothAfhSetting"},
- {45, nullptr, "IsBluetoothAfhSettingEnabled"},
- {46, nullptr, "InitializeBluetoothLe"},
- {47, nullptr, "EnableBluetoothLe"},
- {48, nullptr, "DisableBluetoothLe"},
- {49, nullptr, "CleanupBluetoothLe"},
- {50, nullptr, "SetLeVisibility"},
- {51, nullptr, "SetLeConnectionParameter"},
- {52, nullptr, "SetLeDefaultConnectionParameter"},
- {53, nullptr, "SetLeAdvertiseData"},
- {54, nullptr, "SetLeAdvertiseParameter"},
- {55, nullptr, "StartLeScan"},
- {56, nullptr, "StopLeScan"},
- {57, nullptr, "AddLeScanFilterCondition"},
- {58, nullptr, "DeleteLeScanFilterCondition"},
- {59, nullptr, "DeleteLeScanFilter"},
- {60, nullptr, "ClearLeScanFilters"},
- {61, nullptr, "EnableLeScanFilter"},
- {62, nullptr, "RegisterLeClient"},
- {63, nullptr, "UnregisterLeClient"},
- {64, nullptr, "UnregisterLeClientAll"},
- {65, nullptr, "LeClientConnect"},
- {66, nullptr, "LeClientCancelConnection"},
- {67, nullptr, "LeClientDisconnect"},
- {68, nullptr, "LeClientGetAttributes"},
- {69, nullptr, "LeClientDiscoverService"},
- {70, nullptr, "LeClientConfigureMtu"},
- {71, nullptr, "RegisterLeServer"},
- {72, nullptr, "UnregisterLeServer"},
- {73, nullptr, "LeServerConnect"},
- {74, nullptr, "LeServerDisconnect"},
- {75, nullptr, "CreateLeService"},
- {76, nullptr, "StartLeService"},
- {77, nullptr, "AddLeCharacteristic"},
- {78, nullptr, "AddLeDescriptor"},
- {79, nullptr, "GetLeCoreEventInfo"},
- {80, nullptr, "LeGetFirstCharacteristic"},
- {81, nullptr, "LeGetNextCharacteristic"},
- {82, nullptr, "LeGetFirstDescriptor"},
- {83, nullptr, "LeGetNextDescriptor"},
- {84, nullptr, "RegisterLeCoreDataPath"},
- {85, nullptr, "UnregisterLeCoreDataPath"},
- {86, nullptr, "RegisterLeHidDataPath"},
- {87, nullptr, "UnregisterLeHidDataPath"},
- {88, nullptr, "RegisterLeDataPath"},
- {89, nullptr, "UnregisterLeDataPath"},
- {90, nullptr, "LeClientReadCharacteristic"},
- {91, nullptr, "LeClientReadDescriptor"},
- {92, nullptr, "LeClientWriteCharacteristic"},
- {93, nullptr, "LeClientWriteDescriptor"},
- {94, nullptr, "LeClientRegisterNotification"},
- {95, nullptr, "LeClientDeregisterNotification"},
+ {42, nullptr, "EnableTxPowerBoostSetting"},
+ {43, nullptr, "IsTxPowerBoostSettingEnabled"},
+ {44, nullptr, "EnableAfhSetting"},
+ {45, nullptr, "IsAfhSettingEnabled"},
+ {46, nullptr, "InitializeBle"},
+ {47, nullptr, "EnableBle"},
+ {48, nullptr, "DisableBle"},
+ {49, nullptr, "FinalizeBle"},
+ {50, nullptr, "SetBleVisibility"},
+ {51, nullptr, "SetBleConnectionParameter"},
+ {52, nullptr, "SetBleDefaultConnectionParameter"},
+ {53, nullptr, "SetBleAdvertiseData"},
+ {54, nullptr, "SetBleAdvertiseParameter"},
+ {55, nullptr, "StartBleScan"},
+ {56, nullptr, "StopBleScan"},
+ {57, nullptr, "AddBleScanFilterCondition"},
+ {58, nullptr, "DeleteBleScanFilterCondition"},
+ {59, nullptr, "DeleteBleScanFilter"},
+ {60, nullptr, "ClearBleScanFilters"},
+ {61, nullptr, "EnableBleScanFilter"},
+ {62, nullptr, "RegisterGattClient"},
+ {63, nullptr, "UnregisterGattClient"},
+ {64, nullptr, "UnregisterAllGattClients"},
+ {65, nullptr, "ConnectGattServer"},
+ {66, nullptr, "CancelConnectGattServer"},
+ {67, nullptr, "DisconnectGattServer"},
+ {68, nullptr, "GetGattAttribute"},
+ {69, nullptr, "GetGattService"},
+ {70, nullptr, "ConfigureAttMtu"},
+ {71, nullptr, "RegisterGattServer"},
+ {72, nullptr, "UnregisterGattServer"},
+ {73, nullptr, "ConnectGattClient"},
+ {74, nullptr, "DisconnectGattClient"},
+ {75, nullptr, "AddGattService"},
+ {76, nullptr, "EnableGattService"},
+ {77, nullptr, "AddGattCharacteristic"},
+ {78, nullptr, "AddGattDescriptor"},
+ {79, nullptr, "GetBleManagedEventInfo"},
+ {80, nullptr, "GetGattFirstCharacteristic"},
+ {81, nullptr, "GetGattNextCharacteristic"},
+ {82, nullptr, "GetGattFirstDescriptor"},
+ {83, nullptr, "GetGattNextDescriptor"},
+ {84, nullptr, "RegisterGattManagedDataPath"},
+ {85, nullptr, "UnregisterGattManagedDataPath"},
+ {86, nullptr, "RegisterGattHidDataPath"},
+ {87, nullptr, "UnregisterGattHidDataPath"},
+ {88, nullptr, "RegisterGattDataPath"},
+ {89, nullptr, "UnregisterGattDataPath"},
+ {90, nullptr, "ReadGattCharacteristic"},
+ {91, nullptr, "ReadGattDescriptor"},
+ {92, nullptr, "WriteGattCharacteristic"},
+ {93, nullptr, "WriteGattDescriptor"},
+ {94, nullptr, "RegisterGattNotification"},
+ {95, nullptr, "UnregisterGattNotification"},
{96, nullptr, "GetLeHidEventInfo"},
{97, nullptr, "RegisterBleHidEvent"},
- {98, nullptr, "SetLeScanParameter"},
- {256, nullptr, "GetIsManufacturingMode"},
+ {98, nullptr, "SetBleScanParameter"},
+ {99, nullptr, "MoveToSecondaryPiconet"},
+ {256, nullptr, "IsManufacturingMode"},
{257, nullptr, "EmulateBluetoothCrash"},
{258, nullptr, "GetBleChannelMap"},
};
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 251b3c9df..c8f8ddbd5 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -5,6 +5,7 @@
#include <memory>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
@@ -132,66 +133,71 @@ public:
explicit BTM() : ServiceFramework{"btm"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "RegisterSystemEventForConnectedDeviceCondition"},
- {3, nullptr, "Unknown3"},
- {4, nullptr, "Unknown4"},
- {5, nullptr, "Unknown5"},
- {6, nullptr, "Unknown6"},
- {7, nullptr, "Unknown7"},
- {8, nullptr, "RegisterSystemEventForRegisteredDeviceInfo"},
- {9, nullptr, "Unknown8"},
- {10, nullptr, "Unknown9"},
- {11, nullptr, "Unknown10"},
- {12, nullptr, "Unknown11"},
- {13, nullptr, "Unknown12"},
+ {0, nullptr, "GetState"},
+ {1, nullptr, "GetHostDeviceProperty"},
+ {2, nullptr, "AcquireDeviceConditionEvent"},
+ {3, nullptr, "GetDeviceCondition"},
+ {4, nullptr, "SetBurstMode"},
+ {5, nullptr, "SetSlotMode"},
+ {6, nullptr, "SetBluetoothMode"},
+ {7, nullptr, "SetWlanMode"},
+ {8, nullptr, "AcquireDeviceInfoEvent"},
+ {9, nullptr, "GetDeviceInfo"},
+ {10, nullptr, "AddDeviceInfo"},
+ {11, nullptr, "RemoveDeviceInfo"},
+ {12, nullptr, "IncreaseDeviceInfoOrder"},
+ {13, nullptr, "LlrNotify"},
{14, nullptr, "EnableRadio"},
{15, nullptr, "DisableRadio"},
- {16, nullptr, "Unknown13"},
- {17, nullptr, "Unknown14"},
- {18, nullptr, "Unknown15"},
- {19, nullptr, "Unknown16"},
- {20, nullptr, "Unknown17"},
- {21, nullptr, "Unknown18"},
- {22, nullptr, "Unknown19"},
- {23, nullptr, "Unknown20"},
- {24, nullptr, "Unknown21"},
- {25, nullptr, "Unknown22"},
- {26, nullptr, "Unknown23"},
- {27, nullptr, "Unknown24"},
- {28, nullptr, "Unknown25"},
- {29, nullptr, "Unknown26"},
- {30, nullptr, "Unknown27"},
- {31, nullptr, "Unknown28"},
- {32, nullptr, "Unknown29"},
- {33, nullptr, "Unknown30"},
- {34, nullptr, "Unknown31"},
- {35, nullptr, "Unknown32"},
- {36, nullptr, "Unknown33"},
- {37, nullptr, "Unknown34"},
- {38, nullptr, "Unknown35"},
- {39, nullptr, "Unknown36"},
- {40, nullptr, "Unknown37"},
- {41, nullptr, "Unknown38"},
- {42, nullptr, "Unknown39"},
- {43, nullptr, "Unknown40"},
- {44, nullptr, "Unknown41"},
- {45, nullptr, "Unknown42"},
- {46, nullptr, "Unknown43"},
- {47, nullptr, "Unknown44"},
- {48, nullptr, "Unknown45"},
- {49, nullptr, "Unknown46"},
- {50, nullptr, "Unknown47"},
- {51, nullptr, "Unknown48"},
- {52, nullptr, "Unknown49"},
- {53, nullptr, "Unknown50"},
- {54, nullptr, "Unknown51"},
- {55, nullptr, "Unknown52"},
- {56, nullptr, "Unknown53"},
- {57, nullptr, "Unknown54"},
- {58, nullptr, "Unknown55"},
- {59, nullptr, "Unknown56"},
+ {16, nullptr, "HidDisconnect"},
+ {17, nullptr, "HidSetRetransmissionMode"},
+ {18, nullptr, "AcquireAwakeReqEvent"},
+ {19, nullptr, "AcquireLlrStateEvent"},
+ {20, nullptr, "IsLlrStarted"},
+ {21, nullptr, "EnableSlotSaving"},
+ {22, nullptr, "ProtectDeviceInfo"},
+ {23, nullptr, "AcquireBleScanEvent"},
+ {24, nullptr, "GetBleScanParameterGeneral"},
+ {25, nullptr, "GetBleScanParameterSmartDevice"},
+ {26, nullptr, "StartBleScanForGeneral"},
+ {27, nullptr, "StopBleScanForGeneral"},
+ {28, nullptr, "GetBleScanResultsForGeneral"},
+ {29, nullptr, "StartBleScanForPairedDevice"},
+ {30, nullptr, "StopBleScanForPairedDevice"},
+ {31, nullptr, "StartBleScanForSmartDevice"},
+ {32, nullptr, "StopBleScanForSmartDevice"},
+ {33, nullptr, "GetBleScanResultsForSmartDevice"},
+ {34, nullptr, "AcquireBleConnectionEvent"},
+ {35, nullptr, "BleConnect"},
+ {36, nullptr, "BleOverrideConnection"},
+ {37, nullptr, "BleDisconnect"},
+ {38, nullptr, "BleGetConnectionState"},
+ {39, nullptr, "BleGetGattClientConditionList"},
+ {40, nullptr, "AcquireBlePairingEvent"},
+ {41, nullptr, "BlePairDevice"},
+ {42, nullptr, "BleUnpairDeviceOnBoth"},
+ {43, nullptr, "BleUnpairDevice"},
+ {44, nullptr, "BleGetPairedAddresses"},
+ {45, nullptr, "AcquireBleServiceDiscoveryEvent"},
+ {46, nullptr, "GetGattServices"},
+ {47, nullptr, "GetGattService"},
+ {48, nullptr, "GetGattIncludedServices"},
+ {49, nullptr, "GetBelongingService"},
+ {50, nullptr, "GetGattCharacteristics"},
+ {51, nullptr, "GetGattDescriptors"},
+ {52, nullptr, "AcquireBleMtuConfigEvent"},
+ {53, nullptr, "ConfigureBleMtu"},
+ {54, nullptr, "GetBleMtu"},
+ {55, nullptr, "RegisterBleGattDataPath"},
+ {56, nullptr, "UnregisterBleGattDataPath"},
+ {57, nullptr, "RegisterAppletResourceUserId"},
+ {58, nullptr, "UnregisterAppletResourceUserId"},
+ {59, nullptr, "SetAppletResourceUserId"},
+ {60, nullptr, "Unknown60"},
+ {61, nullptr, "Unknown61"},
+ {62, nullptr, "Unknown62"},
+ {63, nullptr, "Unknown63"},
+ {64, nullptr, "Unknown64"},
};
// clang-format on
@@ -204,19 +210,19 @@ public:
explicit BTM_DBG() : ServiceFramework{"btm:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "RegisterSystemEventForDiscovery"},
- {1, nullptr, "Unknown1"},
- {2, nullptr, "Unknown2"},
- {3, nullptr, "Unknown3"},
- {4, nullptr, "Unknown4"},
- {5, nullptr, "Unknown5"},
- {6, nullptr, "Unknown6"},
- {7, nullptr, "Unknown7"},
- {8, nullptr, "Unknown8"},
- {9, nullptr, "Unknown9"},
- {10, nullptr, "Unknown10"},
- {11, nullptr, "Unknown11"},
- {12, nullptr, "Unknown11"},
+ {0, nullptr, "AcquireDiscoveryEvent"},
+ {1, nullptr, "StartDiscovery"},
+ {2, nullptr, "CancelDiscovery"},
+ {3, nullptr, "GetDeviceProperty"},
+ {4, nullptr, "CreateBond"},
+ {5, nullptr, "CancelBond"},
+ {6, nullptr, "SetTsiMode"},
+ {7, nullptr, "GeneralTest"},
+ {8, nullptr, "HidConnect"},
+ {9, nullptr, "GeneralGet"},
+ {10, nullptr, "GetGattClientDisconnectionReason"},
+ {11, nullptr, "GetBleConnectionParameter"},
+ {12, nullptr, "GetBleConnectionParameterRequest"},
};
// clang-format on
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 26c8a7081..ba5749b84 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -1,4 +1,4 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index fc70a4c27..b8c67b6e2 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -1,4 +1,4 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,73 +12,79 @@ class ServiceManager;
namespace Service::Capture {
-enum AlbumImageOrientation {
+enum class AlbumImageOrientation {
Orientation0 = 0,
Orientation1 = 1,
Orientation2 = 2,
Orientation3 = 3,
};
-enum AlbumReportOption {
+enum class AlbumReportOption {
Disable = 0,
Enable = 1,
};
-enum ContentType : u8 {
+enum class ContentType : u8 {
Screenshot = 0,
Movie = 1,
ExtraMovie = 3,
};
-enum AlbumStorage : u8 {
+enum class AlbumStorage : u8 {
NAND = 0,
SD = 1,
};
struct AlbumFileDateTime {
- u16 year;
- u8 month;
- u8 day;
- u8 hour;
- u8 minute;
- u8 second;
- u8 uid;
+ s16 year{};
+ s8 month{};
+ s8 day{};
+ s8 hour{};
+ s8 minute{};
+ s8 second{};
+ s8 uid{};
};
+static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
struct AlbumEntry {
- u64 size;
- u64 application_id;
- AlbumFileDateTime datetime;
- AlbumStorage storage;
- ContentType content;
- u8 padding[6];
+ u64 size{};
+ u64 application_id{};
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(6);
};
+static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
struct AlbumFileEntry {
- u64 size;
- u64 hash;
- AlbumFileDateTime datetime;
- AlbumStorage storage;
- ContentType content;
- u8 padding[5];
- u8 unknown;
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{1}; // Set to 1 on official SW
};
+static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
struct ApplicationAlbumEntry {
- u64 size;
- u64 hash;
- AlbumFileDateTime datetime;
- AlbumStorage storage;
- ContentType content;
- u8 padding[5];
- u8 unknown;
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{1}; // Set to 1 on official SW
};
+static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
struct ApplicationAlbumFileEntry {
- ApplicationAlbumEntry entry;
- AlbumFileDateTime datetime;
- u64 unknown;
+ ApplicationAlbumEntry entry{};
+ AlbumFileDateTime datetime{};
+ u64 unknown{};
};
+static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
+ "ApplicationAlbumFileEntry has incorrect size.");
/// Registers all Capture services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& sm);
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 88a3fdc05..a0a3b2ae3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 8de832491..cb93aad5b 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index ea6452ffa..a0ee116fa 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -1,7 +1,9 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/caps/caps_c.h"
namespace Service::Capture {
@@ -47,7 +49,7 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") {
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
- {33, nullptr, "Unknown33"},
+ {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,4 +74,16 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") {
CAPS_C::~CAPS_C() = default;
+void CAPS_C::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index d07cdb441..b110301d4 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -16,6 +16,9 @@ class CAPS_C final : public ServiceFramework<CAPS_C> {
public:
explicit CAPS_C();
~CAPS_C() override;
+
+private:
+ void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index d01a8a58e..822ee96c8 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index 9ba372f7a..ac3e929ca 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index eaa3a7494..24dc716e7 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index e258a6925..450686e4f 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 2b4c2d808..e386470f7 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -1,7 +1,9 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/caps/caps_su.h"
namespace Service::Capture {
@@ -9,8 +11,11 @@ namespace Service::Capture {
CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") {
// clang-format off
static const FunctionInfo functions[] = {
+ {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
{201, nullptr, "SaveScreenShot"},
{203, nullptr, "SaveScreenShotEx0"},
+ {205, nullptr, "SaveScreenShotEx1"},
+ {210, nullptr, "SaveScreenShotEx2"},
};
// clang-format on
@@ -19,4 +24,16 @@ CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") {
CAPS_SU::~CAPS_SU() = default;
+void CAPS_SU::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index cb11f7c9a..62c9603a9 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -16,6 +16,9 @@ class CAPS_SU final : public ServiceFramework<CAPS_SU> {
public:
explicit CAPS_SU();
~CAPS_SU() override;
+
+private:
+ void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index 78bab6ed8..f9479bdb3 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -31,8 +31,7 @@ public:
CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
// clang-format off
static const FunctionInfo functions[] = {
- {31, nullptr, "GetShimLibraryVersion"},
- {32, nullptr, "SetShimLibraryVersion"},
+ {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
{102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
{103, nullptr, "DeleteAlbumContentsFileForApplication"},
{104, nullptr, "GetAlbumContentsFileSizeForApplication"},
@@ -42,7 +41,7 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
{130, nullptr, "PrecheckToCreateContentsForApplication"},
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
- {142, nullptr, "GetAlbumFileList3AaeAruid"},
+ {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
};
@@ -53,24 +52,49 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
CAPS_U::~CAPS_U() = default;
+void CAPS_U::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx) {
// Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
// u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
// output entries (which is copied to a s32 by official SW).
IPC::RequestParser rp{ctx};
- [[maybe_unused]] const auto application_album_file_entries = rp.PopRaw<std::array<u8, 0x30>>();
- const auto pid = rp.Pop<s32>();
- const auto content_type = rp.PopRaw<ContentType>();
- [[maybe_unused]] const auto start_datetime = rp.PopRaw<AlbumFileDateTime>();
- [[maybe_unused]] const auto end_datetime = rp.PopRaw<AlbumFileDateTime>();
- const auto applet_resource_user_id = rp.Pop<u64>();
- LOG_WARNING(Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
- content_type, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
+ const auto pid{rp.Pop<s32>()};
+ const auto content_type{rp.PopEnum<ContentType>()};
+ const auto start_posix_time{rp.Pop<s64>()};
+ const auto end_posix_time{rp.Pop<s64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ // TODO: Update this when we implement the album.
+ // Currently we do not have a method of accessing album entries, set this to 0 for now.
+ constexpr u32 total_entries_1{};
+ constexpr u32 total_entries_2{};
+
+ LOG_WARNING(
+ Service_Capture,
+ "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
+ "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
+ pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
+ total_entries_1, total_entries_2);
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<s32>(0);
+ rb.Push(total_entries_1);
+ rb.Push(total_entries_2);
+}
+
+void CAPS_U::GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx) {
+ GetAlbumContentsFileListForApplication(ctx);
}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e6e0716ff..4b80f3156 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -18,7 +18,9 @@ public:
~CAPS_U() override;
private:
+ void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
void GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx);
+ void GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index df00ae625..c2737a365 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -4,6 +4,7 @@
#include "core/crypto/key_manager.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/es/es.h"
#include "core/hle/service/service.h"
namespace Service::ES {
@@ -26,8 +27,8 @@ public:
{8, &ETicket::GetTitleKey, "GetTitleKey"},
{9, &ETicket::CountCommonTicket, "CountCommonTicket"},
{10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
- {11, &ETicket::ListCommonTicket, "ListCommonTicket"},
- {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
+ {11, &ETicket::ListCommonTicketRightsIds, "ListCommonTicketRightsIds"},
+ {12, &ETicket::ListPersonalizedTicketRightsIds, "ListPersonalizedTicketRightsIds"},
{13, nullptr, "ListMissingPersonalizedTicket"},
{14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
{15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
@@ -54,7 +55,46 @@ public:
{36, nullptr, "DeleteAllInactiveELicenseRequiredPersonalizedTicket"},
{37, nullptr, "OwnTicket2"},
{38, nullptr, "OwnTicket3"},
+ {501, nullptr, "Unknown501"},
+ {502, nullptr, "Unknown502"},
{503, nullptr, "GetTitleKey"},
+ {504, nullptr, "Unknown504"},
+ {508, nullptr, "Unknown508"},
+ {509, nullptr, "Unknown509"},
+ {510, nullptr, "Unknown510"},
+ {511, nullptr, "Unknown511"},
+ {1001, nullptr, "Unknown1001"},
+ {1002, nullptr, "Unknown1001"},
+ {1003, nullptr, "Unknown1003"},
+ {1004, nullptr, "Unknown1004"},
+ {1005, nullptr, "Unknown1005"},
+ {1006, nullptr, "Unknown1006"},
+ {1007, nullptr, "Unknown1007"},
+ {1009, nullptr, "Unknown1009"},
+ {1010, nullptr, "Unknown1010"},
+ {1011, nullptr, "Unknown1011"},
+ {1012, nullptr, "Unknown1012"},
+ {1013, nullptr, "Unknown1013"},
+ {1014, nullptr, "Unknown1014"},
+ {1015, nullptr, "Unknown1015"},
+ {1016, nullptr, "Unknown1016"},
+ {1017, nullptr, "Unknown1017"},
+ {1018, nullptr, "Unknown1018"},
+ {1019, nullptr, "Unknown1019"},
+ {1020, nullptr, "Unknown1020"},
+ {1021, nullptr, "Unknown1021"},
+ {1501, nullptr, "Unknown1501"},
+ {1502, nullptr, "Unknown1502"},
+ {1503, nullptr, "Unknown1503"},
+ {1504, nullptr, "Unknown1504"},
+ {1505, nullptr, "Unknown1505"},
+ {2000, nullptr, "Unknown2000"},
+ {2001, nullptr, "Unknown2001"},
+ {2100, nullptr, "Unknown2100"},
+ {2501, nullptr, "Unknown2501"},
+ {2502, nullptr, "Unknown2502"},
+ {3001, nullptr, "Unknown3001"},
+ {3002, nullptr, "Unknown3002"},
};
// clang-format on
RegisterHandlers(functions);
@@ -76,7 +116,6 @@ private:
}
void ImportTicket(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
const auto ticket = ctx.ReadBuffer();
const auto cert = ctx.ReadBuffer(1);
@@ -121,7 +160,7 @@ private:
return;
}
- ctx.WriteBuffer(key.data(), key.size());
+ ctx.WriteBuffer(key);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -147,7 +186,7 @@ private:
rb.Push<u32>(count);
}
- void ListCommonTicket(Kernel::HLERequestContext& ctx) {
+ void ListCommonTicketRightsIds(Kernel::HLERequestContext& ctx) {
u32 out_entries;
if (keys.GetCommonTickets().empty())
out_entries = 0;
@@ -170,7 +209,7 @@ private:
rb.Push<u32>(out_entries);
}
- void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
+ void ListPersonalizedTicketRightsIds(Kernel::HLERequestContext& ctx) {
u32 out_entries;
if (keys.GetPersonalizedTickets().empty())
out_entries = 0;
@@ -263,7 +302,7 @@ private:
rb.Push<u64>(write_size);
}
- Core::Crypto::KeyManager keys;
+ Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp
index 2df30acee..0d6d244f4 100644
--- a/src/core/hle/service/eupld/eupld.cpp
+++ b/src/core/hle/service/eupld/eupld.cpp
@@ -19,6 +19,7 @@ public:
{1, nullptr, "ImportCrt"},
{2, nullptr, "ImportPki"},
{3, nullptr, "SetAutoUpload"},
+ {4, nullptr, "GetAutoUpload"},
};
// clang-format on
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 102017d73..2e53cae5b 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -36,7 +36,7 @@ constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
std::string_view dir_name_) {
- std::string dir_name(FileUtil::SanitizePath(dir_name_));
+ std::string dir_name(Common::FS::SanitizePath(dir_name_));
if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\")
return base;
@@ -53,9 +53,13 @@ std::string VfsDirectoryServiceWrapper::GetName() const {
}
ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- auto file = dir->CreateFile(FileUtil::GetFilename(path));
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ // dir can be nullptr if path contains subdirectories, create those prior to creating the file.
+ if (dir == nullptr) {
+ dir = backing->CreateSubdirectory(Common::FS::GetParentPath(path));
+ }
+ auto file = dir->CreateFile(Common::FS::GetFilename(path));
if (file == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
@@ -68,17 +72,17 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
}
ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
+ std::string path(Common::FS::SanitizePath(path_));
if (path.empty()) {
// TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
return RESULT_SUCCESS;
}
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) {
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- if (!dir->DeleteFile(FileUtil::GetFilename(path))) {
+ if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -87,11 +91,12 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
}
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty())
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) {
dir = backing;
- auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path));
+ }
+ auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path));
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
@@ -100,9 +105,9 @@ ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_)
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) {
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -110,9 +115,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_)
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) {
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -120,10 +125,10 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str
}
ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
- const std::string sanitized_path(FileUtil::SanitizePath(path));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(sanitized_path));
+ const std::string sanitized_path(Common::FS::SanitizePath(path));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path));
- if (!dir->CleanSubdirectoryRecursive(FileUtil::GetFilename(sanitized_path))) {
+ if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -133,14 +138,14 @@ ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::stri
ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
const std::string& dest_path_) const {
- std::string src_path(FileUtil::SanitizePath(src_path_));
- std::string dest_path(FileUtil::SanitizePath(dest_path_));
+ std::string src_path(Common::FS::SanitizePath(src_path_));
+ std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = backing->GetFileRelative(src_path);
- if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
+ if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- if (!src->Rename(FileUtil::GetFilename(dest_path))) {
+ if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -158,7 +163,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
"Could not write all of the bytes but everything else has succeded.");
- if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) {
+ if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -168,14 +173,14 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
const std::string& dest_path_) const {
- std::string src_path(FileUtil::SanitizePath(src_path_));
- std::string dest_path(FileUtil::SanitizePath(dest_path_));
+ std::string src_path(Common::FS::SanitizePath(src_path_));
+ std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = GetDirectoryRelativeWrapped(backing, src_path);
- if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
+ if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- if (!src->Rename(FileUtil::GetFilename(dest_path))) {
+ if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -194,7 +199,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_pa
ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
FileSys::Mode mode) const {
- const std::string path(FileUtil::SanitizePath(path_));
+ const std::string path(Common::FS::SanitizePath(path_));
std::string_view npath = path;
while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
npath.remove_prefix(1);
@@ -214,7 +219,7 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::
}
ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) {
- std::string path(FileUtil::SanitizePath(path_));
+ std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, path);
if (dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
@@ -225,11 +230,11 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s
ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- auto filename = FileUtil::GetFilename(path);
+ auto filename = Common::FS::GetFilename(path);
// TODO(Subv): Some games use the '/' path, find out what this means.
if (filename.empty())
return MakeResult(FileSys::EntryType::Directory);
@@ -307,7 +312,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
}
ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const {
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const {
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}",
static_cast<u8>(space), save_struct.DebugInfo());
@@ -319,15 +324,15 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
}
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const {
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
- static_cast<u8>(space), descriptor.DebugInfo());
+ static_cast<u8>(space), attribute.DebugInfo());
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Open(space, descriptor);
+ return save_data_factory->Open(space, attribute);
}
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
@@ -375,7 +380,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- auto part = bis_factory->OpenPartitionStorage(id);
+ auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem());
if (part == nullptr) {
return FileSys::ERROR_INVALID_ARGUMENT;
}
@@ -450,8 +455,11 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy
const auto res = system.GetAppLoader().ReadControlData(nacp);
if (res != Loader::ResultStatus::Success) {
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
- auto [nacp_unique, discard] = pm.GetControlMetadata();
+ const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto metadata = pm.GetControlMetadata();
+ const auto& nacp_unique = metadata.first;
if (nacp_unique != nullptr) {
new_size = {nacp_unique->GetDefaultNormalSaveSize(),
@@ -690,13 +698,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
sdmc_factory = nullptr;
}
- auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
+ auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir),
FileSys::Mode::ReadWrite);
- auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
+ auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
- auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
+ auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
FileSys::Mode::ReadWrite);
- auto dump_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
+ auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
FileSys::Mode::ReadWrite);
if (bis_factory == nullptr) {
@@ -722,7 +730,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
void InstallInterfaces(Core::System& system) {
std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
- std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter())
+ std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetContentProvider(),
+ system.GetReporter())
->InstallAsService(system.ServiceManager());
}
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 1b0a6a949..6dbbf0b2b 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -31,7 +31,7 @@ enum class SaveDataSpaceId : u8;
enum class SaveDataType : u8;
enum class StorageId : u8;
-struct SaveDataDescriptor;
+struct SaveDataAttribute;
struct SaveDataSize;
} // namespace FileSys
@@ -69,9 +69,9 @@ public:
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualDir> CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
ResultVal<FileSys::VirtualDir> OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
ResultVal<FileSys::VirtualDir> OpenSDMC() const;
ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index e6811d5b5..031c6dbf6 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -316,8 +316,8 @@ public:
{8, &IFileSystem::OpenFile, "OpenFile"},
{9, &IFileSystem::OpenDirectory, "OpenDirectory"},
{10, &IFileSystem::Commit, "Commit"},
- {11, nullptr, "GetFreeSpaceSize"},
- {12, nullptr, "GetTotalSpaceSize"},
+ {11, &IFileSystem::GetFreeSpaceSize, "GetFreeSpaceSize"},
+ {12, &IFileSystem::GetTotalSpaceSize, "GetTotalSpaceSize"},
{13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
{14, nullptr, "GetFileTimeStampRaw"},
{15, nullptr, "QueryEntry"},
@@ -575,6 +575,7 @@ private:
0,
user_id->GetSize(),
{},
+ {},
});
continue;
@@ -595,6 +596,7 @@ private:
stoull_be(title_id->GetName()),
title_id->GetSize(),
{},
+ {},
});
}
}
@@ -619,6 +621,7 @@ private:
stoull_be(title_id->GetName()),
title_id->GetSize(),
{},
+ {},
});
}
}
@@ -647,8 +650,10 @@ private:
u64 next_entry_index = 0;
};
-FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
- : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) {
+FSP_SRV::FSP_SRV(FileSystemController& fsc_, const FileSys::ContentProvider& content_provider_,
+ const Core::Reporter& reporter_)
+ : ServiceFramework("fsp-srv"), fsc(fsc_), content_provider{content_provider_},
+ reporter(reporter_) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenFileSystem"},
@@ -693,13 +698,15 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
{67, nullptr, "FindSaveDataWithFilter"},
{68, nullptr, "OpenSaveDataInfoReaderBySaveDataFilter"},
{69, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataAttribute"},
- {70, nullptr, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"},
+ {70, &FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"},
+ {71, &FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"},
{80, nullptr, "OpenSaveDataMetaFile"},
{81, nullptr, "OpenSaveDataTransferManager"},
{82, nullptr, "OpenSaveDataTransferManagerVersion2"},
{83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
{84, nullptr, "ListApplicationAccessibleSaveDataOwnerId"},
{85, nullptr, "OpenSaveDataTransferManagerForSaveDataRepair"},
+ {86, nullptr, "OpenSaveDataMover"},
{100, nullptr, "OpenImageDirectoryFileSystem"},
{110, nullptr, "OpenContentStorageFileSystem"},
{120, nullptr, "OpenCloudBackupWorkStorageFileSystem"},
@@ -759,9 +766,11 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
{1011, &FSP_SRV::GetAccessLogVersionInfo, "GetAccessLogVersionInfo"},
{1012, nullptr, "GetFsStackUsage"},
{1013, nullptr, "UnsetSaveDataRootPath"},
+ {1014, nullptr, "OutputMultiProgramTagAccessLog"},
{1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
{1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"},
- {1200, nullptr, "OpenMultiCommitManager"},
+ {1200, &FSP_SRV::OpenMultiCommitManager, "OpenMultiCommitManager"},
+ {1300, nullptr, "OpenBisWiper"},
};
// clang-format on
RegisterHandlers(functions);
@@ -805,7 +814,7 @@ void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
+ auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
[[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
u128 uid = rp.PopRaw<u128>();
@@ -819,31 +828,40 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_FS, "called.");
+ IPC::RequestParser rp{ctx};
struct Parameters {
- FileSys::SaveDataSpaceId save_data_space_id;
- FileSys::SaveDataDescriptor descriptor;
+ FileSys::SaveDataSpaceId space_id;
+ FileSys::SaveDataAttribute attribute;
};
- IPC::RequestParser rp{ctx};
const auto parameters = rp.PopRaw<Parameters>();
- auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
+ LOG_INFO(Service_FS, "called.");
+
+ auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute);
if (dir.Failed()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
return;
}
- FileSys::StorageId id;
- if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) {
+ FileSys::StorageId id{};
+ switch (parameters.space_id) {
+ case FileSys::SaveDataSpaceId::NandUser:
id = FileSys::StorageId::NandUser;
- } else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem ||
- parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) {
+ break;
+ case FileSys::SaveDataSpaceId::SdCardSystem:
+ case FileSys::SaveDataSpaceId::SdCardUser:
id = FileSys::StorageId::SdCard;
- } else {
+ break;
+ case FileSys::SaveDataSpaceId::NandSystem:
id = FileSys::StorageId::NandSystem;
+ break;
+ case FileSys::SaveDataSpaceId::TemporaryStorage:
+ case FileSys::SaveDataSpaceId::ProperSystem:
+ case FileSys::SaveDataSpaceId::SafeMode:
+ UNREACHABLE();
}
auto filesystem =
@@ -869,22 +887,38 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
}
-void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- log_mode = rp.PopEnum<LogMode>();
-
- LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_FS, "called");
+void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ struct Parameters {
+ FileSys::SaveDataSpaceId space_id;
+ FileSys::SaveDataAttribute attribute;
+ };
+
+ const auto parameters = rp.PopRaw<Parameters>();
+ // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData
+ constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None);
+
+ LOG_WARNING(Service_FS,
+ "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n"
+ "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n"
+ "attribute.type={}, attribute.rank={}, attribute.index={}",
+ flags, static_cast<u32>(parameters.space_id), parameters.attribute.title_id,
+ parameters.attribute.user_id[1], parameters.attribute.user_id[0],
+ parameters.attribute.save_id, static_cast<u32>(parameters.attribute.type),
+ static_cast<u32>(parameters.attribute.rank), parameters.attribute.index);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushEnum(log_mode);
+ rb.Push(flags);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
@@ -936,7 +970,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
return;
}
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, fsc, content_provider};
auto storage = std::make_shared<IStorage>(
pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
@@ -959,6 +993,24 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
}
+void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ log_mode = rp.PopEnum<LogMode>();
+
+ LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(log_mode);
+}
+
void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
const auto raw = ctx.ReadBuffer();
auto log = Common::StringFromFixedZeroTerminatedBuffer(
@@ -981,4 +1033,40 @@ void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) {
rb.Push(access_log_program_index);
}
+class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> {
+public:
+ explicit IMultiCommitManager() : ServiceFramework("IMultiCommitManager") {
+ static const FunctionInfo functions[] = {
+ {1, &IMultiCommitManager::Add, "Add"},
+ {2, &IMultiCommitManager::Commit, "Commit"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ FileSys::VirtualFile backend;
+
+ void Add(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Commit(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+};
+
+void FSP_SRV::OpenMultiCommitManager(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>());
+}
+
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index d52b55999..6c7239e6a 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -12,8 +12,9 @@ class Reporter;
}
namespace FileSys {
+class ContentProvider;
class FileSystemBackend;
-}
+} // namespace FileSys
namespace Service::FileSystem {
@@ -32,7 +33,8 @@ enum class LogMode : u32 {
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
public:
- explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter);
+ explicit FSP_SRV(FileSystemController& fsc_, const FileSys::ContentProvider& content_provider_,
+ const Core::Reporter& reporter_);
~FSP_SRV() override;
private:
@@ -43,15 +45,19 @@ private:
void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx);
- void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
- void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
+ void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx);
+ void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
+ void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
+ void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
+ void OpenMultiCommitManager(Kernel::HLERequestContext& ctx);
FileSystemController& fsc;
+ const FileSys::ContentProvider& content_provider;
FileSys::VirtualFile romfs;
u64 current_process_id = 0;
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 7938b4b80..ebb323da2 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -5,6 +5,7 @@
#include <queue>
#include "common/logging/log.h"
#include "common/uuid.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
@@ -25,9 +26,13 @@ public:
{10101, &IFriendService::GetFriendList, "GetFriendList"},
{10102, nullptr, "UpdateFriendInfo"},
{10110, nullptr, "GetFriendProfileImage"},
+ {10120, nullptr, "Unknown10120"},
+ {10121, nullptr, "Unknown10121"},
{10200, nullptr, "SendFriendRequestForApplication"},
{10211, nullptr, "AddFacedFriendRequestForApplication"},
{10400, &IFriendService::GetBlockedUserListIds, "GetBlockedUserListIds"},
+ {10420, nullptr, "Unknown10420"},
+ {10421, nullptr, "Unknown10421"},
{10500, nullptr, "GetProfileList"},
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
{10601, &IFriendService::DeclareCloseOnlinePlaySession, "DeclareCloseOnlinePlaySession"},
@@ -96,6 +101,9 @@ public:
{30830, nullptr, "ClearPlayLog"},
{30900, nullptr, "SendFriendInvitation"},
{30910, nullptr, "ReadFriendInvitation"},
+ {30911, nullptr, "ReadAllFriendInvitations"},
+ {40100, nullptr, "Unknown40100"},
+ {40400, nullptr, "Unknown40400"},
{49900, nullptr, "DeleteNetworkServiceAccountCache"},
};
// clang-format on
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index b591ce31b..c6252ff89 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -5,6 +5,7 @@
#include <memory>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h
index c2874c585..f6647f724 100644
--- a/src/core/hle/service/glue/errors.h
+++ b/src/core/hle/service/glue/errors.h
@@ -8,9 +8,9 @@
namespace Service::Glue {
-constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E};
-constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F};
-constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A};
-constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66};
+constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 30};
+constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 31};
+constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 42};
+constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 102};
} // namespace Service::Glue
diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp
index 24910ac6c..401e0b208 100644
--- a/src/core/hle/service/grc/grc.cpp
+++ b/src/core/hle/service/grc/grc.cpp
@@ -17,6 +17,9 @@ public:
static const FunctionInfo functions[] = {
{1, nullptr, "OpenContinuousRecorder"},
{2, nullptr, "OpenGameMovieTrimmer"},
+ {3, nullptr, "OpenOffscreenRecorder"},
+ {101, nullptr, "CreateMovieMaker"},
+ {9903, nullptr, "SetOffscreenRecordingMarker"}
};
// clang-format on
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
index 8bc69c372..f47a9e61c 100644
--- a/src/core/hle/service/hid/controllers/controller_base.h
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -31,6 +31,10 @@ public:
virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) = 0;
+ // When the controller is requesting a motion update for the shared memory
+ virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t size) {}
+
// Called when input devices should be loaded
virtual void OnLoadInputDevices() = 0;
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index 1f2131ec8..ad251ed4a 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -23,7 +23,7 @@ void Controller_DebugPad::OnRelease() {}
void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
- shared_memory.header.timestamp = core_timing.GetTicks();
+ shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
@@ -39,33 +39,36 @@ void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing,
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- cur_entry.attribute.connected.Assign(1);
- auto& pad = cur_entry.pad_state;
- using namespace Settings::NativeButton;
- pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
- pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
- pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
- pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
- pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
- pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
- pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
- pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
- pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
- pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+ if (Settings::values.debug_pad_enabled) {
+ cur_entry.attribute.connected.Assign(1);
+ auto& pad = cur_entry.pad_state;
- const auto [stick_l_x_f, stick_l_y_f] =
- analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
- const auto [stick_r_x_f, stick_r_y_f] =
- analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
- cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ using namespace Settings::NativeButton;
+ pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
+ pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+
+ const auto [stick_l_x_f, stick_l_y_f] =
+ analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
+ const auto [stick_r_x_f, stick_r_y_f] =
+ analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
+ cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ }
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
}
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 6e990dd00..b7b7bfeae 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -19,7 +19,7 @@ void Controller_Gesture::OnRelease() {}
void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
- shared_memory.header.timestamp = core_timing.GetTicks();
+ shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index 358cb9329..59b694cd4 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -21,7 +21,7 @@ void Controller_Keyboard::OnRelease() {}
void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
- shared_memory.header.timestamp = core_timing.GetTicks();
+ shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
@@ -38,16 +38,18 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing,
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
- for (std::size_t k = 0; k < KEYS_PER_BYTE; ++k) {
- cur_entry.key[i / KEYS_PER_BYTE] |= (keyboard_keys[i]->GetStatus() << k);
+ cur_entry.key.fill(0);
+ cur_entry.modifier = 0;
+ if (Settings::values.keyboard_enabled) {
+ for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
+ auto& entry = cur_entry.key[i / KEYS_PER_BYTE];
+ entry = static_cast<u8>(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)));
}
- }
- for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
- cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
+ for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
+ cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
+ }
}
-
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index 93d88ea50..ac40989c5 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -19,7 +19,7 @@ void Controller_Mouse::OnRelease() {}
void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
- shared_memory.header.timestamp = core_timing.GetTicks();
+ shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index c1e32b28c..e2539ded8 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -24,6 +24,7 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
constexpr u32 MAX_NPAD_ID = 7;
+constexpr std::size_t HANDHELD_INDEX = 8;
constexpr std::array<u32, 10> npad_id_list{
0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN,
};
@@ -33,19 +34,41 @@ enum class JoystickId : std::size_t {
Joystick_Right,
};
-static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) {
+Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
+ Settings::ControllerType type) {
switch (type) {
case Settings::ControllerType::ProController:
- return Controller_NPad::NPadControllerType::ProController;
- case Settings::ControllerType::DualJoycon:
- return Controller_NPad::NPadControllerType::JoyDual;
+ return NPadControllerType::ProController;
+ case Settings::ControllerType::DualJoyconDetached:
+ return NPadControllerType::JoyDual;
case Settings::ControllerType::LeftJoycon:
- return Controller_NPad::NPadControllerType::JoyLeft;
+ return NPadControllerType::JoyLeft;
case Settings::ControllerType::RightJoycon:
- return Controller_NPad::NPadControllerType::JoyRight;
+ return NPadControllerType::JoyRight;
+ case Settings::ControllerType::Handheld:
+ return NPadControllerType::Handheld;
default:
UNREACHABLE();
- return Controller_NPad::NPadControllerType::JoyDual;
+ return NPadControllerType::ProController;
+ }
+}
+
+Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
+ Controller_NPad::NPadControllerType type) {
+ switch (type) {
+ case NPadControllerType::ProController:
+ return Settings::ControllerType::ProController;
+ case NPadControllerType::JoyDual:
+ return Settings::ControllerType::DualJoyconDetached;
+ case NPadControllerType::JoyLeft:
+ return Settings::ControllerType::LeftJoycon;
+ case NPadControllerType::JoyRight:
+ return Settings::ControllerType::RightJoycon;
+ case NPadControllerType::Handheld:
+ return Settings::ControllerType::Handheld;
+ default:
+ UNREACHABLE();
+ return Settings::ControllerType::ProController;
}
}
@@ -60,9 +83,9 @@ std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) {
case 6:
case 7:
return npad_id;
- case 8:
+ case HANDHELD_INDEX:
case NPAD_HANDHELD:
- return 8;
+ return HANDHELD_INDEX;
case 9:
case NPAD_UNKNOWN:
return 9;
@@ -83,37 +106,51 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
case 6:
case 7:
return static_cast<u32>(index);
- case 8:
+ case HANDHELD_INDEX:
return NPAD_HANDHELD;
case 9:
return NPAD_UNKNOWN;
default:
UNIMPLEMENTED_MSG("Unknown npad index {}", index);
return 0;
- };
+ }
}
Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
-Controller_NPad::~Controller_NPad() = default;
-void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
+Controller_NPad::~Controller_NPad() {
+ OnRelease();
+}
+
+void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
const auto controller_type = connected_controllers[controller_idx].type;
auto& controller = shared_memory_entries[controller_idx];
if (controller_type == NPadControllerType::None) {
+ styleset_changed_events[controller_idx].writable->Signal();
return;
}
controller.joy_styles.raw = 0; // Zero out
controller.device_type.raw = 0;
+ controller.properties.raw = 0;
switch (controller_type) {
case NPadControllerType::None:
UNREACHABLE();
+ break;
+ case NPadControllerType::ProController:
+ controller.joy_styles.pro_controller.Assign(1);
+ controller.device_type.pro_controller.Assign(1);
+ controller.properties.is_vertical.Assign(1);
+ controller.properties.use_plus.Assign(1);
+ controller.properties.use_minus.Assign(1);
+ controller.pad_assignment = NpadAssignments::Single;
+ break;
case NPadControllerType::Handheld:
controller.joy_styles.handheld.Assign(1);
controller.device_type.handheld.Assign(1);
- controller.pad_assignment = NPadAssignments::Dual;
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
+ controller.pad_assignment = NpadAssignments::Dual;
break;
case NPadControllerType::JoyDual:
controller.joy_styles.joycon_dual.Assign(1);
@@ -122,34 +159,26 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Dual;
+ controller.pad_assignment = NpadAssignments::Dual;
break;
case NPadControllerType::JoyLeft:
controller.joy_styles.joycon_left.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.properties.is_horizontal.Assign(1);
controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.pad_assignment = NpadAssignments::Single;
break;
case NPadControllerType::JoyRight:
controller.joy_styles.joycon_right.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.properties.is_horizontal.Assign(1);
controller.properties.use_plus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.pad_assignment = NpadAssignments::Single;
break;
case NPadControllerType::Pokeball:
controller.joy_styles.pokeball.Assign(1);
controller.device_type.pokeball.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
- break;
- case NPadControllerType::ProController:
- controller.joy_styles.pro_controller.Assign(1);
- controller.device_type.pro_controller.Assign(1);
- controller.properties.is_vertical.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.pad_assignment = NpadAssignments::Single;
break;
}
@@ -158,21 +187,25 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.single_color.button_color = 0;
controller.dual_color_error = ColorReadError::ReadOk;
- controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left;
- controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left;
- controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right;
+ controller.left_color.body_color =
+ Settings::values.players.GetValue()[controller_idx].body_color_left;
+ controller.left_color.button_color =
+ Settings::values.players.GetValue()[controller_idx].button_color_left;
+ controller.right_color.body_color =
+ Settings::values.players.GetValue()[controller_idx].body_color_right;
controller.right_color.button_color =
- Settings::values.players[controller_idx].button_color_right;
+ Settings::values.players.GetValue()[controller_idx].button_color_right;
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
controller.battery_level[2] = BATTERY_FULL;
- styleset_changed_events[controller_idx].writable->Signal();
+
+ SignalStyleSetChangedEvent(IndexToNPad(controller_idx));
}
void Controller_NPad::OnInit() {
auto& kernel = system.Kernel();
- for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
+ for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) {
styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
kernel, fmt::format("npad:NpadStyleSetChanged_{}", i));
}
@@ -181,6 +214,8 @@ void Controller_NPad::OnInit() {
return;
}
+ OnLoadInputDevices();
+
if (style.raw == 0) {
// We want to support all controllers
style.handheld.Assign(1);
@@ -191,42 +226,46 @@ void Controller_NPad::OnInit() {
style.pokeball.Assign(1);
}
- std::transform(
- Settings::values.players.begin(), Settings::values.players.end(),
- connected_controllers.begin(), [](const Settings::PlayerInput& player) {
- return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected};
- });
+ std::transform(Settings::values.players.GetValue().begin(),
+ Settings::values.players.GetValue().end(), connected_controllers.begin(),
+ [](const Settings::PlayerInput& player) {
+ return ControllerHolder{MapSettingsTypeToNPad(player.controller_type),
+ player.connected};
+ });
- std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8,
- [](const ControllerHolder& holder) { return holder.is_connected; });
+ // Connect the Player 1 or Handheld controller if none are connected.
+ if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
+ [](const ControllerHolder& controller) { return controller.is_connected; })) {
+ const auto controller =
+ MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type);
+ if (controller == NPadControllerType::Handheld) {
+ Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
+ connected_controllers[HANDHELD_INDEX] = {controller, true};
+ } else {
+ Settings::values.players.GetValue()[0].connected = true;
+ connected_controllers[0] = {controller, true};
+ }
+ }
// Account for handheld
- if (connected_controllers[8].is_connected)
- connected_controllers[8].type = NPadControllerType::Handheld;
+ if (connected_controllers[HANDHELD_INDEX].is_connected) {
+ connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld;
+ }
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(u32));
- // Add a default dual joycon controller if none are present.
- if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
- [](const ControllerHolder& controller) { return controller.is_connected; })) {
- supported_npad_id_types.resize(npad_id_list.size());
- std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
- npad_id_list.size() * sizeof(u32));
- AddNewController(NPadControllerType::JoyDual);
- }
-
for (std::size_t i = 0; i < connected_controllers.size(); ++i) {
const auto& controller = connected_controllers[i];
if (controller.is_connected) {
- AddNewControllerAt(controller.type, IndexToNPad(i));
+ AddNewControllerAt(controller.type, i);
}
}
}
void Controller_NPad::OnLoadInputDevices() {
- const auto& players = Settings::values.players;
+ const auto& players = Settings::values.players.GetValue();
for (std::size_t i = 0; i < players.size(); ++i) {
std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
@@ -234,14 +273,30 @@ void Controller_NPad::OnLoadInputDevices() {
std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
+ std::transform(players[i].vibrations.begin() +
+ Settings::NativeVibration::VIBRATION_HID_BEGIN,
+ players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END,
+ vibrations[i].begin(), Input::CreateDevice<Input::VibrationDevice>);
+ std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
+ players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
+ motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
+ for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) {
+ InitializeVibrationDeviceAtIndex(i, device_idx);
+ }
}
}
-void Controller_NPad::OnRelease() {}
+void Controller_NPad::OnRelease() {
+ for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) {
+ for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) {
+ VibrateControllerAtIndex(npad_idx, device_idx, {});
+ }
+ }
+}
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
const auto controller_idx = NPadIdToIndex(npad_id);
- [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type;
+ const auto controller_type = connected_controllers[controller_idx].type;
if (!connected_controllers[controller_idx].is_connected) {
return;
}
@@ -256,61 +311,71 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
using namespace Settings::NativeButton;
- pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus());
-
- pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
-
- pad_state.l_stick_right.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::RIGHT));
- pad_state.l_stick_left.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::LEFT));
- pad_state.l_stick_up.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::UP));
- pad_state.l_stick_down.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::DOWN));
-
- pad_state.r_stick_right.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
- pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
- pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
- pad_state.r_stick_down.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
-
- pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
-
- lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ if (controller_type != NPadControllerType::JoyLeft) {
+ pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.r_stick_right.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
+ pad_state.r_stick_left.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
+ pad_state.r_stick_up.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
+ pad_state.r_stick_down.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
+ rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ }
+
+ if (controller_type != NPadControllerType::JoyRight) {
+ pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.l_stick_right.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
+ pad_state.l_stick_left.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
+ pad_state.l_stick_up.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
+ pad_state.l_stick_down.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
+ lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ }
+
+ if (controller_type == NPadControllerType::JoyLeft ||
+ controller_type == NPadControllerType::JoyRight) {
+ pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
+ }
}
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t data_len) {
- if (!IsControllerActivated())
+ if (!IsControllerActivated()) {
return;
- for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+ }
+ for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
auto& npad = shared_memory_entries[i];
const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
&npad.handheld_states,
@@ -327,7 +392,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
const auto& last_entry =
main_controller->npad[main_controller->common.last_entry_index];
- main_controller->common.timestamp = core_timing.GetTicks();
+ main_controller->common.timestamp = core_timing.GetCPUTicks();
main_controller->common.last_entry_index =
(main_controller->common.last_entry_index + 1) % 17;
@@ -343,6 +408,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
continue;
}
const u32 npad_index = static_cast<u32>(i);
+
RequestPadStateUpdate(npad_index);
auto& pad_state = npad_pad_states[npad_index];
@@ -359,12 +425,25 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
libnx_entry.connection_status.raw = 0;
+ libnx_entry.connection_status.IsConnected.Assign(1);
switch (controller_type) {
case NPadControllerType::None:
UNREACHABLE();
+ break;
+ case NPadControllerType::ProController:
+ main_controller.connection_status.raw = 0;
+ main_controller.connection_status.IsConnected.Assign(1);
+ main_controller.connection_status.IsWired.Assign(1);
+ main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
+ main_controller.pad.l_stick = pad_state.l_stick;
+ main_controller.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsWired.Assign(1);
+ break;
case NPadControllerType::Handheld:
handheld_entry.connection_status.raw = 0;
+ handheld_entry.connection_status.IsConnected.Assign(1);
handheld_entry.connection_status.IsWired.Assign(1);
handheld_entry.connection_status.IsLeftJoyConnected.Assign(1);
handheld_entry.connection_status.IsRightJoyConnected.Assign(1);
@@ -373,57 +452,52 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw;
handheld_entry.pad.l_stick = pad_state.l_stick;
handheld_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsWired.Assign(1);
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsLeftJoyWired.Assign(1);
+ libnx_entry.connection_status.IsRightJoyWired.Assign(1);
break;
case NPadControllerType::JoyDual:
dual_entry.connection_status.raw = 0;
-
+ dual_entry.connection_status.IsConnected.Assign(1);
dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
dual_entry.connection_status.IsRightJoyConnected.Assign(1);
- dual_entry.connection_status.IsConnected.Assign(1);
-
- libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
- libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
- libnx_entry.connection_status.IsConnected.Assign(1);
-
dual_entry.pad.pad_states.raw = pad_state.pad_states.raw;
dual_entry.pad.l_stick = pad_state.l_stick;
dual_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
break;
case NPadControllerType::JoyLeft:
left_entry.connection_status.raw = 0;
-
left_entry.connection_status.IsConnected.Assign(1);
+ left_entry.connection_status.IsLeftJoyConnected.Assign(1);
left_entry.pad.pad_states.raw = pad_state.pad_states.raw;
left_entry.pad.l_stick = pad_state.l_stick;
left_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
break;
case NPadControllerType::JoyRight:
right_entry.connection_status.raw = 0;
-
right_entry.connection_status.IsConnected.Assign(1);
+ right_entry.connection_status.IsRightJoyConnected.Assign(1);
right_entry.pad.pad_states.raw = pad_state.pad_states.raw;
right_entry.pad.l_stick = pad_state.l_stick;
right_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
-
pokeball_entry.connection_status.IsConnected.Assign(1);
- pokeball_entry.connection_status.IsWired.Assign(1);
-
pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw;
pokeball_entry.pad.l_stick = pad_state.l_stick;
pokeball_entry.pad.r_stick = pad_state.r_stick;
break;
- case NPadControllerType::ProController:
- main_controller.connection_status.raw = 0;
-
- main_controller.connection_status.IsConnected.Assign(1);
- main_controller.connection_status.IsWired.Assign(1);
- main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
- main_controller.pad.l_stick = pad_state.l_stick;
- main_controller.pad.r_stick = pad_state.r_stick;
- break;
}
// LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
@@ -438,39 +512,144 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
shared_memory_entries.size() * sizeof(NPadEntry));
}
-void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
+void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t data_len) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+ for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
+ auto& npad = shared_memory_entries[i];
+
+ const auto& controller_type = connected_controllers[i].type;
+
+ if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
+ continue;
+ }
+
+ const std::array<SixAxisGeneric*, 6> controller_sixaxes{
+ &npad.sixaxis_full, &npad.sixaxis_handheld, &npad.sixaxis_dual_left,
+ &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right,
+ };
+
+ for (auto* sixaxis_sensor : controller_sixaxes) {
+ sixaxis_sensor->common.entry_count = 16;
+ sixaxis_sensor->common.total_entry_count = 17;
+
+ const auto& last_entry =
+ sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
+
+ sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks();
+ sixaxis_sensor->common.last_entry_index =
+ (sixaxis_sensor->common.last_entry_index + 1) % 17;
+
+ auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
+ // Try to read sixaxis sensor states
+ std::array<MotionDevice, 2> motion_devices;
+
+ if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) {
+ sixaxis_at_rest = true;
+ for (std::size_t e = 0; e < motion_devices.size(); ++e) {
+ const auto& device = motions[i][e];
+ if (device) {
+ std::tie(motion_devices[e].accel, motion_devices[e].gyro,
+ motion_devices[e].rotation, motion_devices[e].orientation) =
+ device->GetStatus();
+ sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
+ }
+ }
+ }
+
+ auto& full_sixaxis_entry =
+ npad.sixaxis_full.sixaxis[npad.sixaxis_full.common.last_entry_index];
+ auto& handheld_sixaxis_entry =
+ npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index];
+ auto& dual_left_sixaxis_entry =
+ npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index];
+ auto& dual_right_sixaxis_entry =
+ npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index];
+ auto& left_sixaxis_entry =
+ npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index];
+ auto& right_sixaxis_entry =
+ npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index];
+
+ switch (controller_type) {
+ case NPadControllerType::None:
+ UNREACHABLE();
+ break;
+ case NPadControllerType::ProController:
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ full_sixaxis_entry.accel = motion_devices[0].accel;
+ full_sixaxis_entry.gyro = motion_devices[0].gyro;
+ full_sixaxis_entry.rotation = motion_devices[0].rotation;
+ full_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::Handheld:
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ handheld_sixaxis_entry.accel = motion_devices[0].accel;
+ handheld_sixaxis_entry.gyro = motion_devices[0].gyro;
+ handheld_sixaxis_entry.rotation = motion_devices[0].rotation;
+ handheld_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::JoyDual:
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ // Set motion for the left joycon
+ dual_left_sixaxis_entry.accel = motion_devices[0].accel;
+ dual_left_sixaxis_entry.gyro = motion_devices[0].gyro;
+ dual_left_sixaxis_entry.rotation = motion_devices[0].rotation;
+ dual_left_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ if (sixaxis_sensors_enabled && motions[i][1]) {
+ // Set motion for the right joycon
+ dual_right_sixaxis_entry.accel = motion_devices[1].accel;
+ dual_right_sixaxis_entry.gyro = motion_devices[1].gyro;
+ dual_right_sixaxis_entry.rotation = motion_devices[1].rotation;
+ dual_right_sixaxis_entry.orientation = motion_devices[1].orientation;
+ }
+ break;
+ case NPadControllerType::JoyLeft:
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ left_sixaxis_entry.accel = motion_devices[0].accel;
+ left_sixaxis_entry.gyro = motion_devices[0].gyro;
+ left_sixaxis_entry.rotation = motion_devices[0].rotation;
+ left_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::JoyRight:
+ if (sixaxis_sensors_enabled && motions[i][1]) {
+ right_sixaxis_entry.accel = motion_devices[1].accel;
+ right_sixaxis_entry.gyro = motion_devices[1].gyro;
+ right_sixaxis_entry.rotation = motion_devices[1].rotation;
+ right_sixaxis_entry.orientation = motion_devices[1].orientation;
+ }
+ break;
+ case NPadControllerType::Pokeball:
+ break;
+ }
+ }
+ std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
+ shared_memory_entries.size() * sizeof(NPadEntry));
+}
+
+void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) {
style.raw = style_set.raw;
}
-Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
+Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const {
return style;
}
-void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
+void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) {
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
std::memcpy(supported_npad_id_types.data(), data, length);
- for (std::size_t i = 0; i < connected_controllers.size(); i++) {
- auto& controller = connected_controllers[i];
- if (!controller.is_connected) {
- continue;
- }
- const auto requested_controller =
- i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type)
- : NPadControllerType::Handheld;
- if (!IsControllerSupported(requested_controller)) {
- const auto is_handheld = requested_controller == NPadControllerType::Handheld;
- if (is_handheld) {
- controller.type = NPadControllerType::None;
- controller.is_connected = false;
- AddNewController(requested_controller);
- } else {
- controller.type = requested_controller;
- InitNewlyAddedControler(i);
- }
- }
- }
}
void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
@@ -478,7 +657,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length)
std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
}
-std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
+std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
return supported_npad_id_types.size();
}
@@ -490,7 +669,15 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
return hold_type;
}
-void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
+void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
+ handheld_activation_mode = activation_mode;
+}
+
+Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const {
+ return handheld_activation_mode;
+}
+
+void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) {
const std::size_t npad_index = NPadIdToIndex(npad_id);
ASSERT(npad_index < shared_memory_entries.size());
if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
@@ -498,70 +685,230 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
}
}
-void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
- const std::vector<Vibration>& vibrations) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+ const VibrationValue& vibration_value) {
+ if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) {
+ return false;
+ }
+
+ const auto& player = Settings::values.players.GetValue()[npad_index];
- if (!can_controllers_vibrate) {
- return;
+ if (!player.vibration_enabled) {
+ if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f ||
+ latest_vibration_values[npad_index][device_index].amp_high != 0.0f) {
+ // Send an empty vibration to stop any vibrations.
+ vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f);
+ // Then reset the vibration value to its default value.
+ latest_vibration_values[npad_index][device_index] = {};
+ }
+
+ return false;
}
- for (std::size_t i = 0; i < controller_ids.size(); i++) {
- std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i));
- if (connected_controllers[controller_pos].is_connected) {
- // TODO(ogniK): Vibrate the physical controller
+
+ if (!Settings::values.enable_accurate_vibrations.GetValue()) {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ using std::chrono::steady_clock;
+
+ const auto now = steady_clock::now();
+
+ // Filter out non-zero vibrations that are within 10ms of each other.
+ if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) &&
+ duration_cast<milliseconds>(now - last_vibration_timepoints[npad_index][device_index]) <
+ milliseconds(10)) {
+ return false;
}
+
+ last_vibration_timepoints[npad_index][device_index] = now;
}
- last_processed_vibration = vibrations.back();
+
+ auto& vibration = vibrations[npad_index][device_index];
+ const auto player_vibration_strength = static_cast<f32>(player.vibration_strength);
+ const auto amp_low =
+ std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f);
+ const auto amp_high =
+ std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f);
+ return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high,
+ vibration_value.freq_high);
+}
+
+void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
+ const VibrationValue& vibration_value) {
+ if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
+ return;
+ }
+
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+
+ if (!vibration_devices_mounted[npad_index][device_index] ||
+ !connected_controllers[npad_index].is_connected) {
+ return;
+ }
+
+ if (vibration_device_handle.device_index == DeviceIndex::None) {
+ UNREACHABLE_MSG("DeviceIndex should never be None!");
+ return;
+ }
+
+ // Some games try to send mismatched parameters in the device handle, block these.
+ if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft &&
+ (vibration_device_handle.npad_type == NpadType::JoyconRight ||
+ vibration_device_handle.device_index == DeviceIndex::Right)) ||
+ (connected_controllers[npad_index].type == NPadControllerType::JoyRight &&
+ (vibration_device_handle.npad_type == NpadType::JoyconLeft ||
+ vibration_device_handle.device_index == DeviceIndex::Left))) {
+ return;
+ }
+
+ // Filter out vibrations with equivalent values to reduce unnecessary state changes.
+ if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low &&
+ vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) {
+ return;
+ }
+
+ if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) {
+ latest_vibration_values[npad_index][device_index] = vibration_value;
+ }
+}
+
+void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+ const std::vector<VibrationValue>& vibration_values) {
+ if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
+ return;
+ }
+
+ ASSERT_OR_EXECUTE_MSG(
+ vibration_device_handles.size() == vibration_values.size(), { return; },
+ "The amount of device handles does not match with the amount of vibration values,"
+ "this is undefined behavior!");
+
+ for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) {
+ VibrateController(vibration_device_handles[i], vibration_values[i]);
+ }
+}
+
+Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
+ const DeviceHandle& vibration_device_handle) const {
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ return latest_vibration_values[npad_index][device_index];
+}
+
+void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ InitializeVibrationDeviceAtIndex(npad_index, device_index);
+}
+
+void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
+ std::size_t device_index) {
+ if (vibrations[npad_index][device_index]) {
+ vibration_devices_mounted[npad_index][device_index] =
+ vibrations[npad_index][device_index]->GetStatus() == 1;
+ } else {
+ vibration_devices_mounted[npad_index][device_index] = false;
+ }
+}
+
+void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
+ permit_vibration_session_enabled = permit_vibration_session;
+}
+
+bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ return vibration_devices_mounted[npad_index][device_index];
}
std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
- // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
- // be signalled at least once, and signaled after a new controller is connected?
const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
return styleset_event.readable;
}
-Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
- return last_processed_vibration;
+void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const {
+ styleset_changed_events[NPadIdToIndex(npad_id)].writable->Signal();
}
-void Controller_NPad::AddNewController(NPadControllerType controller) {
- controller = DecideBestController(controller);
- if (controller == NPadControllerType::Handheld) {
- connected_controllers[8] = {controller, true};
- InitNewlyAddedControler(8);
+void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) {
+ UpdateControllerAt(controller, npad_index, true);
+}
+
+void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
+ bool connected) {
+ if (!connected) {
+ DisconnectNpadAtIndex(npad_index);
return;
}
- const auto pos =
- std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
- [](const ControllerHolder& holder) { return !holder.is_connected; });
- if (pos == connected_controllers.end() - 2) {
- LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
+
+ if (controller == NPadControllerType::Handheld) {
+ Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type =
+ MapNPadToSettingsType(controller);
+ Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
+ connected_controllers[HANDHELD_INDEX] = {controller, true};
+ InitNewlyAddedController(HANDHELD_INDEX);
return;
}
- const auto controller_id = std::distance(connected_controllers.begin(), pos);
- connected_controllers[controller_id] = {controller, true};
- InitNewlyAddedControler(controller_id);
+
+ Settings::values.players.GetValue()[npad_index].controller_type =
+ MapNPadToSettingsType(controller);
+ Settings::values.players.GetValue()[npad_index].connected = true;
+ connected_controllers[npad_index] = {controller, true};
+ InitNewlyAddedController(npad_index);
}
-void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) {
- controller = DecideBestController(controller);
- if (controller == NPadControllerType::Handheld) {
- connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true};
- InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD));
- return;
+void Controller_NPad::DisconnectNpad(u32 npad_id) {
+ DisconnectNpadAtIndex(NPadIdToIndex(npad_id));
+}
+
+void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
+ for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) {
+ // Send an empty vibration to stop any vibrations.
+ VibrateControllerAtIndex(npad_index, device_idx, {});
+ vibration_devices_mounted[npad_index][device_idx] = false;
}
- connected_controllers[NPadIdToIndex(npad_id)] = {controller, true};
- InitNewlyAddedControler(NPadIdToIndex(npad_id));
+ Settings::values.players.GetValue()[npad_index].connected = false;
+ connected_controllers[npad_index].is_connected = false;
+
+ auto& controller = shared_memory_entries[npad_index];
+ controller.joy_styles.raw = 0; // Zero out
+ controller.device_type.raw = 0;
+ controller.properties.raw = 0;
+
+ SignalStyleSetChangedEvent(IndexToNPad(npad_index));
+}
+
+void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) {
+ gyroscope_zero_drift_mode = drift_mode;
+}
+
+Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode() const {
+ return gyroscope_zero_drift_mode;
+}
+
+bool Controller_NPad::IsSixAxisSensorAtRest() const {
+ return sixaxis_at_rest;
}
-void Controller_NPad::ConnectNPad(u32 npad_id) {
- connected_controllers[NPadIdToIndex(npad_id)].is_connected = true;
+void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) {
+ sixaxis_sensors_enabled = six_axis_status;
}
-void Controller_NPad::DisconnectNPad(u32 npad_id) {
- connected_controllers[NPadIdToIndex(npad_id)].is_connected = false;
+void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
+ const auto npad_index_1 = NPadIdToIndex(npad_id_1);
+ const auto npad_index_2 = NPadIdToIndex(npad_id_2);
+
+ // If the controllers at both npad indices form a pair of left and right joycons, merge them.
+ // Otherwise, do nothing.
+ if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) ||
+ (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
+ // Disconnect the joycon at the second id and connect the dual joycon at the first index.
+ DisconnectNpad(npad_id_2);
+ AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
+ }
}
void Controller_NPad::StartLRAssignmentMode() {
@@ -589,8 +936,8 @@ bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) {
std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type);
- InitNewlyAddedControler(npad_index_1);
- InitNewlyAddedControler(npad_index_2);
+ AddNewControllerAt(connected_controllers[npad_index_1].type, npad_index_1);
+ AddNewControllerAt(connected_controllers[npad_index_2].type, npad_index_2);
return true;
}
@@ -604,11 +951,11 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
case 0:
return LedPattern{1, 0, 0, 0};
case 1:
- return LedPattern{0, 1, 0, 0};
+ return LedPattern{1, 1, 0, 0};
case 2:
- return LedPattern{0, 0, 1, 0};
+ return LedPattern{1, 1, 1, 0};
case 3:
- return LedPattern{0, 0, 0, 1};
+ return LedPattern{1, 1, 1, 1};
case 4:
return LedPattern{1, 0, 0, 1};
case 5:
@@ -618,17 +965,17 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
case 7:
return LedPattern{0, 1, 1, 0};
default:
- UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
return LedPattern{0, 0, 0, 0};
- };
+ }
}
-void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
- can_controllers_vibrate = can_vibrate;
+bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const {
+ return unintended_home_button_input_protection[NPadIdToIndex(npad_id)];
}
-bool Controller_NPad::IsVibrationEnabled() const {
- return can_controllers_vibrate;
+void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
+ u32 npad_id) {
+ unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled;
}
void Controller_NPad::ClearAllConnectedControllers() {
@@ -641,13 +988,13 @@ void Controller_NPad::ClearAllConnectedControllers() {
}
void Controller_NPad::DisconnectAllConnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.is_connected = false;
}
}
void Controller_NPad::ConnectAllDisconnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
if (controller.type != NPadControllerType::None && !controller.is_connected) {
controller.is_connected = true;
}
@@ -655,7 +1002,7 @@ void Controller_NPad::ConnectAllDisconnectedControllers() {
}
void Controller_NPad::ClearAllControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
}
@@ -675,7 +1022,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
// Handheld should not be supported in docked mode
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
return false;
}
@@ -703,92 +1050,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
-Controller_NPad::NPadControllerType Controller_NPad::DecideBestController(
- NPadControllerType priority) const {
- if (IsControllerSupported(priority)) {
- return priority;
- }
- const auto is_docked = Settings::values.use_docked_mode;
- if (is_docked && priority == NPadControllerType::Handheld) {
- priority = NPadControllerType::JoyDual;
- if (IsControllerSupported(priority)) {
- return priority;
- }
- }
- std::vector<NPadControllerType> priority_list;
- switch (priority) {
- case NPadControllerType::ProController:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Handheld:
- priority_list.push_back(NPadControllerType::JoyDual);
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyDual:
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyLeft:
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyRight:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Pokeball:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- break;
- default:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- break;
- }
-
- const auto iter = std::find_if(priority_list.begin(), priority_list.end(),
- [this](auto type) { return IsControllerSupported(type); });
- if (iter == priority_list.end()) {
- UNIMPLEMENTED_MSG("Could not find supported controller!");
- return priority;
- }
-
- return *iter;
-}
-
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 931f03430..160dcbbe3 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -32,51 +32,90 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ // When the controller is requesting a motion update for the shared memory
+ void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t size) override;
+
// Called when input devices should be loaded
void OnLoadInputDevices() override;
- struct NPadType {
- union {
- u32_le raw{};
+ enum class NPadControllerType {
+ None,
+ ProController,
+ Handheld,
+ JoyDual,
+ JoyLeft,
+ JoyRight,
+ Pokeball,
+ };
- BitField<0, 1, u32> pro_controller;
- BitField<1, 1, u32> handheld;
- BitField<2, 1, u32> joycon_dual;
- BitField<3, 1, u32> joycon_left;
- BitField<4, 1, u32> joycon_right;
+ enum class NpadType : u8 {
+ ProController = 3,
+ Handheld = 4,
+ JoyconDual = 5,
+ JoyconLeft = 6,
+ JoyconRight = 7,
+ Pokeball = 9,
+ };
- BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
- };
+ enum class DeviceIndex : u8 {
+ Left = 0,
+ Right = 1,
+ None = 2,
};
- static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
- struct Vibration {
- f32 amp_low;
- f32 freq_low;
- f32 amp_high;
- f32 freq_high;
+ enum class GyroscopeZeroDriftMode : u32 {
+ Loose = 0,
+ Standard = 1,
+ Tight = 2,
};
- static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
enum class NpadHoldType : u64 {
Vertical = 0,
Horizontal = 1,
};
- enum class NPadAssignments : u32_le {
+ enum class NpadAssignments : u32 {
Dual = 0,
Single = 1,
};
- enum class NPadControllerType {
- None,
- ProController,
- Handheld,
- JoyDual,
- JoyLeft,
- JoyRight,
- Pokeball,
+ enum class NpadHandheldActivationMode : u64 {
+ Dual = 0,
+ Single = 1,
+ None = 2,
+ };
+
+ struct DeviceHandle {
+ NpadType npad_type{};
+ u8 npad_id{};
+ DeviceIndex device_index{};
+ INSERT_PADDING_BYTES(1);
};
+ static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
+
+ struct NpadStyleSet {
+ union {
+ u32_le raw{};
+
+ BitField<0, 1, u32> pro_controller;
+ BitField<1, 1, u32> handheld;
+ BitField<2, 1, u32> joycon_dual;
+ BitField<3, 1, u32> joycon_left;
+ BitField<4, 1, u32> joycon_right;
+
+ BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
+ };
+ };
+ static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
+
+ struct VibrationValue {
+ f32 amp_low{0.0f};
+ f32 freq_low{160.0f};
+ f32 amp_high{0.0f};
+ f32 freq_high{320.0f};
+ };
+ static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size");
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
@@ -94,37 +133,64 @@ public:
};
};
- void SetSupportedStyleSet(NPadType style_set);
- NPadType GetSupportedStyleSet() const;
+ void SetSupportedStyleSet(NpadStyleSet style_set);
+ NpadStyleSet GetSupportedStyleSet() const;
- void SetSupportedNPadIdTypes(u8* data, std::size_t length);
+ void SetSupportedNpadIdTypes(u8* data, std::size_t length);
void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
- std::size_t GetSupportedNPadIdTypesSize() const;
+ std::size_t GetSupportedNpadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
- void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
+ void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
+ NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
+
+ void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
- void VibrateController(const std::vector<u32>& controller_ids,
- const std::vector<Vibration>& vibrations);
+ bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+ const VibrationValue& vibration_value);
+
+ void VibrateController(const DeviceHandle& vibration_device_handle,
+ const VibrationValue& vibration_value);
+
+ void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+ const std::vector<VibrationValue>& vibration_values);
+
+ VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const;
+
+ void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle);
+
+ void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index);
+
+ void SetPermitVibrationSession(bool permit_vibration_session);
+
+ bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const;
std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
- Vibration GetLastVibration() const;
+ void SignalStyleSetChangedEvent(u32 npad_id) const;
+
+ // Adds a new controller at an index.
+ void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
+ // Adds a new controller at an index with connection status.
+ void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
- void AddNewController(NPadControllerType controller);
- void AddNewControllerAt(NPadControllerType controller, u32 npad_id);
+ void DisconnectNpad(u32 npad_id);
+ void DisconnectNpadAtIndex(std::size_t index);
- void ConnectNPad(u32 npad_id);
- void DisconnectNPad(u32 npad_id);
+ void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
+ GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
+ bool IsSixAxisSensorAtRest() const;
+ void SetSixAxisEnabled(bool six_axis_status);
LedPattern GetLedPattern(u32 npad_id);
- void SetVibrationEnabled(bool can_vibrate);
- bool IsVibrationEnabled() const;
+ bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
+ void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
+ void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2);
void StartLRAssignmentMode();
void StopLRAssignmentMode();
bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
@@ -133,6 +199,8 @@ public:
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
+ static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type);
+ static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
@@ -236,6 +304,24 @@ private:
};
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
+ struct SixAxisStates {
+ s64_le timestamp{};
+ INSERT_PADDING_WORDS(2);
+ s64_le timestamp2{};
+ Common::Vec3f accel{};
+ Common::Vec3f gyro{};
+ Common::Vec3f rotation{};
+ std::array<Common::Vec3f, 3> orientation{};
+ s64_le always_one{1};
+ };
+ static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
+
+ struct SixAxisGeneric {
+ CommonHeader common{};
+ std::array<SixAxisStates, 17> sixaxis{};
+ };
+ static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
+
enum class ColorReadError : u32_le {
ReadOk = 0,
ColorDoesntExist = 1,
@@ -265,9 +351,16 @@ private:
};
};
+ struct MotionDevice {
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f rotation;
+ std::array<Common::Vec3f, 3> orientation;
+ };
+
struct NPadEntry {
- NPadType joy_styles;
- NPadAssignments pad_assignment;
+ NpadStyleSet joy_styles;
+ NpadAssignments pad_assignment;
ColorReadError single_color_error;
ControllerColor single_color;
@@ -284,9 +377,12 @@ private:
NPadGeneric pokeball_states;
NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
// relying on this for the time being
- INSERT_PADDING_BYTES(
- 0x708 *
- 6); // TODO(ogniK): SixAxis states, require more information before implementation
+ SixAxisGeneric sixaxis_full;
+ SixAxisGeneric sixaxis_handheld;
+ SixAxisGeneric sixaxis_dual_left;
+ SixAxisGeneric sixaxis_dual_right;
+ SixAxisGeneric sixaxis_left;
+ SixAxisGeneric sixaxis_right;
NPadDevice device_type;
NPadProperties properties;
INSERT_PADDING_WORDS(1);
@@ -301,31 +397,44 @@ private:
bool is_connected;
};
- void InitNewlyAddedControler(std::size_t controller_idx);
+ void InitNewlyAddedController(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
- NPadControllerType DecideBestController(NPadControllerType priority) const;
void RequestPadStateUpdate(u32 npad_id);
u32 press_state{};
- NPadType style{};
+ NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};
- std::array<
+ using ButtonArray = std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
- 10>
- buttons;
- std::array<
+ 10>;
+ using StickArray = std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
- 10>
- sticks;
+ 10>;
+ using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
+ Settings::NativeVibration::NUM_VIBRATIONS_HID>,
+ 10>;
+ using MotionArray = std::array<
+ std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
+ 10>;
+ ButtonArray buttons;
+ StickArray sticks;
+ VibrationArray vibrations;
+ MotionArray motions;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
+ NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
// Each controller should have their own styleset changed event
std::array<Kernel::EventPair, 10> styleset_changed_events;
- Vibration last_processed_vibration{};
+ std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10> last_vibration_timepoints;
+ std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
+ bool permit_vibration_session_enabled{false};
+ std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
std::array<ControllerHolder, 10> connected_controllers{};
- bool can_controllers_vibrate{true};
-
+ std::array<bool, 10> unintended_home_button_input_protection{};
+ GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
+ bool sixaxis_sensors_enabled{true};
+ bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};
bool is_in_lr_assignment_mode{false};
Core::System& system;
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
index 9e527d176..e7483bfa2 100644
--- a/src/core/hle/service/hid/controllers/stubbed.cpp
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -23,7 +23,7 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
}
CommonHeader header{};
- header.timestamp = core_timing.GetTicks();
+ header.timestamp = core_timing.GetCPUTicks();
header.total_entry_count = 17;
header.entry_count = 0;
header.last_entry_index = 0;
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 1c6e55566..0df395e85 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -22,7 +22,7 @@ void Controller_Touchscreen::OnRelease() {}
void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
- shared_memory.header.timestamp = core_timing.GetTicks();
+ shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
@@ -40,16 +40,21 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- const auto [x, y, pressed] = touch_device->GetStatus();
+ bool pressed = false;
+ float x, y;
+ std::tie(x, y, pressed) = touch_device->GetStatus();
auto& touch_entry = cur_entry.states[0];
touch_entry.attribute.raw = 0;
+ if (!pressed && touch_btn_device) {
+ std::tie(x, y, pressed) = touch_btn_device->GetStatus();
+ }
if (pressed && Settings::values.touchscreen.enabled) {
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle;
- const u64 tick = core_timing.GetTicks();
+ const u64 tick = core_timing.GetCPUTicks();
touch_entry.delta_time = tick - last_touch;
last_touch = tick;
touch_entry.finger = Settings::values.touchscreen.finger;
@@ -63,5 +68,10 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
void Controller_Touchscreen::OnLoadInputDevices() {
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
+ if (Settings::values.use_touch_from_button) {
+ touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
+ } else {
+ touch_btn_device.reset();
+ }
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index a1d97269e..4d9042adc 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -68,6 +68,7 @@ private:
"TouchScreenSharedMemory is an invalid size");
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_device;
+ std::unique_ptr<Input::TouchDevice> touch_btn_device;
s64_le last_touch{};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
index 27511b27b..2503ef241 100644
--- a/src/core/hle/service/hid/controllers/xpad.cpp
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -20,7 +20,7 @@ void Controller_XPad::OnRelease() {}
void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
for (auto& xpad_entry : shared_memory.shared_memory_entries) {
- xpad_entry.header.timestamp = core_timing.GetTicks();
+ xpad_entry.header.timestamp = core_timing.GetCPUTicks();
xpad_entry.header.total_entry_count = 17;
if (!IsControllerActivated()) {
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index d6ed5f304..902516b29 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -14,6 +14,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/writable_event.h"
@@ -37,12 +38,10 @@
namespace Service::HID {
// Updating period for each HID device.
-// TODO(ogniK): Find actual polling rate of hid
-constexpr s64 pad_update_ticks = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 66);
-[[maybe_unused]] constexpr s64 accelerometer_update_ticks =
- static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100);
-[[maybe_unused]] constexpr s64 gyroscope_update_ticks =
- static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100);
+// HID is polled every 15ms, this value was derived from
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
+constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz)
+constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system)
@@ -53,9 +52,7 @@ IAppletResource::IAppletResource(Core::System& system)
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- shared_mem = Kernel::SharedMemory::Create(
- kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
+ shared_mem = SharedFrom(&kernel.GetHidSharedMem());
MakeController<Controller_DebugPad>(HidController::DebugPad);
MakeController<Controller_Touchscreen>(HidController::Touchscreen);
@@ -78,14 +75,19 @@ IAppletResource::IAppletResource(Core::System& system)
GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
// Register update callbacks
- pad_update_event =
- Core::Timing::CreateEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 cycles_late) {
- UpdateControllers(userdata, cycles_late);
+ pad_update_event = Core::Timing::CreateEvent(
+ "HID::UpdatePadCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ UpdateControllers(user_data, ns_late);
+ });
+ motion_update_event = Core::Timing::CreateEvent(
+ "HID::MotionPadCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ UpdateMotion(user_data, ns_late);
});
- // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
-
- system.CoreTiming().ScheduleEvent(pad_update_ticks, pad_update_event);
+ system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
+ system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
ReloadInputDevices();
}
@@ -110,7 +112,8 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(shared_mem);
}
-void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) {
+void IAppletResource::UpdateControllers(std::uintptr_t user_data,
+ std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
@@ -121,25 +124,49 @@ void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) {
controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
- core_timing.ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
+ core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
+}
+
+void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+
+ for (const auto& controller : controllers) {
+ controller->OnMotionUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
+ }
+
+ core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event);
}
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
public:
- IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") {
+ explicit IActiveVibrationDeviceList(std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework("IActiveVibrationDeviceList"), applet_resource(applet_resource_) {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"},
+ {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
private:
- void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .InitializeVibrationDevice(vibration_device_handle);
+
+ LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
+ vibration_device_handle.npad_type, vibration_device_handle.npad_id,
+ vibration_device_handle.device_index);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+
+ std::shared_ptr<IAppletResource> applet_resource;
};
std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
@@ -158,16 +185,16 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
{21, &Hid::ActivateMouse, "ActivateMouse"},
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
- {32, nullptr, "SendKeyboardLockKeyEvent"},
+ {32, &Hid::SendKeyboardLockKeyEvent, "SendKeyboardLockKeyEvent"},
{40, nullptr, "AcquireXpadIdEventHandle"},
{41, nullptr, "ReleaseXpadIdEventHandle"},
{51, &Hid::ActivateXpad, "ActivateXpad"},
- {55, nullptr, "GetXpadIds"},
+ {55, &Hid::GetXpadIDs, "GetXpadIds"},
{56, nullptr, "ActivateJoyXpad"},
{58, nullptr, "GetJoyXpadLifoHandle"},
{59, nullptr, "GetJoyXpadIds"},
- {60, nullptr, "ActivateSixAxisSensor"},
- {61, nullptr, "DeactivateSixAxisSensor"},
+ {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
+ {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
{62, nullptr, "GetSixAxisSensorLifoHandle"},
{63, nullptr, "ActivateJoySixAxisSensor"},
{64, nullptr, "DeactivateJoySixAxisSensor"},
@@ -175,7 +202,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
{67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
{68, nullptr, "IsSixAxisSensorFusionEnabled"},
- {69, nullptr, "EnableSixAxisSensorFusion"},
+ {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"},
{70, nullptr, "SetSixAxisSensorFusionParameters"},
{71, nullptr, "GetSixAxisSensorFusionParameters"},
{72, nullptr, "ResetSixAxisSensorFusionParameters"},
@@ -186,8 +213,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{77, nullptr, "GetAccelerometerPlayMode"},
{78, nullptr, "ResetAccelerometerPlayMode"},
{79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
- {80, nullptr, "GetGyroscopeZeroDriftMode"},
- {81, nullptr, "ResetGyroscopeZeroDriftMode"},
+ {80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"},
+ {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},
{82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
{83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
{91, &Hid::ActivateGesture, "ActivateGesture"},
@@ -211,8 +238,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
{129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"},
{130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"},
- {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
- {132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
+ {131, &Hid::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"},
+ {132, &Hid::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"},
{133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
{134, nullptr, "SetNpadAnalogStickUseCenterClamp"},
{135, nullptr, "SetNpadCaptureButtonAssignment"},
@@ -228,18 +255,18 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{208, nullptr, "GetActualVibrationGcErmCommand"},
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
- {211, nullptr, "IsVibrationDeviceMounted"},
+ {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
- {302, nullptr, "StopConsoleSixAxisSensor"},
- {303, nullptr, "ActivateSevenSixAxisSensor"},
- {304, nullptr, "StartSevenSixAxisSensor"},
- {305, nullptr, "StopSevenSixAxisSensor"},
+ {302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
+ {303, &Hid::ActivateSevenSixAxisSensor, "ActivateSevenSixAxisSensor"},
+ {304, &Hid::StartSevenSixAxisSensor, "StartSevenSixAxisSensor"},
+ {305, &Hid::StopSevenSixAxisSensor, "StopSevenSixAxisSensor"},
{306, &Hid::InitializeSevenSixAxisSensor, "InitializeSevenSixAxisSensor"},
- {307, nullptr, "FinalizeSevenSixAxisSensor"},
+ {307, &Hid::FinalizeSevenSixAxisSensor, "FinalizeSevenSixAxisSensor"},
{308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
{309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
- {310, nullptr, "ResetSevenSixAxisSensorTimestamp"},
+ {310, &Hid::ResetSevenSixAxisSensorTimestamp, "ResetSevenSixAxisSensorTimestamp"},
{400, nullptr, "IsUsbFullKeyControllerEnabled"},
{401, nullptr, "EnableUsbFullKeyController"},
{402, nullptr, "IsUsbFullKeyControllerConnected"},
@@ -247,7 +274,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{404, nullptr, "HasLeftRightBattery"},
{405, nullptr, "GetNpadInterfaceType"},
{406, nullptr, "GetNpadLeftRightInterfaceType"},
- {407, nullptr, "GetNpadOfHighestBatteryLevelForJoyLeft"},
+ {407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
{500, nullptr, "GetPalmaConnectionHandle"},
{501, nullptr, "InitializePalma"},
@@ -283,6 +310,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{1001, nullptr, "GetNpadCommunicationMode"},
{1002, nullptr, "SetTouchScreenConfiguration"},
{1003, nullptr, "IsFirmwareUpdateNeededForNotification"},
+ {2000, nullptr, "ActivateDigitizer"},
};
// clang-format on
@@ -306,97 +334,195 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IAppletResource>(applet_resource);
}
-void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto basic_xpad_id{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id,
- applet_resource_user_id);
+ applet_resource->ActivateController(HidController::DebugPad);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::XPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Touchscreen);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Mouse);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Keyboard);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto flags{rp.Pop<u32>()};
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags);
- applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 basic_xpad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::XPad);
+
+ LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
+ parameters.basic_xpad_id, parameters.applet_resource_user_id);
- applet_resource->ActivateController(HidController::Gesture);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
- // Should have no effect with how our npad sets up the data
+void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(0);
+}
+
+void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
- applet_resource->ActivateController(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enable_sixaxis_sensor_fusion{};
+ INSERT_PADDING_BYTES(3);
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
+ "device_index={}, applet_resource_user_id={}",
+ parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
+ parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
+ parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -404,13 +530,61 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto drift_mode{rp.Pop<u32>()};
+ const auto sixaxis_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+ const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID,
- "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}", handle,
- drift_mode, applet_resource_user_id);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetGyroscopeZeroDriftMode(drift_mode);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
+ "applet_resource_user_id={}",
+ sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index,
+ drift_mode, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetGyroscopeZeroDriftMode());
+}
+
+void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -418,27 +592,53 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- // TODO (Hexagon12): Properly implement reading gyroscope values from controllers.
- rb.Push(true);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsSixAxisSensorAtRest());
+}
+
+void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 unknown{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::Gesture);
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto supported_styleset{rp.Pop<u32>()};
- LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
-
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedStyleSet({supported_styleset});
+ LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -449,21 +649,22 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
-
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(controller.GetSupportedStyleSet().raw);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetSupportedStyleSet()
+ .raw);
}
void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -472,48 +673,62 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::NPad);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- applet_resource->ActivateController(HidController::NPad);
}
void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->DeactivateController(HidController::NPad);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- applet_resource->DeactivateController(HidController::NPad);
}
void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto unknown{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ u64 unknown{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id,
- applet_resource_user_id, unknown);
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
+ parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetStyleSetChangedEvent(npad_id));
+ .GetStyleSetChangedEvent(parameters.npad_id));
}
void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .DisconnectNpad(parameters.npad_id);
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
+ parameters.applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -526,22 +741,41 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLedPattern(npad_id)
- .raw);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLedPattern(npad_id)
+ .raw);
+}
+
+void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
+ // Should have no effect with how our npad sets up the data
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 unknown{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::NPad);
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto hold_type{rp.Pop<u64>()};
+ const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type);
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
applet_resource_user_id, hold_type);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
-
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -552,22 +786,26 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad).GetHoldType());
}
void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -576,16 +814,22 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx
void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
// TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto npad_joy_device_type{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ u64 npad_joy_device_type{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
LOG_WARNING(Service_HID,
"(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
- npad_id, applet_resource_user_id, npad_joy_device_type);
-
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
+ parameters.npad_id, parameters.applet_resource_user_id,
+ parameters.npad_joy_device_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -593,14 +837,19 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -608,13 +857,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown_1{rp.Pop<u32>()};
- const auto unknown_2{rp.Pop<u32>()};
+ const auto npad_id_1{rp.Pop<u32>()};
+ const auto npad_id_2{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- unknown_1, unknown_2, applet_resource_user_id);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -624,9 +875,9 @@ void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).StartLRAssignmentMode();
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.StartLRAssignmentMode();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -636,9 +887,9 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).StopLRAssignmentMode();
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.StopLRAssignmentMode();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -647,10 +898,13 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto mode{rp.Pop<u64>()};
+ const auto activation_mode{rp.PopEnum<Controller_NPad::NpadHandheldActivationMode>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadHandheldActivationMode(activation_mode);
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
- applet_resource_user_id, mode);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}",
+ applet_resource_user_id, activation_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -660,25 +914,28 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetNpadHandheldActivationMode());
}
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_1{rp.Pop<u32>()};
- const auto npad_2{rp.Pop<u32>()};
+ const auto npad_id_1{rp.Pop<u32>()};
+ const auto npad_id_2{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}",
- applet_resource_user_id, npad_1, npad_2);
+ const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SwapNpadAssignment(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
- if (controller.SwapNpadAssignment(npad_1, npad_2)) {
+ if (res) {
rb.Push(RESULT_SUCCESS);
} else {
LOG_ERROR(Service_HID, "Npads are not connected!");
@@ -686,119 +943,275 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
}
}
-void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true);
- IPC::ResponseBuilder rb{ctx, 2};
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id));
}
-void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool unintended_home_button_input_protection{};
+ INSERT_PADDING_BYTES(3);
+ u32 npad_id{};
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetUnintendedHomeButtonInputProtectionEnabled(
+ parameters.unintended_home_button_input_protection, parameters.npad_id);
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={},"
+ "applet_resource_user_id={}",
+ parameters.unintended_home_button_input_protection, parameters.npad_id,
+ parameters.applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+
+ VibrationDeviceInfo vibration_device_info;
+
+ vibration_device_info.type = VibrationDeviceType::LinearResonantActuator;
+
+ switch (vibration_device_handle.device_index) {
+ case Controller_NPad::DeviceIndex::Left:
+ vibration_device_info.position = VibrationDevicePosition::Left;
+ break;
+ case Controller_NPad::DeviceIndex::Right:
+ vibration_device_info.position = VibrationDevicePosition::Right;
+ break;
+ case Controller_NPad::DeviceIndex::None:
+ default:
+ UNREACHABLE_MSG("DeviceIndex should never be None!");
+ vibration_device_info.position = VibrationDevicePosition::None;
+ break;
+ }
+
+ LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
+ vibration_device_info.type, vibration_device_info.position);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(vibration_device_info);
+}
+
void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
- const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle{};
+ Controller_NPad::VibrationValue vibration_value{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController(parameters.vibration_device_handle, parameters.vibration_value);
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
+}
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController({controller_id}, {vibration_values});
+void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLastVibration(parameters.vibration_device_handle));
+}
+
+void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IActiveVibrationDeviceList>(applet_resource);
+}
+
+void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto can_vibrate{rp.Pop<bool>()};
+
+ Settings::values.vibration_enabled.SetValue(can_vibrate);
+
+ LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(Settings::values.vibration_enabled.GetValue());
}
void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- const auto controllers = ctx.ReadBuffer(0);
+ const auto handles = ctx.ReadBuffer(0);
const auto vibrations = ctx.ReadBuffer(1);
- std::vector<u32> controller_list(controllers.size() / sizeof(u32));
- std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
- sizeof(Controller_NPad::Vibration));
+ std::vector<Controller_NPad::DeviceHandle> vibration_device_handles(
+ handles.size() / sizeof(Controller_NPad::DeviceHandle));
+ std::vector<Controller_NPad::VibrationValue> vibration_values(
+ vibrations.size() / sizeof(Controller_NPad::VibrationValue));
- std::memcpy(controller_list.data(), controllers.data(), controllers.size());
- std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
- std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
- [](u32 controller_id) { return controller_id - 3; });
+ std::memcpy(vibration_device_handles.data(), handles.data(), handles.size());
+ std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size());
applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController(controller_list, vibration_list);
+ .VibrateControllers(vibration_device_handles, vibration_values);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
- applet_resource_user_id);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetPermitVibrationSession(true);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- IPC::ResponseBuilder rb{ctx, 6};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<Controller_NPad::Vibration>(
- applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration());
}
-void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
+void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetPermitVibrationSession(false);
+
LOG_DEBUG(Service_HID, "called");
- IPC::ResponseBuilder rb{ctx, 4};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(1);
- rb.Push<u32>(0);
}
-void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IActiveVibrationDeviceList>();
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsVibrationDeviceMounted(parameters.vibration_device_handle));
}
-void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto can_vibrate{rp.Pop<bool>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetVibrationEnabled(can_vibrate);
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
- IPC::ResponseBuilder rb{ctx, 3};
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push(
- applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled());
}
-void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle{};
+ INSERT_PADDING_WORDS(1);
+ u64 applet_resource_user_id{};
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::ActivateSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -809,52 +1222,75 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+void Hid::StartSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
+void Hid::StopSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle);
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
+void Hid::InitializeSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto unknown{rp.Pop<u32>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}",
- applet_resource_user_id, unknown);
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
+void Hid::ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown);
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::InitializeSevenSixAxisSensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto is_palma_all_connectable{rp.Pop<bool>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}",
+ applet_resource_user_id, is_palma_all_connectable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto palma_boost_mode{rp.Pop<bool>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -871,6 +1307,7 @@ public:
{10, nullptr, "DeactivateTouchScreen"},
{11, nullptr, "SetTouchScreenAutoPilotState"},
{12, nullptr, "UnsetTouchScreenAutoPilotState"},
+ {13, nullptr, "GetTouchScreenConfiguration"},
{20, nullptr, "DeactivateMouse"},
{21, nullptr, "SetMouseAutoPilotState"},
{22, nullptr, "UnsetMouseAutoPilotState"},
@@ -880,7 +1317,9 @@ public:
{50, nullptr, "DeactivateXpad"},
{51, nullptr, "SetXpadAutoPilotState"},
{52, nullptr, "UnsetXpadAutoPilotState"},
- {60, nullptr, "DeactivateJoyXpad"},
+ {60, nullptr, "ClearNpadSystemCommonPolicy"},
+ {61, nullptr, "DeactivateNpad"},
+ {62, nullptr, "ForceDisconnectNpad"},
{91, nullptr, "DeactivateGesture"},
{110, nullptr, "DeactivateHomeButton"},
{111, nullptr, "SetHomeButtonAutoPilotState"},
@@ -900,6 +1339,15 @@ public:
{141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"},
{142, nullptr, "DeactivateSevenSixAxisSensor"},
{143, nullptr, "GetConsoleSixAxisSensorCountStates"},
+ {144, nullptr, "GetAccelerometerFsr"},
+ {145, nullptr, "SetAccelerometerFsr"},
+ {146, nullptr, "GetAccelerometerOdr"},
+ {147, nullptr, "SetAccelerometerOdr"},
+ {148, nullptr, "GetGyroscopeFsr"},
+ {149, nullptr, "SetGyroscopeFsr"},
+ {150, nullptr, "GetGyroscopeOdr"},
+ {151, nullptr, "SetGyroscopeOdr"},
+ {152, nullptr, "GetWhoAmI"},
{201, nullptr, "ActivateFirmwareUpdate"},
{202, nullptr, "DeactivateFirmwareUpdate"},
{203, nullptr, "StartFirmwareUpdate"},
@@ -928,6 +1376,17 @@ public:
{233, nullptr, "ClearPairingInfo"},
{234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
{235, nullptr, "EnableAnalogStickPower"},
+ {236, nullptr, "RequestKuinaUartClockCal"},
+ {237, nullptr, "GetKuinaUartClockCal"},
+ {238, nullptr, "SetKuinaUartClockTrim"},
+ {239, nullptr, "KuinaLoopbackTest"},
+ {240, nullptr, "RequestBatteryVoltage"},
+ {241, nullptr, "GetBatteryVoltage"},
+ {242, nullptr, "GetUniquePadPowerInfo"},
+ {243, nullptr, "RebootUniquePad"},
+ {244, nullptr, "RequestKuinaFirmwareVersion"},
+ {245, nullptr, "GetKuinaFirmwareVersion"},
+ {246, nullptr, "GetVidPid"},
{301, nullptr, "GetAbstractedPadHandles"},
{302, nullptr, "GetAbstractedPadState"},
{303, nullptr, "GetAbstractedPadsState"},
@@ -946,6 +1405,17 @@ public:
{350, nullptr, "AddRegisteredDevice"},
{400, nullptr, "DisableExternalMcuOnNxDevice"},
{401, nullptr, "DisableRailDeviceFiltering"},
+ {402, nullptr, "EnableWiredPairing"},
+ {403, nullptr, "EnableShipmentModeAutoClear"},
+ {500, nullptr, "SetFactoryInt"},
+ {501, nullptr, "IsFactoryBootEnabled"},
+ {550, nullptr, "SetAnalogStickModelDataTemporarily"},
+ {551, nullptr, "GetAnalogStickModelData"},
+ {552, nullptr, "ResetAnalogStickModelData"},
+ {600, nullptr, "ConvertPadState"},
+ {2000, nullptr, "DeactivateDigitizer"},
+ {2001, nullptr, "SetDigitizerAutoPilotState"},
+ {2002, nullptr, "UnsetDigitizerAutoPilotState"},
};
// clang-format on
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 039c38b58..c8e4a4b55 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,10 +4,9 @@
#pragma once
-#include "core/hle/service/hid/controllers/controller_base.h"
-#include "core/hle/service/service.h"
+#include <chrono>
-#include "controllers/controller_base.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/service.h"
namespace Core::Timing {
@@ -65,11 +64,13 @@ private:
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
- void UpdateControllers(u64 userdata, s64 cycles_late);
+ void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
std::shared_ptr<Kernel::SharedMemory> shared_mem;
std::shared_ptr<Core::Timing::EventType> pad_update_event;
+ std::shared_ptr<Core::Timing::EventType> motion_update_event;
Core::System& system;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
@@ -85,16 +86,23 @@ public:
private:
void CreateAppletResource(Kernel::HLERequestContext& ctx);
- void ActivateXpad(Kernel::HLERequestContext& ctx);
void ActivateDebugPad(Kernel::HLERequestContext& ctx);
void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
void ActivateMouse(Kernel::HLERequestContext& ctx);
void ActivateKeyboard(Kernel::HLERequestContext& ctx);
- void ActivateGesture(Kernel::HLERequestContext& ctx);
- void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
+ void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx);
+ void ActivateXpad(Kernel::HLERequestContext& ctx);
+ void GetXpadIDs(Kernel::HLERequestContext& ctx);
+ void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx);
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
+ void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
+ void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
+ void ActivateGesture(Kernel::HLERequestContext& ctx);
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
@@ -103,6 +111,7 @@ private:
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
void DisconnectNpad(Kernel::HLERequestContext& ctx);
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
+ void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
@@ -114,21 +123,45 @@ private:
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
- void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
- void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx);
+ void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx);
+ void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void SendVibrationValue(Kernel::HLERequestContext& ctx);
- void SendVibrationValues(Kernel::HLERequestContext& ctx);
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
- void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
void PermitVibration(Kernel::HLERequestContext& ctx);
void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
+ void SendVibrationValues(Kernel::HLERequestContext& ctx);
+ void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
- void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void ActivateSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StartSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StopSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void InitializeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
- void InitializeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
+
+ enum class VibrationDeviceType : u32 {
+ LinearResonantActuator = 1,
+ };
+
+ enum class VibrationDevicePosition : u32 {
+ None = 0,
+ Left = 1,
+ Right = 2,
+ };
+
+ struct VibrationDeviceInfo {
+ VibrationDeviceType type{};
+ VibrationDevicePosition position{};
+ };
+ static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
std::shared_ptr<IAppletResource> applet_resource;
Core::System& system;
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 5e79e2c1a..e82fd031b 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -6,6 +6,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/hid/irs.h"
@@ -38,9 +39,8 @@ IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) {
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- shared_mem = Kernel::SharedMemory::Create(
- kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory");
+
+ shared_mem = SharedFrom(&kernel.GetIrsSharedMem());
}
void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
@@ -98,7 +98,7 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 5};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(system.CoreTiming().GetTicks());
+ rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks());
rb.PushRaw<u32>(0);
}
diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp
index e8f9f2d29..17350b403 100644
--- a/src/core/hle/service/lbl/lbl.cpp
+++ b/src/core/hle/service/lbl/lbl.cpp
@@ -47,6 +47,7 @@ public:
{26, &LBL::EnableVrMode, "EnableVrMode"},
{27, &LBL::DisableVrMode, "DisableVrMode"},
{28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"},
+ {29, nullptr, "IsAutoBrightnessControlSupported"},
};
// clang-format on
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 92adde6d4..49972cd69 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -69,6 +69,7 @@ public:
{101, nullptr, "GetNetworkInfoLatestUpdate"},
{102, nullptr, "Scan"},
{103, nullptr, "ScanPrivate"},
+ {104, nullptr, "SetWirelessControllerRestriction"},
{200, nullptr, "OpenAccessPoint"},
{201, nullptr, "CloseAccessPoint"},
{202, nullptr, "CreateNetwork"},
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 647943020..65c209725 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -8,15 +8,23 @@
#include "common/alignment.h"
#include "common/hex_util.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/memory/page_table.h"
+#include "core/hle/kernel/memory/system_control.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
#include "core/loader/nro.h"
+#include "core/memory.h"
namespace Service::LDR {
-constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
+constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
+
+[[maybe_unused]] constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54};
@@ -26,10 +34,84 @@ constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57};
constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81};
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82};
constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
-constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
+[[maybe_unused]] constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
-constexpr u64 MAXIMUM_LOADED_RO = 0x40;
+constexpr std::size_t MAXIMUM_LOADED_RO{0x40};
+constexpr std::size_t MAXIMUM_MAP_RETRIES{0x200};
+
+constexpr std::size_t TEXT_INDEX{0};
+constexpr std::size_t RO_INDEX{1};
+constexpr std::size_t DATA_INDEX{2};
+
+struct NRRCertification {
+ u64_le application_id_mask;
+ u64_le application_id_pattern;
+ INSERT_PADDING_BYTES(0x10);
+ std::array<u8, 0x100> public_key; // Also known as modulus
+ std::array<u8, 0x100> signature;
+};
+static_assert(sizeof(NRRCertification) == 0x220, "NRRCertification has invalid size.");
+
+struct NRRHeader {
+ u32_le magic;
+ u32_le certification_signature_key_generation; // 9.0.0+
+ INSERT_PADDING_WORDS(2);
+ NRRCertification certification;
+ std::array<u8, 0x100> signature;
+ u64_le application_id;
+ u32_le size;
+ u8 nrr_kind; // 7.0.0+
+ INSERT_PADDING_BYTES(3);
+ u32_le hash_offset;
+ u32_le hash_count;
+ INSERT_PADDING_WORDS(2);
+};
+static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has invalid size.");
+
+struct SegmentHeader {
+ u32_le memory_offset;
+ u32_le memory_size;
+};
+static_assert(sizeof(SegmentHeader) == 0x8, "SegmentHeader has invalid size.");
+
+struct NROHeader {
+ // Switchbrew calls this "Start" (0x10)
+ INSERT_PADDING_WORDS(1);
+ u32_le mod_offset;
+ INSERT_PADDING_WORDS(2);
+
+ // Switchbrew calls this "Header" (0x70)
+ u32_le magic;
+ u32_le version;
+ u32_le nro_size;
+ u32_le flags;
+ // .text, .ro, .data
+ std::array<SegmentHeader, 3> segment_headers;
+ u32_le bss_size;
+ INSERT_PADDING_WORDS(1);
+ std::array<u8, 0x20> build_id;
+ u32_le dso_handle_offset;
+ INSERT_PADDING_WORDS(1);
+ // .apiInfo, .dynstr, .dynsym
+ std::array<SegmentHeader, 3> segment_headers_2;
+};
+static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
+
+using SHA256Hash = std::array<u8, 0x20>;
+
+struct NROInfo {
+ SHA256Hash hash{};
+ VAddr nro_address{};
+ std::size_t nro_size{};
+ VAddr bss_address{};
+ std::size_t bss_size{};
+ std::size_t text_size{};
+ std::size_t ro_size{};
+ std::size_t data_size{};
+ VAddr src_addr{};
+};
+static_assert(sizeof(NROInfo) == 0x60, "NROInfo has invalid size.");
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
@@ -55,6 +137,7 @@ public:
{1, nullptr, "GetProgramInfo"},
{2, nullptr, "RegisterTitle"},
{3, nullptr, "UnregisterTitle"},
+ {4, nullptr, "SetEnabledProgramVerification"},
};
// clang-format on
@@ -164,11 +247,11 @@ public:
return;
}
- if (system.CurrentProcess()->GetTitleID() != header.title_id) {
+ if (system.CurrentProcess()->GetTitleID() != header.application_id) {
LOG_ERROR(Service_LDR,
"Attempting to load NRR with title ID other than current process. (actual "
"{:016X})!",
- header.title_id);
+ header.application_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_NRR);
return;
@@ -191,45 +274,140 @@ public:
}
void UnloadNrr(Kernel::HLERequestContext& ctx) {
- if (!initialized) {
- LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NOT_INITIALIZED);
- return;
+ IPC::RequestParser rp{ctx};
+ const auto pid = rp.Pop<u64>();
+ const auto nrr_address = rp.Pop<VAddr>();
+
+ LOG_DEBUG(Service_LDR, "called with pid={}, nrr_address={:016X}", pid, nrr_address);
+
+ nrr.erase(nrr_address);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start,
+ std::size_t size) const {
+ constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize};
+ const auto start_info{page_table.QueryInfo(start - 1)};
+
+ if (start_info.state != Kernel::Memory::MemoryState::Free) {
+ return {};
}
- struct Parameters {
- u64_le process_id;
- u64_le nrr_address;
- };
+ if (start_info.GetAddress() > (start - padding_size)) {
+ return {};
+ }
- IPC::RequestParser rp{ctx};
- const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
+ const auto end_info{page_table.QueryInfo(start + size)};
- LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id,
- nrr_address);
+ if (end_info.state != Kernel::Memory::MemoryState::Free) {
+ return {};
+ }
- if (!Common::Is4KBAligned(nrr_address)) {
- LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
- nrr_address);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ALIGNMENT);
- return;
+ return (start + size + padding_size) <= (end_info.GetAddress() + end_info.GetSize());
+ }
+
+ VAddr GetRandomMapRegion(const Kernel::Memory::PageTable& page_table, std::size_t size) const {
+ VAddr addr{};
+ const std::size_t end_pages{(page_table.GetAliasCodeRegionSize() - size) >>
+ Kernel::Memory::PageBits};
+ do {
+ addr = page_table.GetAliasCodeRegionStart() +
+ (Kernel::Memory::SystemControl::GenerateRandomRange(0, end_pages)
+ << Kernel::Memory::PageBits);
+ } while (!page_table.IsInsideAddressSpace(addr, size) ||
+ page_table.IsInsideHeapRegion(addr, size) ||
+ page_table.IsInsideAliasRegion(addr, size));
+ return addr;
+ }
+
+ ResultVal<VAddr> MapProcessCodeMemory(Kernel::Process* process, VAddr baseAddress,
+ u64 size) const {
+ for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ auto& page_table{process->PageTable()};
+ const VAddr addr{GetRandomMapRegion(page_table, size)};
+ const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
+
+ if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ continue;
+ }
+
+ CASCADE_CODE(result);
+
+ if (ValidateRegionForMap(page_table, addr, size)) {
+ return MakeResult<VAddr>(addr);
+ }
}
- const auto iter = nrr.find(nrr_address);
- if (iter == nrr.end()) {
- LOG_ERROR(Service_LDR,
- "Attempting to unload NRR which has not been loaded! (addr={:016X})",
- nrr_address);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_NRR_ADDRESS);
- return;
+ return ERROR_INSUFFICIENT_ADDRESS_SPACE;
+ }
+
+ ResultVal<VAddr> MapNro(Kernel::Process* process, VAddr nro_addr, std::size_t nro_size,
+ VAddr bss_addr, std::size_t bss_size, std::size_t size) const {
+ for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ auto& page_table{process->PageTable()};
+ VAddr addr{};
+
+ CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size));
+
+ if (bss_size) {
+ auto block_guard = detail::ScopeExit([&] {
+ page_table.UnmapProcessCodeMemory(addr + nro_size, bss_addr, bss_size);
+ page_table.UnmapProcessCodeMemory(addr, nro_addr, nro_size);
+ });
+
+ const ResultCode result{
+ page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)};
+
+ if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ continue;
+ }
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ block_guard.Cancel();
+ }
+
+ if (ValidateRegionForMap(page_table, addr, size)) {
+ return MakeResult<VAddr>(addr);
+ }
}
- nrr.erase(iter);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ return ERROR_INSUFFICIENT_ADDRESS_SPACE;
+ }
+
+ ResultCode LoadNro(Kernel::Process* process, const NROHeader& nro_header, VAddr nro_addr,
+ VAddr start) const {
+ const VAddr text_start{start + nro_header.segment_headers[TEXT_INDEX].memory_offset};
+ const VAddr ro_start{start + nro_header.segment_headers[RO_INDEX].memory_offset};
+ const VAddr data_start{start + nro_header.segment_headers[DATA_INDEX].memory_offset};
+ const VAddr bss_start{data_start + nro_header.segment_headers[DATA_INDEX].memory_size};
+ const VAddr bss_end_addr{
+ Common::AlignUp(bss_start + nro_header.bss_size, Kernel::Memory::PageSize)};
+
+ auto CopyCode{[&](VAddr src_addr, VAddr dst_addr, u64 size) {
+ std::vector<u8> source_data(size);
+ system.Memory().ReadBlock(src_addr, source_data.data(), source_data.size());
+ system.Memory().WriteBlock(dst_addr, source_data.data(), source_data.size());
+ }};
+ CopyCode(nro_addr + nro_header.segment_headers[TEXT_INDEX].memory_offset, text_start,
+ nro_header.segment_headers[TEXT_INDEX].memory_size);
+ CopyCode(nro_addr + nro_header.segment_headers[RO_INDEX].memory_offset, ro_start,
+ nro_header.segment_headers[RO_INDEX].memory_size);
+ CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start,
+ nro_header.segment_headers[DATA_INDEX].memory_size);
+
+ CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
+ text_start, ro_start - text_start, Kernel::Memory::MemoryPermission::ReadAndExecute));
+ CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
+ ro_start, data_start - ro_start, Kernel::Memory::MemoryPermission::Read));
+
+ return process->PageTable().SetCodeMemoryPermission(
+ data_start, bss_end_addr - data_start, Kernel::Memory::MemoryPermission::ReadAndWrite);
}
void LoadNro(Kernel::HLERequestContext& ctx) {
@@ -317,9 +495,9 @@ public:
return;
}
- NROHeader header;
+ // Load and validate the NRO header
+ NROHeader header{};
std::memcpy(&header, nro_data.data(), sizeof(NROHeader));
-
if (!IsValidNRO(header, nro_size, bss_size)) {
LOG_ERROR(Service_LDR, "NRO was invalid!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -327,62 +505,50 @@ public:
return;
}
- // Load NRO as new executable module
- auto* process = system.CurrentProcess();
- auto& vm_manager = process->VMManager();
- auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size);
-
- if (!map_address.Succeeded() ||
- *map_address + nro_size + bss_size > vm_manager.GetAddressSpaceEndAddress()) {
-
- LOG_ERROR(Service_LDR,
- "General error while allocation memory or no available memory to allocate!");
+ // Map memory for the NRO
+ const auto map_result{MapNro(system.CurrentProcess(), nro_address, nro_size, bss_address,
+ bss_size, nro_size + bss_size)};
+ if (map_result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_MEMORY_STATE);
- return;
+ rb.Push(map_result.Code());
}
- // Mark text and read-only region as ModuleCode
- ASSERT(vm_manager
- .MirrorMemory(*map_address, nro_address, header.text_size + header.ro_size,
- Kernel::MemoryState::ModuleCode)
- .IsSuccess());
- // Mark read/write region as ModuleCodeData, which is necessary if this region is used for
- // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
- ASSERT(vm_manager
- .MirrorMemory(*map_address + header.rw_offset, nro_address + header.rw_offset,
- header.rw_size, Kernel::MemoryState::ModuleCodeData)
- .IsSuccess());
- // Revoke permissions from the old memory region
- ASSERT(vm_manager.ReprotectRange(nro_address, nro_size, Kernel::VMAPermission::None)
- .IsSuccess());
-
- if (bss_size > 0) {
- // Mark BSS region as ModuleCodeData, which is necessary if this region is used for
- // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
- ASSERT(vm_manager
- .MirrorMemory(*map_address + nro_size, bss_address, bss_size,
- Kernel::MemoryState::ModuleCodeData)
- .IsSuccess());
- ASSERT(vm_manager.ReprotectRange(bss_address, bss_size, Kernel::VMAPermission::None)
- .IsSuccess());
+ // Load the NRO into the mapped memory
+ if (const auto result{LoadNro(system.CurrentProcess(), header, nro_address, *map_result)};
+ result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(map_result.Code());
}
- vm_manager.ReprotectRange(*map_address, header.text_size,
- Kernel::VMAPermission::ReadExecute);
- vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size,
- Kernel::VMAPermission::Read);
- vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
- Kernel::VMAPermission::ReadWrite);
+ // Track the loaded NRO
+ nro.insert_or_assign(*map_result,
+ NROInfo{hash, *map_result, nro_size, bss_address, bss_size,
+ header.segment_headers[TEXT_INDEX].memory_size,
+ header.segment_headers[RO_INDEX].memory_size,
+ header.segment_headers[DATA_INDEX].memory_size, nro_address});
+ // Invalidate JIT caches for the newly mapped process code
system.InvalidateCpuInstructionCaches();
- nro.insert_or_assign(*map_address,
- NROInfo{hash, nro_address, nro_size, bss_address, bss_size});
-
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(*map_address);
+ rb.Push(*map_result);
+ }
+
+ ResultCode UnmapNro(const NROInfo& info) {
+ // Each region must be unmapped separately to validate memory state
+ auto& page_table{system.CurrentProcess()->PageTable()};
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(info.nro_address + info.text_size +
+ info.ro_size + info.data_size,
+ info.bss_address, info.bss_size));
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(
+ info.nro_address + info.text_size + info.ro_size,
+ info.src_addr + info.text_size + info.ro_size, info.data_size));
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(
+ info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size));
+ CASCADE_CODE(
+ page_table.UnmapProcessCodeMemory(info.nro_address, info.src_addr, info.text_size));
+ return RESULT_SUCCESS;
}
void UnloadNro(Kernel::HLERequestContext& ctx) {
@@ -422,30 +588,15 @@ public:
return;
}
- auto& vm_manager = system.CurrentProcess()->VMManager();
- const auto& nro_info = iter->second;
-
- // Unmap the mirrored memory
- ASSERT(
- vm_manager.UnmapRange(nro_address, nro_info.nro_size + nro_info.bss_size).IsSuccess());
-
- // Reprotect the source memory
- ASSERT(vm_manager
- .ReprotectRange(nro_info.nro_address, nro_info.nro_size,
- Kernel::VMAPermission::ReadWrite)
- .IsSuccess());
- if (nro_info.bss_size > 0) {
- ASSERT(vm_manager
- .ReprotectRange(nro_info.bss_address, nro_info.bss_size,
- Kernel::VMAPermission::ReadWrite)
- .IsSuccess());
- }
+ const auto result{UnmapNro(iter->second)};
system.InvalidateCpuInstructionCaches();
nro.erase(iter);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+
+ rb.Push(result);
}
void Initialize(Kernel::HLERequestContext& ctx) {
@@ -458,56 +609,7 @@ public:
}
private:
- using SHA256Hash = std::array<u8, 0x20>;
-
- struct NROHeader {
- INSERT_PADDING_WORDS(1);
- u32_le mod_offset;
- INSERT_PADDING_WORDS(2);
- u32_le magic;
- u32_le version;
- u32_le nro_size;
- u32_le flags;
- u32_le text_offset;
- u32_le text_size;
- u32_le ro_offset;
- u32_le ro_size;
- u32_le rw_offset;
- u32_le rw_size;
- u32_le bss_size;
- INSERT_PADDING_WORDS(1);
- std::array<u8, 0x20> build_id;
- INSERT_PADDING_BYTES(0x20);
- };
- static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
-
- struct NRRHeader {
- u32_le magic;
- INSERT_PADDING_BYTES(12);
- u64_le title_id_mask;
- u64_le title_id_pattern;
- INSERT_PADDING_BYTES(16);
- std::array<u8, 0x100> modulus;
- std::array<u8, 0x100> signature_1;
- std::array<u8, 0x100> signature_2;
- u64_le title_id;
- u32_le size;
- INSERT_PADDING_BYTES(4);
- u32_le hash_offset;
- u32_le hash_count;
- INSERT_PADDING_BYTES(8);
- };
- static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
-
- struct NROInfo {
- SHA256Hash hash;
- VAddr nro_address;
- u64 nro_size;
- VAddr bss_address;
- u64 bss_size;
- };
-
- bool initialized = false;
+ bool initialized{};
std::map<VAddr, NROInfo> nro;
std::map<VAddr, std::vector<SHA256Hash>> nrr;
@@ -521,11 +623,21 @@ private:
static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) {
return header.magic == Common::MakeMagic('N', 'R', 'O', '0') &&
header.nro_size == nro_size && header.bss_size == bss_size &&
- header.ro_offset == header.text_offset + header.text_size &&
- header.rw_offset == header.ro_offset + header.ro_size &&
- nro_size == header.rw_offset + header.rw_size &&
- Common::Is4KBAligned(header.text_size) && Common::Is4KBAligned(header.ro_size) &&
- Common::Is4KBAligned(header.rw_size);
+
+ header.segment_headers[RO_INDEX].memory_offset ==
+ header.segment_headers[TEXT_INDEX].memory_offset +
+ header.segment_headers[TEXT_INDEX].memory_size &&
+
+ header.segment_headers[DATA_INDEX].memory_offset ==
+ header.segment_headers[RO_INDEX].memory_offset +
+ header.segment_headers[RO_INDEX].memory_size &&
+
+ nro_size == header.segment_headers[DATA_INDEX].memory_offset +
+ header.segment_headers[DATA_INDEX].memory_size &&
+
+ Common::Is4KBAligned(header.segment_headers[TEXT_INDEX].memory_size) &&
+ Common::Is4KBAligned(header.segment_headers[RO_INDEX].memory_size) &&
+ Common::Is4KBAligned(header.segment_headers[DATA_INDEX].memory_size);
}
Core::System& system;
};
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index 346c8f899..49a42a9c9 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/lm/lm.h"
#include "core/hle/service/lm/manager.h"
@@ -17,7 +18,7 @@ namespace Service::LM {
class ILogger final : public ServiceFramework<ILogger> {
public:
- explicit ILogger(Manager& manager_, Memory::Memory& memory_)
+ explicit ILogger(Manager& manager_, Core::Memory::Memory& memory_)
: ServiceFramework("ILogger"), manager{manager_}, memory{memory_} {
static const FunctionInfo functions[] = {
{0, &ILogger::Log, "Log"},
@@ -75,12 +76,12 @@ private:
}
Manager& manager;
- Memory::Memory& memory;
+ Core::Memory::Memory& memory;
};
class LM final : public ServiceFramework<LM> {
public:
- explicit LM(Manager& manager_, Memory::Memory& memory_)
+ explicit LM(Manager& manager_, Core::Memory::Memory& memory_)
: ServiceFramework{"lm"}, manager{manager_}, memory{memory_} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -101,7 +102,7 @@ private:
}
Manager& manager;
- Memory::Memory& memory;
+ Core::Memory::Memory& memory;
};
void InstallInterfaces(Core::System& system) {
diff --git a/src/core/hle/service/lm/manager.cpp b/src/core/hle/service/lm/manager.cpp
index b67081b86..3ee2374e7 100644
--- a/src/core/hle/service/lm/manager.cpp
+++ b/src/core/hle/service/lm/manager.cpp
@@ -86,7 +86,8 @@ std::string FormatField(Field type, const std::vector<u8>& data) {
return Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(data.data()), data.size());
default:
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented field type={}", type);
+ return "";
}
}
diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp
index d16367f2c..113a4665c 100644
--- a/src/core/hle/service/mig/mig.cpp
+++ b/src/core/hle/service/mig/mig.cpp
@@ -20,6 +20,12 @@ public:
{101, nullptr, "ResumeServer"},
{200, nullptr, "CreateClient"},
{201, nullptr, "ResumeClient"},
+ {1001, nullptr, "Unknown1001"},
+ {1010, nullptr, "Unknown1010"},
+ {1100, nullptr, "Unknown1100"},
+ {1101, nullptr, "Unknown1101"},
+ {1200, nullptr, "Unknown1200"},
+ {1201, nullptr, "Unknown1201"}
};
// clang-format on
diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp
new file mode 100644
index 000000000..d73b90015
--- /dev/null
+++ b/src/core/hle/service/mii/manager.cpp
@@ -0,0 +1,484 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <random>
+
+#include "common/assert.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/mii/manager.h"
+#include "core/hle/service/mii/raw_data.h"
+#include "core/hle/service/mii/types.h"
+
+namespace Service::Mii {
+
+namespace {
+
+constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
+
+constexpr std::size_t DefaultMiiCount{sizeof(RawData::DefaultMii) / sizeof(DefaultMii)};
+
+constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'};
+constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
+constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
+constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
+constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
+constexpr std::array<u8, 62> EyeRotateLookup{
+ {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
+ 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
+ 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
+constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
+ 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
+ 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
+
+template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
+std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
+ std::array<T, DestArraySize> out{};
+ std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
+ return out;
+}
+
+MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
+ MiiStoreBitFields bf;
+ std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
+
+ return {
+ .uuid = data.data.uuid,
+ .name = ResizeArray<char16_t, 10, 11>(data.data.name),
+ .font_region = static_cast<u8>(bf.font_region.Value()),
+ .favorite_color = static_cast<u8>(bf.favorite_color.Value()),
+ .gender = static_cast<u8>(bf.gender.Value()),
+ .height = static_cast<u8>(bf.height.Value()),
+ .build = static_cast<u8>(bf.build.Value()),
+ .type = static_cast<u8>(bf.type.Value()),
+ .region_move = static_cast<u8>(bf.region_move.Value()),
+ .faceline_type = static_cast<u8>(bf.faceline_type.Value()),
+ .faceline_color = static_cast<u8>(bf.faceline_color.Value()),
+ .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
+ .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
+ .hair_type = static_cast<u8>(bf.hair_type.Value()),
+ .hair_color = static_cast<u8>(bf.hair_color.Value()),
+ .hair_flip = static_cast<u8>(bf.hair_flip.Value()),
+ .eye_type = static_cast<u8>(bf.eye_type.Value()),
+ .eye_color = static_cast<u8>(bf.eye_color.Value()),
+ .eye_scale = static_cast<u8>(bf.eye_scale.Value()),
+ .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
+ .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
+ .eye_x = static_cast<u8>(bf.eye_x.Value()),
+ .eye_y = static_cast<u8>(bf.eye_y.Value()),
+ .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
+ .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
+ .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
+ .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
+ .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
+ .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
+ .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
+ .nose_type = static_cast<u8>(bf.nose_type.Value()),
+ .nose_scale = static_cast<u8>(bf.nose_scale.Value()),
+ .nose_y = static_cast<u8>(bf.nose_y.Value()),
+ .mouth_type = static_cast<u8>(bf.mouth_type.Value()),
+ .mouth_color = static_cast<u8>(bf.mouth_color.Value()),
+ .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
+ .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
+ .mouth_y = static_cast<u8>(bf.mouth_y.Value()),
+ .beard_color = static_cast<u8>(bf.beard_color.Value()),
+ .beard_type = static_cast<u8>(bf.beard_type.Value()),
+ .mustache_type = static_cast<u8>(bf.mustache_type.Value()),
+ .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
+ .mustache_y = static_cast<u8>(bf.mustache_y.Value()),
+ .glasses_type = static_cast<u8>(bf.glasses_type.Value()),
+ .glasses_color = static_cast<u8>(bf.glasses_color.Value()),
+ .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
+ .glasses_y = static_cast<u8>(bf.glasses_y.Value()),
+ .mole_type = static_cast<u8>(bf.mole_type.Value()),
+ .mole_scale = static_cast<u8>(bf.mole_scale.Value()),
+ .mole_x = static_cast<u8>(bf.mole_x.Value()),
+ .mole_y = static_cast<u8>(bf.mole_y.Value()),
+ };
+}
+
+u16 GenerateCrc16(const void* data, std::size_t size) {
+ s32 crc{};
+ for (std::size_t i = 0; i < size; i++) {
+ crc ^= static_cast<const u8*>(data)[i] << 8;
+ for (std::size_t j = 0; j < 8; j++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = (crc ^ 0x1021) & 0xFFFF;
+ }
+ }
+ }
+ return Common::swap16(static_cast<u16>(crc));
+}
+
+Common::UUID GenerateValidUUID() {
+ auto uuid{Common::UUID::Generate()};
+
+ // Bit 7 must be set, and bit 6 unset for the UUID to be valid
+ uuid.uuid[1] &= 0xFFFFFFFFFFFFFF3FULL;
+ uuid.uuid[1] |= 0x0000000000000080ULL;
+
+ return uuid;
+}
+
+template <typename T>
+T GetRandomValue(T min, T max) {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
+ return static_cast<T>(distribution(gen));
+}
+
+template <typename T>
+T GetRandomValue(T max) {
+ return GetRandomValue<T>({}, max);
+}
+
+template <typename T>
+T GetArrayValue(const u8* data, std::size_t index) {
+ T result{};
+ std::memcpy(&result, &data[index * sizeof(T)], sizeof(T));
+ return result;
+}
+
+MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
+ MiiStoreBitFields bf{};
+
+ if (gender == Gender::All) {
+ gender = GetRandomValue<Gender>(Gender::Maximum);
+ }
+
+ bf.gender.Assign(gender);
+ bf.favorite_color.Assign(GetRandomValue<u8>(11));
+ bf.region_move.Assign(0);
+ bf.font_region.Assign(FontRegion::Standard);
+ bf.type.Assign(0);
+ bf.height.Assign(64);
+ bf.build.Assign(64);
+
+ if (age == Age::All) {
+ const auto temp{GetRandomValue<int>(10)};
+ if (temp >= 8) {
+ age = Age::Old;
+ } else if (temp >= 4) {
+ age = Age::Normal;
+ } else {
+ age = Age::Young;
+ }
+ }
+
+ if (race == Race::All) {
+ const auto temp{GetRandomValue<int>(10)};
+ if (temp >= 8) {
+ race = Race::Black;
+ } else if (temp >= 4) {
+ race = Race::White;
+ } else {
+ race = Race::Asian;
+ }
+ }
+
+ u32 axis_y{};
+ if (gender == Gender::Female && age == Age::Young) {
+ axis_y = GetRandomValue<u32>(3);
+ }
+
+ const std::size_t index{3 * static_cast<std::size_t>(age) +
+ 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
+
+ const auto faceline_type_info{
+ GetArrayValue<RandomMiiData4>(&RawData::RandomMiiFaceline[0], index)};
+ const auto faceline_color_info{GetArrayValue<RandomMiiData3>(
+ RawData::RandomMiiFacelineColor.data(),
+ 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
+ const auto faceline_wrinkle_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineWrinkle.data(), index)};
+ const auto faceline_makeup_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineMakeup.data(), index)};
+ const auto hair_type_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiHairType.data(), index)};
+ const auto hair_color_info{GetArrayValue<RandomMiiData3>(RawData::RandomMiiHairColor.data(),
+ 3 * static_cast<std::size_t>(race) +
+ static_cast<std::size_t>(age))};
+ const auto eye_type_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyeType.data(), index)};
+ const auto eye_color_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiEyeColor.data(),
+ static_cast<std::size_t>(race))};
+ const auto eyebrow_type_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyebrowType.data(), index)};
+ const auto nose_type_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiNoseType.data(), index)};
+ const auto mouth_type_info{
+ GetArrayValue<RandomMiiData4>(RawData::RandomMiiMouthType.data(), index)};
+ const auto glasses_type_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiGlassType.data(),
+ static_cast<std::size_t>(age))};
+
+ bf.faceline_type.Assign(
+ faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
+ bf.faceline_color.Assign(
+ faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
+ bf.faceline_wrinkle.Assign(
+ faceline_wrinkle_info
+ .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
+ bf.faceline_makeup.Assign(
+ faceline_makeup_info
+ .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
+
+ bf.hair_type.Assign(
+ hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
+ bf.hair_color.Assign(
+ HairColorLookup[hair_color_info
+ .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
+ bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
+
+ bf.eye_type.Assign(
+ eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
+
+ const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
+ const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
+ const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
+ const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
+
+ bf.eye_color.Assign(
+ EyeColorLookup[eye_color_info
+ .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
+ bf.eye_scale.Assign(4);
+ bf.eye_aspect.Assign(3);
+ bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
+ bf.eye_x.Assign(2);
+ bf.eye_y.Assign(axis_y + 12);
+
+ bf.eyebrow_type.Assign(
+ eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
+
+ const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
+ const auto eyebrow_y{race == Race::Asian ? 9 : 10};
+ const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
+ const auto eyebrow_rotate{
+ 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
+
+ bf.eyebrow_color.Assign(bf.hair_color);
+ bf.eyebrow_scale.Assign(4);
+ bf.eyebrow_aspect.Assign(3);
+ bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
+ bf.eyebrow_x.Assign(2);
+ bf.eyebrow_y.Assign(axis_y + eyebrow_y);
+
+ const auto nose_scale{gender == Gender::Female ? 3 : 4};
+
+ bf.nose_type.Assign(
+ nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
+ bf.nose_scale.Assign(nose_scale);
+ bf.nose_y.Assign(axis_y + 9);
+
+ const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
+
+ bf.mouth_type.Assign(
+ mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
+ bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
+ bf.mouth_scale.Assign(4);
+ bf.mouth_aspect.Assign(3);
+ bf.mouth_y.Assign(axis_y + 13);
+
+ bf.beard_color.Assign(bf.hair_color);
+ bf.mustache_scale.Assign(4);
+
+ if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
+ const auto mustache_and_beard_flag{
+ GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
+
+ auto beard_type{BeardType::None};
+ auto mustache_type{MustacheType::None};
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
+ BeardAndMustacheFlag::Beard) {
+ beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
+ }
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
+ BeardAndMustacheFlag::Mustache) {
+ mustache_type =
+ GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
+ }
+
+ bf.mustache_type.Assign(mustache_type);
+ bf.beard_type.Assign(beard_type);
+ bf.mustache_y.Assign(10);
+ } else {
+ bf.mustache_type.Assign(MustacheType::None);
+ bf.beard_type.Assign(BeardType::None);
+ bf.mustache_y.Assign(axis_y + 10);
+ }
+
+ const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
+ u8 glasses_type{};
+ while (glasses_type_start < glasses_type_info.values[glasses_type]) {
+ if (++glasses_type >= glasses_type_info.values_count) {
+ UNREACHABLE();
+ break;
+ }
+ }
+
+ bf.glasses_type.Assign(glasses_type);
+ bf.glasses_color.Assign(GlassesColorLookup[0]);
+ bf.glasses_scale.Assign(4);
+ bf.glasses_y.Assign(axis_y + 10);
+
+ bf.mole_type.Assign(0);
+ bf.mole_scale.Assign(4);
+ bf.mole_x.Assign(2);
+ bf.mole_y.Assign(20);
+
+ return {DefaultMiiName, bf, user_id};
+}
+
+MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
+ MiiStoreBitFields bf{};
+
+ bf.font_region.Assign(info.font_region);
+ bf.favorite_color.Assign(info.favorite_color);
+ bf.gender.Assign(info.gender);
+ bf.height.Assign(info.height);
+ bf.build.Assign(info.weight);
+ bf.type.Assign(info.type);
+ bf.region_move.Assign(info.region);
+ bf.faceline_type.Assign(info.face_type);
+ bf.faceline_color.Assign(info.face_color);
+ bf.faceline_wrinkle.Assign(info.face_wrinkle);
+ bf.faceline_makeup.Assign(info.face_makeup);
+ bf.hair_type.Assign(info.hair_type);
+ bf.hair_color.Assign(HairColorLookup[info.hair_color]);
+ bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
+ bf.eye_type.Assign(info.eye_type);
+ bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
+ bf.eye_scale.Assign(info.eye_scale);
+ bf.eye_aspect.Assign(info.eye_aspect);
+ bf.eye_rotate.Assign(info.eye_rotate);
+ bf.eye_x.Assign(info.eye_x);
+ bf.eye_y.Assign(info.eye_y);
+ bf.eyebrow_type.Assign(info.eyebrow_type);
+ bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
+ bf.eyebrow_scale.Assign(info.eyebrow_scale);
+ bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
+ bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
+ bf.eyebrow_x.Assign(info.eyebrow_x);
+ bf.eyebrow_y.Assign(info.eyebrow_y - 3);
+ bf.nose_type.Assign(info.nose_type);
+ bf.nose_scale.Assign(info.nose_scale);
+ bf.nose_y.Assign(info.nose_y);
+ bf.mouth_type.Assign(info.mouth_type);
+ bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
+ bf.mouth_scale.Assign(info.mouth_scale);
+ bf.mouth_aspect.Assign(info.mouth_aspect);
+ bf.mouth_y.Assign(info.mouth_y);
+ bf.beard_color.Assign(HairColorLookup[info.beard_color]);
+ bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
+ bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
+ bf.mustache_scale.Assign(info.mustache_scale);
+ bf.mustache_y.Assign(info.mustache_y);
+ bf.glasses_type.Assign(info.glasses_type);
+ bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
+ bf.glasses_scale.Assign(info.glasses_scale);
+ bf.glasses_y.Assign(info.glasses_y);
+ bf.mole_type.Assign(info.mole_type);
+ bf.mole_scale.Assign(info.mole_scale);
+ bf.mole_x.Assign(info.mole_x);
+ bf.mole_y.Assign(info.mole_y);
+
+ return {DefaultMiiName, bf, user_id};
+}
+
+} // namespace
+
+MiiStoreData::MiiStoreData() = default;
+
+MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
+ const Common::UUID& user_id) {
+ data.name = name;
+ data.uuid = GenerateValidUUID();
+
+ std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
+ data_crc = GenerateCrc16(data.data.data(), sizeof(data));
+ device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
+}
+
+MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {}
+
+bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return false;
+ }
+
+ const bool result{current_update_counter != update_counter};
+
+ current_update_counter = update_counter;
+
+ return result;
+}
+
+bool MiiManager::IsFullDatabase() const {
+ // TODO(bunnei): We don't implement the Mii database, so it cannot be full
+ return false;
+}
+
+u32 MiiManager::GetCount(SourceFlag source_flag) const {
+ std::size_t count{};
+ if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
+ // TODO(bunnei): We don't implement the Mii database, but when we do, update this
+ count += 0;
+ }
+ if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
+ count += DefaultMiiCount;
+ }
+ return static_cast<u32>(count);
+}
+
+ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info,
+ SourceFlag source_flag) {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return ERROR_CANNOT_FIND_ENTRY;
+ }
+
+ // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
+ return ERROR_CANNOT_FIND_ENTRY;
+}
+
+MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
+ return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
+}
+
+MiiInfo MiiManager::BuildDefault(std::size_t index) {
+ return ConvertStoreDataToInfo(BuildDefaultStoreData(
+ GetArrayValue<DefaultMii>(RawData::DefaultMii.data(), index), user_id));
+}
+
+ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
+ std::vector<MiiInfoElement> result;
+
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return MakeResult(std::move(result));
+ }
+
+ for (std::size_t index = 0; index < DefaultMiiCount; index++) {
+ result.emplace_back(BuildDefault(index), Source::Default);
+ }
+
+ return MakeResult(std::move(result));
+}
+
+ResultCode MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) {
+ constexpr u32 INVALID_INDEX{0xFFFFFFFF};
+
+ index = INVALID_INDEX;
+
+ // TODO(bunnei): We don't implement the Mii database, so we can't have an index
+ return ERROR_CANNOT_FIND_ENTRY;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/manager.h b/src/core/hle/service/mii/manager.h
new file mode 100644
index 000000000..927451dea
--- /dev/null
+++ b/src/core/hle/service/mii/manager.h
@@ -0,0 +1,331 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/uuid.h"
+#include "core/hle/result.h"
+#include "core/hle/service/mii/types.h"
+
+namespace Service::Mii {
+
+enum class Source : u32 {
+ Database = 0,
+ Default = 1,
+ Account = 2,
+ Friend = 3,
+};
+
+enum class SourceFlag : u32 {
+ None = 0,
+ Database = 1 << 0,
+ Default = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
+
+struct MiiInfo {
+ Common::UUID uuid{Common::INVALID_UUID};
+ std::array<char16_t, 11> name{};
+ u8 font_region{};
+ u8 favorite_color{};
+ u8 gender{};
+ u8 height{};
+ u8 build{};
+ u8 type{};
+ u8 region_move{};
+ u8 faceline_type{};
+ u8 faceline_color{};
+ u8 faceline_wrinkle{};
+ u8 faceline_make{};
+ u8 hair_type{};
+ u8 hair_color{};
+ u8 hair_flip{};
+ u8 eye_type{};
+ u8 eye_color{};
+ u8 eye_scale{};
+ u8 eye_aspect{};
+ u8 eye_rotate{};
+ u8 eye_x{};
+ u8 eye_y{};
+ u8 eyebrow_type{};
+ u8 eyebrow_color{};
+ u8 eyebrow_scale{};
+ u8 eyebrow_aspect{};
+ u8 eyebrow_rotate{};
+ u8 eyebrow_x{};
+ u8 eyebrow_y{};
+ u8 nose_type{};
+ u8 nose_scale{};
+ u8 nose_y{};
+ u8 mouth_type{};
+ u8 mouth_color{};
+ u8 mouth_scale{};
+ u8 mouth_aspect{};
+ u8 mouth_y{};
+ u8 beard_color{};
+ u8 beard_type{};
+ u8 mustache_type{};
+ u8 mustache_scale{};
+ u8 mustache_y{};
+ u8 glasses_type{};
+ u8 glasses_color{};
+ u8 glasses_scale{};
+ u8 glasses_y{};
+ u8 mole_type{};
+ u8 mole_scale{};
+ u8 mole_x{};
+ u8 mole_y{};
+ INSERT_PADDING_BYTES(1);
+
+ std::u16string Name() const;
+};
+static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<MiiInfo>,
+ "All bits of MiiInfo must contribute to its value.");
+
+#pragma pack(push, 4)
+
+struct MiiInfoElement {
+ MiiInfoElement(const MiiInfo& info, Source source) : info{info}, source{source} {}
+
+ MiiInfo info{};
+ Source source{};
+};
+static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
+
+struct MiiStoreBitFields {
+ union {
+ u32 word_0{};
+
+ BitField<0, 8, u32> hair_type;
+ BitField<8, 7, u32> height;
+ BitField<15, 1, u32> mole_type;
+ BitField<16, 7, u32> build;
+ BitField<23, 1, HairFlip> hair_flip;
+ BitField<24, 7, u32> hair_color;
+ BitField<31, 1, u32> type;
+ };
+
+ union {
+ u32 word_1{};
+
+ BitField<0, 7, u32> eye_color;
+ BitField<7, 1, Gender> gender;
+ BitField<8, 7, u32> eyebrow_color;
+ BitField<16, 7, u32> mouth_color;
+ BitField<24, 7, u32> beard_color;
+ };
+
+ union {
+ u32 word_2{};
+
+ BitField<0, 7, u32> glasses_color;
+ BitField<8, 6, u32> eye_type;
+ BitField<14, 2, u32> region_move;
+ BitField<16, 6, u32> mouth_type;
+ BitField<22, 2, FontRegion> font_region;
+ BitField<24, 5, u32> eye_y;
+ BitField<29, 3, u32> glasses_scale;
+ };
+
+ union {
+ u32 word_3{};
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, MustacheType> mustache_type;
+ BitField<8, 5, u32> nose_type;
+ BitField<13, 3, BeardType> beard_type;
+ BitField<16, 5, u32> nose_y;
+ BitField<21, 3, u32> mouth_aspect;
+ BitField<24, 5, u32> mouth_y;
+ BitField<29, 3, u32> eyebrow_aspect;
+ };
+
+ union {
+ u32 word_4{};
+
+ BitField<0, 5, u32> mustache_y;
+ BitField<5, 3, u32> eye_rotate;
+ BitField<8, 5, u32> glasses_y;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> mole_x;
+ BitField<21, 3, u32> eye_scale;
+ BitField<24, 5, u32> mole_y;
+ };
+
+ union {
+ u32 word_5{};
+
+ BitField<0, 5, u32> glasses_type;
+ BitField<8, 4, u32> favorite_color;
+ BitField<12, 4, u32> faceline_type;
+ BitField<16, 4, u32> faceline_color;
+ BitField<20, 4, u32> faceline_wrinkle;
+ BitField<24, 4, u32> faceline_makeup;
+ BitField<28, 4, u32> eye_x;
+ };
+
+ union {
+ u32 word_6{};
+
+ BitField<0, 4, u32> eyebrow_scale;
+ BitField<4, 4, u32> eyebrow_rotate;
+ BitField<8, 4, u32> eyebrow_x;
+ BitField<12, 4, u32> eyebrow_y;
+ BitField<16, 4, u32> nose_scale;
+ BitField<20, 4, u32> mouth_scale;
+ BitField<24, 4, u32> mustache_scale;
+ BitField<28, 4, u32> mole_scale;
+ };
+};
+static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
+static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
+ "MiiStoreBitFields is not trivially copyable.");
+
+struct MiiStoreData {
+ using Name = std::array<char16_t, 10>;
+
+ MiiStoreData();
+ MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
+ const Common::UUID& user_id);
+
+ // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
+ // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
+ // not suitable for our uses.
+ struct {
+ std::array<u8, 0x1C> data{};
+ static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
+
+ Name name{};
+ Common::UUID uuid{Common::INVALID_UUID};
+ } data;
+
+ u16 data_crc{};
+ u16 device_crc{};
+};
+static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
+
+struct MiiStoreDataElement {
+ MiiStoreData data{};
+ Source source{};
+};
+static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
+
+struct MiiDatabase {
+ u32 magic{}; // 'NFDB'
+ std::array<MiiStoreData, 0x64> miis{};
+ INSERT_PADDING_BYTES(1);
+ u8 count{};
+ u16 crc{};
+};
+static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
+
+struct RandomMiiValues {
+ std::array<u8, 0xbc> values{};
+};
+static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
+
+struct RandomMiiData4 {
+ Gender gender{};
+ Age age{};
+ Race race{};
+ u32 values_count{};
+ std::array<u8, 0xbc> values{};
+};
+static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
+
+struct RandomMiiData3 {
+ u32 arg_1;
+ u32 arg_2;
+ u32 values_count;
+ std::array<u8, 0xbc> values{};
+};
+static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
+
+struct RandomMiiData2 {
+ u32 arg_1;
+ u32 values_count;
+ std::array<u8, 0xbc> values{};
+};
+static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
+
+struct DefaultMii {
+ u32 face_type{};
+ u32 face_color{};
+ u32 face_wrinkle{};
+ u32 face_makeup{};
+ u32 hair_type{};
+ u32 hair_color{};
+ u32 hair_flip{};
+ u32 eye_type{};
+ u32 eye_color{};
+ u32 eye_scale{};
+ u32 eye_aspect{};
+ u32 eye_rotate{};
+ u32 eye_x{};
+ u32 eye_y{};
+ u32 eyebrow_type{};
+ u32 eyebrow_color{};
+ u32 eyebrow_scale{};
+ u32 eyebrow_aspect{};
+ u32 eyebrow_rotate{};
+ u32 eyebrow_x{};
+ u32 eyebrow_y{};
+ u32 nose_type{};
+ u32 nose_scale{};
+ u32 nose_y{};
+ u32 mouth_type{};
+ u32 mouth_color{};
+ u32 mouth_scale{};
+ u32 mouth_aspect{};
+ u32 mouth_y{};
+ u32 mustache_type{};
+ u32 beard_type{};
+ u32 beard_color{};
+ u32 mustache_scale{};
+ u32 mustache_y{};
+ u32 glasses_type{};
+ u32 glasses_color{};
+ u32 glasses_scale{};
+ u32 glasses_y{};
+ u32 mole_type{};
+ u32 mole_scale{};
+ u32 mole_x{};
+ u32 mole_y{};
+ u32 height{};
+ u32 weight{};
+ Gender gender{};
+ u32 favorite_color{};
+ u32 region{};
+ FontRegion font_region{};
+ u32 type{};
+ INSERT_PADDING_WORDS(5);
+};
+static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
+
+#pragma pack(pop)
+
+// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
+// with providing an easy interface for HLE emulation of the mii service.
+class MiiManager {
+public:
+ MiiManager();
+
+ bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
+ bool IsFullDatabase() const;
+ u32 GetCount(SourceFlag source_flag) const;
+ ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag);
+ MiiInfo BuildRandom(Age age, Gender gender, Race race);
+ MiiInfo BuildDefault(std::size_t index);
+ ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
+ ResultCode GetIndex(const MiiInfo& info, u32& index);
+
+private:
+ const Common::UUID user_id;
+ u64 update_counter{};
+};
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index a128edb43..d7080b715 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -4,22 +4,17 @@
#include <memory>
-#include <fmt/ostream.h>
-
#include "common/logging/log.h"
-#include "common/string_util.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/mii/manager.h"
#include "core/hle/service/mii/mii.h"
-#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::Mii {
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
-constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
-constexpr ResultCode ERROR_NOT_IN_TEST_MODE{ErrorModule::Mii, 99};
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
@@ -31,19 +26,19 @@ public:
{2, &IDatabaseService::GetCount, "GetCount"},
{3, &IDatabaseService::Get, "Get"},
{4, &IDatabaseService::Get1, "Get1"},
- {5, nullptr, "UpdateLatest"},
+ {5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
- {8, &IDatabaseService::Get2, "Get2"},
- {9, &IDatabaseService::Get3, "Get3"},
+ {8, nullptr, "Get2"},
+ {9, nullptr, "Get3"},
{10, nullptr, "UpdateLatest1"},
- {11, &IDatabaseService::FindIndex, "FindIndex"},
- {12, &IDatabaseService::Move, "Move"},
- {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
- {14, &IDatabaseService::Delete, "Delete"},
- {15, &IDatabaseService::DestroyFile, "DestroyFile"},
- {16, &IDatabaseService::DeleteFile, "DeleteFile"},
- {17, &IDatabaseService::Format, "Format"},
+ {11, nullptr, "FindIndex"},
+ {12, nullptr, "Move"},
+ {13, nullptr, "AddOrReplace"},
+ {14, nullptr, "Delete"},
+ {15, nullptr, "DestroyFile"},
+ {16, nullptr, "DeleteFile"},
+ {17, nullptr, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
@@ -52,6 +47,7 @@ public:
{23, nullptr, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
+ {26, nullptr, "Append"},
};
// clang-format on
@@ -59,31 +55,26 @@ public:
}
private:
- template <typename OutType>
- std::vector<u8> SerializeArray(OutType (MiiManager::*getter)(u32) const, u32 offset,
- u32 requested_size, u32& read_size) {
- read_size = std::min(requested_size, db.Size() - offset);
-
- std::vector<u8> out(read_size * sizeof(OutType));
-
- for (u32 i = 0; i < read_size; ++i) {
- const auto obj = (db.*getter)(offset + i);
- std::memcpy(out.data() + i * sizeof(OutType), &obj, sizeof(OutType));
+ template <typename T>
+ std::vector<u8> SerializeArray(const std::vector<T>& values) {
+ std::vector<u8> out(values.size() * sizeof(T));
+ std::size_t offset{};
+ for (const auto& value : values) {
+ std::memcpy(out.data() + offset, &value, sizeof(T));
+ offset += sizeof(T);
}
-
return out;
}
void IsUpdated(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto source{rp.PopRaw<Source>()};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source={}", source);
+ LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(db.CheckUpdatedFlag());
- db.ResetUpdatedFlag();
+ rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter));
}
void IsFullDatabase(Kernel::HLERequestContext& ctx) {
@@ -91,264 +82,149 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(db.Full());
+ rb.Push(manager.IsFullDatabase());
}
void GetCount(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto source{rp.PopRaw<Source>()};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source={}", source);
+ LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(db.Size());
+ rb.Push<u32>(manager.GetCount(source_flag));
}
- // Gets Miis from database at offset and index in format MiiInfoElement
void Get(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto size{rp.PopRaw<u32>()};
- const auto source{rp.PopRaw<Source>()};
-
- LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
- offsets[0], source);
-
- u32 read_size{};
- ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfoElement, offsets[0], size, read_size));
- offsets[0] += read_size;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(read_size);
- }
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
- // Gets Miis from database at offset and index in format MiiInfo
- void Get1(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto size{rp.PopRaw<u32>()};
- const auto source{rp.PopRaw<Source>()};
+ LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
- LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
- offsets[1], source);
+ const auto result{manager.GetDefault(source_flag)};
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
- u32 read_size{};
- ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfo, offsets[1], size, read_size));
- offsets[1] += read_size;
+ if (result->size() > 0) {
+ ctx.WriteBuffer(SerializeArray(*result));
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(read_size);
+ rb.Push<u32>(static_cast<u32>(result->size()));
}
- void BuildRandom(Kernel::HLERequestContext& ctx) {
+ void Get1(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto [unknown1, unknown2, unknown3] = rp.PopRaw<RandomParameters>();
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
- if (unknown1 > 3) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "Invalid unknown1 value: {}", unknown1);
- return;
- }
+ LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
- if (unknown2 > 2) {
+ const auto result{manager.GetDefault(source_flag)};
+ if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "Invalid unknown2 value: {}", unknown2);
+ rb.Push(result.Code());
return;
}
- if (unknown3 > 3) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "Invalid unknown3 value: {}", unknown3);
- return;
+ std::vector<MiiInfo> values;
+ for (const auto& element : *result) {
+ values.emplace_back(element.info);
}
- LOG_DEBUG(Service_Mii, "called with param_1={:08X}, param_2={:08X}, param_3={:08X}",
- unknown1, unknown2, unknown3);
+ ctx.WriteBuffer(SerializeArray(values));
- const auto info = db.CreateRandom({unknown1, unknown2, unknown3});
- IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<MiiInfo>(info);
+ rb.Push<u32>(static_cast<u32>(result->size()));
}
- void BuildDefault(Kernel::HLERequestContext& ctx) {
+ void UpdateLatest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto index{rp.PopRaw<u32>()};
+ const auto info{rp.PopRaw<MiiInfo>()};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
- if (index > 5) {
- LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
- index);
+ LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+
+ const auto result{manager.UpdateLatest(info, source_flag)};
+ if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
+ rb.Push(result.Code());
return;
}
- LOG_DEBUG(Service_Mii, "called with index={:08X}", index);
-
- const auto info = db.CreateDefault(index);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<MiiInfo>(info);
+ rb.PushRaw<MiiInfo>(*result);
}
- // Gets Miis from database at offset and index in format MiiStoreDataElement
- void Get2(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto size{rp.PopRaw<u32>()};
- const auto source{rp.PopRaw<Source>()};
-
- LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
- offsets[2], source);
-
- u32 read_size{};
- ctx.WriteBuffer(
- SerializeArray(&MiiManager::GetStoreDataElement, offsets[2], size, read_size));
- offsets[2] += read_size;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(read_size);
- }
-
- // Gets Miis from database at offset and index in format MiiStoreData
- void Get3(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto size{rp.PopRaw<u32>()};
- const auto source{rp.PopRaw<Source>()};
-
- LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
- offsets[3], source);
-
- u32 read_size{};
- ctx.WriteBuffer(SerializeArray(&MiiManager::GetStoreData, offsets[3], size, read_size));
- offsets[3] += read_size;
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(read_size);
- }
-
- void FindIndex(Kernel::HLERequestContext& ctx) {
+ void BuildRandom(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto uuid{rp.PopRaw<Common::UUID>()};
- const auto unknown{rp.PopRaw<bool>()};
- LOG_DEBUG(Service_Mii, "called with uuid={}, unknown={}", uuid.FormatSwitch(), unknown);
+ const auto age{rp.PopRaw<Age>()};
+ const auto gender{rp.PopRaw<Gender>()};
+ const auto race{rp.PopRaw<Race>()};
- IPC::ResponseBuilder rb{ctx, 3};
+ LOG_DEBUG(Service_Mii, "called with age={}, gender={}, race={}", age, gender, race);
- const auto index = db.IndexOf(uuid);
- if (index > MAX_MIIS) {
- // TODO(DarkLordZach): Find a better error code
- rb.Push(RESULT_UNKNOWN);
- rb.Push(index);
- } else {
- rb.Push(RESULT_SUCCESS);
- rb.Push(index);
+ if (age > Age::All) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "invalid age={}", age);
+ return;
}
- }
- void Move(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto uuid{rp.PopRaw<Common::UUID>()};
- const auto index{rp.PopRaw<s32>()};
-
- if (index < 0) {
- LOG_ERROR(Service_Mii, "Index cannot be negative but is {:08X}!", index);
+ if (gender > Gender::All) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "invalid gender={}", gender);
return;
}
- LOG_DEBUG(Service_Mii, "called with uuid={}, index={:08X}", uuid.FormatSwitch(), index);
-
- const auto success = db.Move(uuid, index);
-
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code
- rb.Push(success ? RESULT_SUCCESS : RESULT_UNKNOWN);
- }
-
- void AddOrReplace(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto data{rp.PopRaw<MiiStoreData>()};
-
- LOG_DEBUG(Service_Mii, "called with Mii data uuid={}, name={}", data.uuid.FormatSwitch(),
- Common::UTF16ToUTF8(data.Name()));
-
- const auto success = db.AddOrReplace(data);
-
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code
- rb.Push(success ? RESULT_SUCCESS : RESULT_UNKNOWN);
- }
-
- void Delete(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto uuid{rp.PopRaw<Common::UUID>()};
-
- LOG_DEBUG(Service_Mii, "called with uuid={}", uuid.FormatSwitch());
-
- const auto success = db.Remove(uuid);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(success ? RESULT_SUCCESS : ERROR_CANNOT_FIND_ENTRY);
- }
-
- void DestroyFile(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Mii, "called");
-
- if (!db.IsTestModeEnabled()) {
- LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot destory database file.");
+ if (race > Race::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NOT_IN_TEST_MODE);
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "invalid race={}", race);
return;
}
- IPC::ResponseBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
rb.Push(RESULT_SUCCESS);
- rb.Push(db.DestroyFile());
+ rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race));
}
- void DeleteFile(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Mii, "called");
+ void BuildDefault(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto index{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_Mii, "called with index={}", index);
- if (!db.IsTestModeEnabled()) {
- LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot delete database file.");
+ if (index > 5) {
+ LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
+ index);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NOT_IN_TEST_MODE);
+ rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(db.DeleteFile());
- }
-
- void Format(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Mii, "called");
-
- db.Clear();
-
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<MiiInfo>(manager.BuildDefault(index));
}
void GetIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<MiiInfo>()};
- LOG_DEBUG(Service_Mii, "called with Mii info uuid={}, name={}", info.uuid.FormatSwitch(),
- Common::UTF16ToUTF8(info.Name()));
-
- const auto index = db.IndexOf(info);
+ LOG_DEBUG(Service_Mii, "called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ u32 index{};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(manager.GetIndex(info, index));
rb.Push(index);
}
@@ -364,12 +240,14 @@ private:
rb.Push(RESULT_SUCCESS);
}
- MiiManager db;
+ constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
+ return current_interface_version >= interface_version;
+ }
- u32 current_interface_version = 0;
+ MiiManager manager;
- // Last read offsets of Get functions
- std::array<u32, 4> offsets{};
+ u32 current_interface_version{};
+ u64 current_update_counter{};
};
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
deleted file mode 100644
index 8d0353075..000000000
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstring>
-#include "common/assert.h"
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "common/string_util.h"
-#include "core/hle/service/mii/mii_manager.h"
-
-namespace Service::Mii {
-
-namespace {
-
-constexpr char MII_SAVE_DATABASE_PATH[] = "/system/save/8000000000000030/MiiDatabase.dat";
-constexpr std::array<char16_t, 11> DEFAULT_MII_NAME = {u'y', u'u', u'z', u'u', u'\0'};
-
-// This value was retrieved from HW test
-constexpr MiiStoreData DEFAULT_MII = {
- {
- 0x21, 0x40, 0x40, 0x01, 0x08, 0x01, 0x13, 0x08, 0x08, 0x02, 0x17, 0x8C, 0x06, 0x01,
- 0x69, 0x6D, 0x8A, 0x6A, 0x82, 0x14, 0x00, 0x00, 0x00, 0x20, 0x64, 0x72, 0x44, 0x44,
- },
- {'y', 'u', 'z', 'u', '\0'},
- Common::UUID{1, 0},
- 0,
- 0,
-};
-
-// Default values taken from multiple real databases
-const MiiDatabase DEFAULT_MII_DATABASE{Common::MakeMagic('N', 'F', 'D', 'B'), {}, {1}, 0, 0};
-
-constexpr std::array<const char*, 4> SOURCE_NAMES{
- "Database",
- "Default",
- "Account",
- "Friend",
-};
-
-template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
-std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
- std::array<T, DestArraySize> out{};
- std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
- return out;
-}
-
-MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
- MiiStoreBitFields bf{};
- std::memcpy(&bf, data.data.data(), sizeof(MiiStoreBitFields));
- return {
- data.uuid,
- ResizeArray<char16_t, 10, 11>(data.name),
- static_cast<u8>(bf.font_region.Value()),
- static_cast<u8>(bf.favorite_color.Value()),
- static_cast<u8>(bf.gender.Value()),
- static_cast<u8>(bf.height.Value()),
- static_cast<u8>(bf.weight.Value()),
- static_cast<u8>(bf.mii_type.Value()),
- static_cast<u8>(bf.mii_region.Value()),
- static_cast<u8>(bf.face_type.Value()),
- static_cast<u8>(bf.face_color.Value()),
- static_cast<u8>(bf.face_wrinkle.Value()),
- static_cast<u8>(bf.face_makeup.Value()),
- static_cast<u8>(bf.hair_type.Value()),
- static_cast<u8>(bf.hair_color.Value()),
- static_cast<bool>(bf.hair_flip.Value()),
- static_cast<u8>(bf.eye_type.Value()),
- static_cast<u8>(bf.eye_color.Value()),
- static_cast<u8>(bf.eye_scale.Value()),
- static_cast<u8>(bf.eye_aspect.Value()),
- static_cast<u8>(bf.eye_rotate.Value()),
- static_cast<u8>(bf.eye_x.Value()),
- static_cast<u8>(bf.eye_y.Value()),
- static_cast<u8>(bf.eyebrow_type.Value()),
- static_cast<u8>(bf.eyebrow_color.Value()),
- static_cast<u8>(bf.eyebrow_scale.Value()),
- static_cast<u8>(bf.eyebrow_aspect.Value()),
- static_cast<u8>(bf.eyebrow_rotate.Value()),
- static_cast<u8>(bf.eyebrow_x.Value()),
- static_cast<u8>(bf.eyebrow_y.Value()),
- static_cast<u8>(bf.nose_type.Value()),
- static_cast<u8>(bf.nose_scale.Value()),
- static_cast<u8>(bf.nose_y.Value()),
- static_cast<u8>(bf.mouth_type.Value()),
- static_cast<u8>(bf.mouth_color.Value()),
- static_cast<u8>(bf.mouth_scale.Value()),
- static_cast<u8>(bf.mouth_aspect.Value()),
- static_cast<u8>(bf.mouth_y.Value()),
- static_cast<u8>(bf.facial_hair_color.Value()),
- static_cast<u8>(bf.beard_type.Value()),
- static_cast<u8>(bf.mustache_type.Value()),
- static_cast<u8>(bf.mustache_scale.Value()),
- static_cast<u8>(bf.mustache_y.Value()),
- static_cast<u8>(bf.glasses_type.Value()),
- static_cast<u8>(bf.glasses_color.Value()),
- static_cast<u8>(bf.glasses_scale.Value()),
- static_cast<u8>(bf.glasses_y.Value()),
- static_cast<u8>(bf.mole_type.Value()),
- static_cast<u8>(bf.mole_scale.Value()),
- static_cast<u8>(bf.mole_x.Value()),
- static_cast<u8>(bf.mole_y.Value()),
- 0x00,
- };
-}
-MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) {
- MiiStoreData out{};
- out.name = ResizeArray<char16_t, 11, 10>(info.name);
- out.uuid = info.uuid;
-
- MiiStoreBitFields bf{};
-
- bf.hair_type.Assign(info.hair_type);
- bf.mole_type.Assign(info.mole_type);
- bf.height.Assign(info.height);
- bf.hair_flip.Assign(info.hair_flip);
- bf.weight.Assign(info.weight);
- bf.hair_color.Assign(info.hair_color);
-
- bf.gender.Assign(info.gender);
- bf.eye_color.Assign(info.eye_color);
- bf.eyebrow_color.Assign(info.eyebrow_color);
- bf.mouth_color.Assign(info.mouth_color);
- bf.facial_hair_color.Assign(info.facial_hair_color);
-
- bf.mii_type.Assign(info.mii_type);
- bf.glasses_color.Assign(info.glasses_color);
- bf.font_region.Assign(info.font_region);
- bf.eye_type.Assign(info.eye_type);
- bf.mii_region.Assign(info.mii_region);
- bf.mouth_type.Assign(info.mouth_type);
- bf.glasses_scale.Assign(info.glasses_scale);
- bf.eye_y.Assign(info.eye_y);
-
- bf.mustache_type.Assign(info.mustache_type);
- bf.eyebrow_type.Assign(info.eyebrow_type);
- bf.beard_type.Assign(info.beard_type);
- bf.nose_type.Assign(info.nose_type);
- bf.mouth_aspect.Assign(info.mouth_aspect_ratio);
- bf.nose_y.Assign(info.nose_y);
- bf.eyebrow_aspect.Assign(info.eyebrow_aspect_ratio);
- bf.mouth_y.Assign(info.mouth_y);
-
- bf.eye_rotate.Assign(info.eye_rotate);
- bf.mustache_y.Assign(info.mustache_y);
- bf.eye_aspect.Assign(info.eye_aspect_ratio);
- bf.glasses_y.Assign(info.glasses_y);
- bf.eye_scale.Assign(info.eye_scale);
- bf.mole_x.Assign(info.mole_x);
- bf.mole_y.Assign(info.mole_y);
-
- bf.glasses_type.Assign(info.glasses_type);
- bf.face_type.Assign(info.face_type);
- bf.favorite_color.Assign(info.favorite_color);
- bf.face_wrinkle.Assign(info.face_wrinkle);
- bf.face_color.Assign(info.face_color);
- bf.eye_x.Assign(info.eye_x);
- bf.face_makeup.Assign(info.face_makeup);
-
- bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
- bf.eyebrow_scale.Assign(info.eyebrow_scale);
- bf.eyebrow_y.Assign(info.eyebrow_y);
- bf.eyebrow_x.Assign(info.eyebrow_x);
- bf.mouth_scale.Assign(info.mouth_scale);
- bf.nose_scale.Assign(info.nose_scale);
- bf.mole_scale.Assign(info.mole_scale);
- bf.mustache_scale.Assign(info.mustache_scale);
-
- std::memcpy(out.data.data(), &bf, sizeof(MiiStoreBitFields));
-
- return out;
-}
-
-} // namespace
-
-std::ostream& operator<<(std::ostream& os, Source source) {
- if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) {
- return os << "[UNKNOWN SOURCE]";
- }
-
- os << SOURCE_NAMES.at(static_cast<std::size_t>(source));
- return os;
-}
-
-std::u16string MiiInfo::Name() const {
- return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
-}
-
-bool operator==(const MiiInfo& lhs, const MiiInfo& rhs) {
- return std::memcmp(&lhs, &rhs, sizeof(MiiInfo)) == 0;
-}
-
-bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs) {
- return !operator==(lhs, rhs);
-}
-
-std::u16string MiiStoreData::Name() const {
- return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
-}
-
-MiiManager::MiiManager() = default;
-
-MiiManager::~MiiManager() = default;
-
-MiiInfo MiiManager::CreateRandom(RandomParameters params) {
- LOG_WARNING(Service_Mii,
- "(STUBBED) called with params={:08X}{:08X}{:08X}, returning default Mii",
- params.unknown_1, params.unknown_2, params.unknown_3);
-
- return ConvertStoreDataToInfo(CreateMiiWithUniqueUUID());
-}
-
-MiiInfo MiiManager::CreateDefault(u32 index) {
- const auto new_mii = CreateMiiWithUniqueUUID();
-
- database.miis.at(index) = new_mii;
-
- EnsureDatabasePartition();
- return ConvertStoreDataToInfo(new_mii);
-}
-
-bool MiiManager::CheckUpdatedFlag() const {
- return updated_flag;
-}
-
-void MiiManager::ResetUpdatedFlag() {
- updated_flag = false;
-}
-
-bool MiiManager::IsTestModeEnabled() const {
- return is_test_mode_enabled;
-}
-
-bool MiiManager::Empty() const {
- return Size() == 0;
-}
-
-bool MiiManager::Full() const {
- return Size() == MAX_MIIS;
-}
-
-void MiiManager::Clear() {
- updated_flag = true;
- std::fill(database.miis.begin(), database.miis.end(), MiiStoreData{});
-}
-
-u32 MiiManager::Size() const {
- return static_cast<u32>(std::count_if(database.miis.begin(), database.miis.end(),
- [](const MiiStoreData& elem) { return elem.uuid; }));
-}
-
-MiiInfo MiiManager::GetInfo(u32 index) const {
- return ConvertStoreDataToInfo(GetStoreData(index));
-}
-
-MiiInfoElement MiiManager::GetInfoElement(u32 index) const {
- return {GetInfo(index), Source::Database};
-}
-
-MiiStoreData MiiManager::GetStoreData(u32 index) const {
- return database.miis.at(index);
-}
-
-MiiStoreDataElement MiiManager::GetStoreDataElement(u32 index) const {
- return {GetStoreData(index), Source::Database};
-}
-
-bool MiiManager::Remove(Common::UUID uuid) {
- const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
- [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
-
- if (iter == database.miis.end())
- return false;
-
- updated_flag = true;
- *iter = MiiStoreData{};
- EnsureDatabasePartition();
- return true;
-}
-
-u32 MiiManager::IndexOf(Common::UUID uuid) const {
- const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
- [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
-
- if (iter == database.miis.end())
- return INVALID_INDEX;
-
- return static_cast<u32>(std::distance(database.miis.begin(), iter));
-}
-
-u32 MiiManager::IndexOf(const MiiInfo& info) const {
- const auto iter =
- std::find_if(database.miis.begin(), database.miis.end(), [&info](const MiiStoreData& elem) {
- return ConvertStoreDataToInfo(elem) == info;
- });
-
- if (iter == database.miis.end())
- return INVALID_INDEX;
-
- return static_cast<u32>(std::distance(database.miis.begin(), iter));
-}
-
-bool MiiManager::Move(Common::UUID uuid, u32 new_index) {
- const auto index = IndexOf(uuid);
-
- if (index == INVALID_INDEX || new_index >= MAX_MIIS)
- return false;
-
- updated_flag = true;
- const auto moving = database.miis[index];
- const auto replacing = database.miis[new_index];
- if (replacing.uuid) {
- database.miis[index] = replacing;
- database.miis[new_index] = moving;
- } else {
- database.miis[index] = MiiStoreData{};
- database.miis[new_index] = moving;
- }
-
- EnsureDatabasePartition();
- return true;
-}
-
-bool MiiManager::AddOrReplace(const MiiStoreData& data) {
- const auto index = IndexOf(data.uuid);
-
- updated_flag = true;
- if (index == INVALID_INDEX) {
- const auto size = Size();
- if (size == MAX_MIIS)
- return false;
- database.miis[size] = data;
- } else {
- database.miis[index] = data;
- }
-
- return true;
-}
-
-bool MiiManager::DestroyFile() {
- database = DEFAULT_MII_DATABASE;
- updated_flag = false;
- return DeleteFile();
-}
-
-bool MiiManager::DeleteFile() {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
- return FileUtil::Exists(path) && FileUtil::Delete(path);
-}
-
-void MiiManager::WriteToFile() {
- const auto raw_path =
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000030";
- if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
- FileUtil::Delete(raw_path);
-
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
-
- if (!FileUtil::CreateFullPath(path)) {
- LOG_WARNING(Service_Mii,
- "Failed to create full path of MiiDatabase.dat. Create the directory "
- "nand/system/save/8000000000000030 to mitigate this "
- "issue.");
- return;
- }
-
- FileUtil::IOFile save(path, "wb");
-
- if (!save.IsOpen()) {
- LOG_WARNING(Service_Mii, "Failed to write save data to file... No changes to user data "
- "made in current session will be saved.");
- return;
- }
-
- save.Resize(sizeof(MiiDatabase));
- if (save.WriteBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
- LOG_WARNING(Service_Mii, "Failed to write all data to save file... Data may be malformed "
- "and/or regenerated on next run.");
- save.Resize(0);
- }
-}
-
-void MiiManager::ReadFromFile() {
- FileUtil::IOFile save(
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH, "rb");
-
- if (!save.IsOpen()) {
- LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
- "blank Mii database with no Miis.");
- std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
- return;
- }
-
- if (save.ReadBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
- LOG_WARNING(Service_ACC, "MiiDatabase.dat is smaller than expected... Generating new blank "
- "Mii database with no Miis.");
- std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
- return;
- }
-
- EnsureDatabasePartition();
-}
-
-MiiStoreData MiiManager::CreateMiiWithUniqueUUID() const {
- auto new_mii = DEFAULT_MII;
-
- do {
- new_mii.uuid = Common::UUID::Generate();
- } while (IndexOf(new_mii.uuid) != INVALID_INDEX);
-
- return new_mii;
-}
-
-void MiiManager::EnsureDatabasePartition() {
- std::stable_partition(database.miis.begin(), database.miis.end(),
- [](const MiiStoreData& elem) { return elem.uuid; });
-}
-
-} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
deleted file mode 100644
index fc742816a..000000000
--- a/src/core/hle/service/mii/mii_manager.h
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/uuid.h"
-
-namespace Service::Mii {
-
-constexpr std::size_t MAX_MIIS{100};
-constexpr u32 INVALID_INDEX{0xFFFFFFFF};
-
-struct RandomParameters {
- u32 unknown_1{};
- u32 unknown_2{};
- u32 unknown_3{};
-};
-static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size.");
-
-enum class Source : u32 {
- Database = 0,
- Default = 1,
- Account = 2,
- Friend = 3,
-};
-
-std::ostream& operator<<(std::ostream& os, Source source);
-
-struct MiiInfo {
- Common::UUID uuid{Common::INVALID_UUID};
- std::array<char16_t, 11> name{};
- u8 font_region{};
- u8 favorite_color{};
- u8 gender{};
- u8 height{};
- u8 weight{};
- u8 mii_type{};
- u8 mii_region{};
- u8 face_type{};
- u8 face_color{};
- u8 face_wrinkle{};
- u8 face_makeup{};
- u8 hair_type{};
- u8 hair_color{};
- bool hair_flip{};
- u8 eye_type{};
- u8 eye_color{};
- u8 eye_scale{};
- u8 eye_aspect_ratio{};
- u8 eye_rotate{};
- u8 eye_x{};
- u8 eye_y{};
- u8 eyebrow_type{};
- u8 eyebrow_color{};
- u8 eyebrow_scale{};
- u8 eyebrow_aspect_ratio{};
- u8 eyebrow_rotate{};
- u8 eyebrow_x{};
- u8 eyebrow_y{};
- u8 nose_type{};
- u8 nose_scale{};
- u8 nose_y{};
- u8 mouth_type{};
- u8 mouth_color{};
- u8 mouth_scale{};
- u8 mouth_aspect_ratio{};
- u8 mouth_y{};
- u8 facial_hair_color{};
- u8 beard_type{};
- u8 mustache_type{};
- u8 mustache_scale{};
- u8 mustache_y{};
- u8 glasses_type{};
- u8 glasses_color{};
- u8 glasses_scale{};
- u8 glasses_y{};
- u8 mole_type{};
- u8 mole_scale{};
- u8 mole_x{};
- u8 mole_y{};
- INSERT_PADDING_BYTES(1);
-
- std::u16string Name() const;
-};
-static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
-static_assert(std::has_unique_object_representations_v<MiiInfo>,
- "All bits of MiiInfo must contribute to its value.");
-
-bool operator==(const MiiInfo& lhs, const MiiInfo& rhs);
-bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs);
-
-#pragma pack(push, 4)
-struct MiiInfoElement {
- MiiInfo info{};
- Source source{};
-};
-static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size.");
-
-struct MiiStoreBitFields {
- union {
- u32 word_0{};
-
- BitField<24, 8, u32> hair_type;
- BitField<23, 1, u32> mole_type;
- BitField<16, 7, u32> height;
- BitField<15, 1, u32> hair_flip;
- BitField<8, 7, u32> weight;
- BitField<0, 7, u32> hair_color;
- };
-
- union {
- u32 word_1{};
-
- BitField<31, 1, u32> gender;
- BitField<24, 7, u32> eye_color;
- BitField<16, 7, u32> eyebrow_color;
- BitField<8, 7, u32> mouth_color;
- BitField<0, 7, u32> facial_hair_color;
- };
-
- union {
- u32 word_2{};
-
- BitField<31, 1, u32> mii_type;
- BitField<24, 7, u32> glasses_color;
- BitField<22, 2, u32> font_region;
- BitField<16, 6, u32> eye_type;
- BitField<14, 2, u32> mii_region;
- BitField<8, 6, u32> mouth_type;
- BitField<5, 3, u32> glasses_scale;
- BitField<0, 5, u32> eye_y;
- };
-
- union {
- u32 word_3{};
-
- BitField<29, 3, u32> mustache_type;
- BitField<24, 5, u32> eyebrow_type;
- BitField<21, 3, u32> beard_type;
- BitField<16, 5, u32> nose_type;
- BitField<13, 3, u32> mouth_aspect;
- BitField<8, 5, u32> nose_y;
- BitField<5, 3, u32> eyebrow_aspect;
- BitField<0, 5, u32> mouth_y;
- };
-
- union {
- u32 word_4{};
-
- BitField<29, 3, u32> eye_rotate;
- BitField<24, 5, u32> mustache_y;
- BitField<21, 3, u32> eye_aspect;
- BitField<16, 5, u32> glasses_y;
- BitField<13, 3, u32> eye_scale;
- BitField<8, 5, u32> mole_x;
- BitField<0, 5, u32> mole_y;
- };
-
- union {
- u32 word_5{};
-
- BitField<24, 5, u32> glasses_type;
- BitField<20, 4, u32> face_type;
- BitField<16, 4, u32> favorite_color;
- BitField<12, 4, u32> face_wrinkle;
- BitField<8, 4, u32> face_color;
- BitField<4, 4, u32> eye_x;
- BitField<0, 4, u32> face_makeup;
- };
-
- union {
- u32 word_6{};
-
- BitField<28, 4, u32> eyebrow_rotate;
- BitField<24, 4, u32> eyebrow_scale;
- BitField<20, 4, u32> eyebrow_y;
- BitField<16, 4, u32> eyebrow_x;
- BitField<12, 4, u32> mouth_scale;
- BitField<8, 4, u32> nose_scale;
- BitField<4, 4, u32> mole_scale;
- BitField<0, 4, u32> mustache_scale;
- };
-};
-static_assert(sizeof(MiiStoreBitFields) == 0x1C, "MiiStoreBitFields has incorrect size.");
-static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
- "MiiStoreBitFields is not trivially copyable.");
-
-struct MiiStoreData {
- // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
- // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
- // not suitable for our uses.
- std::array<u8, 0x1C> data{};
- static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
-
- std::array<char16_t, 10> name{};
- Common::UUID uuid{Common::INVALID_UUID};
- u16 crc_1{};
- u16 crc_2{};
-
- std::u16string Name() const;
-};
-static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
-
-struct MiiStoreDataElement {
- MiiStoreData data{};
- Source source{};
-};
-static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
-
-struct MiiDatabase {
- u32 magic{}; // 'NFDB'
- std::array<MiiStoreData, MAX_MIIS> miis{};
- INSERT_PADDING_BYTES(1);
- u8 count{};
- u16 crc{};
-};
-static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
-#pragma pack(pop)
-
-// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
-// with providing an easy interface for HLE emulation of the mii service.
-class MiiManager {
-public:
- MiiManager();
- ~MiiManager();
-
- MiiInfo CreateRandom(RandomParameters params);
- MiiInfo CreateDefault(u32 index);
-
- bool CheckUpdatedFlag() const;
- void ResetUpdatedFlag();
-
- bool IsTestModeEnabled() const;
-
- bool Empty() const;
- bool Full() const;
-
- void Clear();
-
- u32 Size() const;
-
- MiiInfo GetInfo(u32 index) const;
- MiiInfoElement GetInfoElement(u32 index) const;
- MiiStoreData GetStoreData(u32 index) const;
- MiiStoreDataElement GetStoreDataElement(u32 index) const;
-
- bool Remove(Common::UUID uuid);
- u32 IndexOf(Common::UUID uuid) const;
- u32 IndexOf(const MiiInfo& info) const;
-
- bool Move(Common::UUID uuid, u32 new_index);
- bool AddOrReplace(const MiiStoreData& data);
-
- bool DestroyFile();
- bool DeleteFile();
-
-private:
- void WriteToFile();
- void ReadFromFile();
-
- MiiStoreData CreateMiiWithUniqueUUID() const;
-
- void EnsureDatabasePartition();
-
- MiiDatabase database;
- bool updated_flag{};
- bool is_test_mode_enabled{};
-};
-
-}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp
new file mode 100644
index 000000000..25d7bae0c
--- /dev/null
+++ b/src/core/hle/service/mii/raw_data.cpp
@@ -0,0 +1,2261 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include "core/hle/service/mii/raw_data.h"
+
+namespace Service::Mii::RawData {
+
+const std::array<u8, 1728> DefaultMii{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
+ 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
+ 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
+ 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiFaceline{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 1200> RandomMiiFacelineColor{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiFacelineWrinkle{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiFacelineMakeup{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiHairType{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
+ 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
+ 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
+ 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
+ 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00,
+ 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
+ 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x4a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
+ 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
+ 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x4a, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
+ 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
+ 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x51, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
+ 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 1800> RandomMiiHairColor{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiEyeType{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+ 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x39, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 588> RandomMiiEyeColor{
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiEyebrowType{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiNoseType{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 3672> RandomMiiMouthType{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+const std::array<u8, 588> RandomMiiGlassType{
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
new file mode 100644
index 000000000..a02a5c0fd
--- /dev/null
+++ b/src/core/hle/service/mii/raw_data.h
@@ -0,0 +1,27 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace Service::Mii::RawData {
+
+extern const std::array<u8, 1728> DefaultMii;
+extern const std::array<u8, 3672> RandomMiiFaceline;
+extern const std::array<u8, 1200> RandomMiiFacelineColor;
+extern const std::array<u8, 3672> RandomMiiFacelineWrinkle;
+extern const std::array<u8, 3672> RandomMiiFacelineMakeup;
+extern const std::array<u8, 3672> RandomMiiHairType;
+extern const std::array<u8, 1800> RandomMiiHairColor;
+extern const std::array<u8, 3672> RandomMiiEyeType;
+extern const std::array<u8, 588> RandomMiiEyeColor;
+extern const std::array<u8, 3672> RandomMiiEyebrowType;
+extern const std::array<u8, 3672> RandomMiiNoseType;
+extern const std::array<u8, 3672> RandomMiiMouthType;
+extern const std::array<u8, 588> RandomMiiGlassType;
+
+} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h
new file mode 100644
index 000000000..d65a1055e
--- /dev/null
+++ b/src/core/hle/service/mii/types.h
@@ -0,0 +1,67 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::Mii {
+
+enum class Age : u32 {
+ Young,
+ Normal,
+ Old,
+ All,
+};
+
+enum class BeardType : u32 {
+ None,
+ Beard1,
+ Beard2,
+ Beard3,
+ Beard4,
+ Beard5,
+};
+
+enum class BeardAndMustacheFlag : u32 { Beard = 1, Mustache, All = Beard | Mustache };
+DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
+
+enum class FontRegion : u32 {
+ Standard,
+ China,
+ Korea,
+ Taiwan,
+};
+
+enum class Gender : u32 {
+ Male,
+ Female,
+ All,
+ Maximum = Female,
+};
+
+enum class HairFlip : u32 {
+ Left,
+ Right,
+ Maximum = Right,
+};
+
+enum class MustacheType : u32 {
+ None,
+ Mustache1,
+ Mustache2,
+ Mustache3,
+ Mustache4,
+ Mustache5,
+};
+
+enum class Race : u32 {
+ Black,
+ White,
+ Asian,
+ All,
+};
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index def63dc8a..25c24e537 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -14,14 +14,14 @@ public:
explicit MM_U() : ServiceFramework{"mm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &MM_U::Initialize, "Initialize"},
- {1, &MM_U::Finalize, "Finalize"},
- {2, &MM_U::SetAndWait, "SetAndWait"},
- {3, &MM_U::Get, "Get"},
- {4, &MM_U::InitializeWithId, "InitializeWithId"},
- {5, &MM_U::FinalizeWithId, "FinalizeWithId"},
- {6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"},
- {7, &MM_U::GetWithId, "GetWithId"},
+ {0, &MM_U::InitializeOld, "InitializeOld"},
+ {1, &MM_U::FinalizeOld, "FinalizeOld"},
+ {2, &MM_U::SetAndWaitOld, "SetAndWaitOld"},
+ {3, &MM_U::GetOld, "GetOld"},
+ {4, &MM_U::Initialize, "Initialize"},
+ {5, &MM_U::Finalize, "Finalize"},
+ {6, &MM_U::SetAndWait, "SetAndWait"},
+ {7, &MM_U::Get, "Get"},
};
// clang-format on
@@ -29,21 +29,21 @@ public:
}
private:
- void Initialize(Kernel::HLERequestContext& ctx) {
+ void InitializeOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
- void Finalize(Kernel::HLERequestContext& ctx) {
+ void FinalizeOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
- void SetAndWait(Kernel::HLERequestContext& ctx) {
+ void SetAndWaitOld(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
min = rp.Pop<u32>();
max = rp.Pop<u32>();
@@ -54,7 +54,7 @@ private:
rb.Push(RESULT_SUCCESS);
}
- void Get(Kernel::HLERequestContext& ctx) {
+ void GetOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
@@ -62,7 +62,7 @@ private:
rb.Push(current);
}
- void InitializeWithId(Kernel::HLERequestContext& ctx) {
+ void Initialize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
@@ -70,14 +70,14 @@ private:
rb.Push<u32>(id); // Any non zero value
}
- void FinalizeWithId(Kernel::HLERequestContext& ctx) {
+ void Finalize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
- void SetAndWaitWithId(Kernel::HLERequestContext& ctx) {
+ void SetAndWait(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 input_id = rp.Pop<u32>();
min = rp.Pop<u32>();
@@ -90,7 +90,7 @@ private:
rb.Push(RESULT_SUCCESS);
}
- void GetWithId(Kernel::HLERequestContext& ctx) {
+ void Get(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp
index 89e283ca5..e38dea1f4 100644
--- a/src/core/hle/service/ncm/ncm.cpp
+++ b/src/core/hle/service/ncm/ncm.cpp
@@ -28,16 +28,16 @@ public:
{7, nullptr, "ResolveApplicationLegalInformationPath"},
{8, nullptr, "RedirectApplicationLegalInformationPath"},
{9, nullptr, "Refresh"},
- {10, nullptr, "RedirectProgramPath2"},
- {11, nullptr, "Refresh2"},
- {12, nullptr, "DeleteProgramPath"},
- {13, nullptr, "DeleteApplicationControlPath"},
- {14, nullptr, "DeleteApplicationHtmlDocumentPath"},
- {15, nullptr, "DeleteApplicationLegalInformationPath"},
- {16, nullptr, ""},
- {17, nullptr, ""},
- {18, nullptr, ""},
- {19, nullptr, ""},
+ {10, nullptr, "RedirectApplicationProgramPath"},
+ {11, nullptr, "ClearApplicationRedirection"},
+ {12, nullptr, "EraseProgramRedirection"},
+ {13, nullptr, "EraseApplicationControlRedirection"},
+ {14, nullptr, "EraseApplicationHtmlDocumentRedirection"},
+ {15, nullptr, "EraseApplicationLegalInformationRedirection"},
+ {16, nullptr, "ResolveProgramPathForDebug"},
+ {17, nullptr, "RedirectProgramPathForDebug"},
+ {18, nullptr, "RedirectApplicationProgramPathForDebug"},
+ {19, nullptr, "EraseProgramRedirectionForDebug"},
};
// clang-format on
@@ -122,6 +122,7 @@ public:
{11, nullptr, "ActivateContentMetaDatabase"},
{12, nullptr, "InactivateContentMetaDatabase"},
{13, nullptr, "InvalidateRightsIdCache"},
+ {14, nullptr, "GetMemoryReport"},
};
// clang-format on
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index b7b34ce7e..780ea30fe 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -198,9 +198,9 @@ public:
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{1, nullptr, "Finalize"},
- {2, nullptr, "GetState"},
- {3, nullptr, "IsNfcEnabled"},
- {100, nullptr, "SetNfcEnabled"},
+ {2, nullptr, "GetStateOld"},
+ {3, nullptr, "IsNfcEnabledOld"},
+ {100, nullptr, "SetNfcEnabledOld"},
{400, nullptr, "InitializeSystem"},
{401, nullptr, "FinalizeSystem"},
{402, nullptr, "GetState"},
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 4b79eb81d..a0469ffbd 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <atomic>
#include "common/logging/log.h"
@@ -72,10 +73,10 @@ private:
std::array<u8, 10> uuid;
u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
// mean something else
- INSERT_PADDING_BYTES(0x15);
+ std::array<u8, 0x15> padding_1;
u32_le protocol;
u32_le tag_type;
- INSERT_PADDING_BYTES(0x2c);
+ std::array<u8, 0x2c> padding_2;
};
static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
@@ -127,7 +128,7 @@ private:
const u32 array_size = rp.Pop<u32>();
LOG_DEBUG(Service_NFP, "called, array_size={}", array_size);
- ctx.WriteBuffer(&device_handle, sizeof(device_handle));
+ ctx.WriteBuffer(device_handle);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -213,14 +214,16 @@ private:
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 2};
- auto amiibo = nfp_interface.GetAmiiboBuffer();
- TagInfo tag_info{};
- tag_info.uuid = amiibo.uuid;
- tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size());
-
- tag_info.protocol = 1; // TODO(ogniK): Figure out actual values
- tag_info.tag_type = 2;
- ctx.WriteBuffer(&tag_info, sizeof(TagInfo));
+ const auto& amiibo = nfp_interface.GetAmiiboBuffer();
+ const TagInfo tag_info{
+ .uuid = amiibo.uuid,
+ .uuid_length = static_cast<u8>(tag_info.uuid.size()),
+ .padding_1 = {},
+ .protocol = 1, // TODO(ogniK): Figure out actual values
+ .tag_type = 2,
+ .padding_2 = {},
+ };
+ ctx.WriteBuffer(tag_info);
rb.Push(RESULT_SUCCESS);
}
@@ -236,8 +239,8 @@ private:
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 2};
- auto amiibo = nfp_interface.GetAmiiboBuffer();
- ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info));
+ const auto& amiibo = nfp_interface.GetAmiiboBuffer();
+ ctx.WriteBuffer(amiibo.model_info);
rb.Push(RESULT_SUCCESS);
}
@@ -283,7 +286,7 @@ private:
CommonInfo common_info{};
common_info.application_area_size = 0;
- ctx.WriteBuffer(&common_info, sizeof(CommonInfo));
+ ctx.WriteBuffer(common_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 767158444..db7ec6d0e 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -9,6 +9,7 @@
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/service.h"
+#include "core/network/network.h"
#include "core/settings.h"
namespace Service::NIFM {
@@ -61,7 +62,7 @@ public:
{18, nullptr, "SetRequirementByRevision"},
{19, nullptr, "GetRequirement"},
{20, nullptr, "GetRevision"},
- {21, nullptr, "GetAppletInfo"},
+ {21, &IRequest::GetAppletInfo, "GetAppletInfo"},
{22, nullptr, "GetAdditionalInfo"},
{23, nullptr, "SetKeptInSleep"},
{24, nullptr, "RegisterSocketDescriptor"},
@@ -124,6 +125,16 @@ private:
rb.Push(RESULT_SUCCESS);
}
+ void GetAppletInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 8};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ }
+
Kernel::EventPair event1, event2;
};
@@ -174,10 +185,21 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ const auto [ipv4, error] = Network::GetHostIPv4Address();
+ UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(ipv4);
+ }
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIFM, "called");
- ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "NetworkProfileData is not the correct size");
+ ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c,
+ "SfNetworkProfileData is not the correct size");
u128 uuid{};
auto buffer = ctx.ReadBuffer();
std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
@@ -234,7 +256,7 @@ IGeneralService::IGeneralService(Core::System& system)
{9, nullptr, "SetNetworkProfile"},
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
{11, nullptr, "GetScanDataOld"},
- {12, nullptr, "GetCurrentIpAddress"},
+ {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"},
{13, nullptr, "GetCurrentAccessPointOld"},
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
{15, nullptr, "GetCurrentIpConfigInfo"},
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index e85f123e2..11aa74828 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -15,6 +15,66 @@
namespace Service::NIM {
+class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
+public:
+ IShopServiceAsync() : ServiceFramework("IShopServiceAsync") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Cancel"},
+ {1, nullptr, "GetSize"},
+ {2, nullptr, "Read"},
+ {3, nullptr, "GetErrorCode"},
+ {4, nullptr, "Request"},
+ {5, nullptr, "Prepare"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IShopServiceAccessor final : public ServiceFramework<IShopServiceAccessor> {
+public:
+ IShopServiceAccessor() : ServiceFramework("IShopServiceAccessor") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IShopServiceAccessor::CreateAsyncInterface, "CreateAsyncInterface"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CreateAsyncInterface(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IShopServiceAsync>();
+ }
+};
+
+class IShopServiceAccessServer final : public ServiceFramework<IShopServiceAccessServer> {
+public:
+ IShopServiceAccessServer() : ServiceFramework("IShopServiceAccessServer") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IShopServiceAccessServer::CreateAccessorInterface, "CreateAccessorInterface"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CreateAccessorInterface(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IShopServiceAccessor>();
+ }
+};
+
class NIM final : public ServiceFramework<NIM> {
public:
explicit NIM() : ServiceFramework{"nim"} {
@@ -61,11 +121,83 @@ public:
{39, nullptr, "PrepareShutdown"},
{40, nullptr, "ListApplyDeltaTask"},
{41, nullptr, "ClearNotEnoughSpaceStateOfApplyDeltaTask"},
- {42, nullptr, "Unknown1"},
- {43, nullptr, "Unknown2"},
- {44, nullptr, "Unknown3"},
- {45, nullptr, "Unknown4"},
- {46, nullptr, "Unknown5"},
+ {42, nullptr, "Unknown42"},
+ {43, nullptr, "Unknown43"},
+ {44, nullptr, "Unknown44"},
+ {45, nullptr, "Unknown45"},
+ {46, nullptr, "Unknown46"},
+ {47, nullptr, "Unknown47"},
+ {48, nullptr, "Unknown48"},
+ {49, nullptr, "Unknown49"},
+ {50, nullptr, "Unknown50"},
+ {51, nullptr, "Unknown51"},
+ {52, nullptr, "Unknown52"},
+ {53, nullptr, "Unknown53"},
+ {54, nullptr, "Unknown54"},
+ {55, nullptr, "Unknown55"},
+ {56, nullptr, "Unknown56"},
+ {57, nullptr, "Unknown57"},
+ {58, nullptr, "Unknown58"},
+ {59, nullptr, "Unknown59"},
+ {60, nullptr, "Unknown60"},
+ {61, nullptr, "Unknown61"},
+ {62, nullptr, "Unknown62"},
+ {63, nullptr, "Unknown63"},
+ {64, nullptr, "Unknown64"},
+ {65, nullptr, "Unknown65"},
+ {66, nullptr, "Unknown66"},
+ {67, nullptr, "Unknown67"},
+ {68, nullptr, "Unknown68"},
+ {69, nullptr, "Unknown69"},
+ {70, nullptr, "Unknown70"},
+ {71, nullptr, "Unknown71"},
+ {72, nullptr, "Unknown72"},
+ {73, nullptr, "Unknown73"},
+ {74, nullptr, "Unknown74"},
+ {75, nullptr, "Unknown75"},
+ {76, nullptr, "Unknown76"},
+ {77, nullptr, "Unknown77"},
+ {78, nullptr, "Unknown78"},
+ {79, nullptr, "Unknown79"},
+ {80, nullptr, "Unknown80"},
+ {81, nullptr, "Unknown81"},
+ {82, nullptr, "Unknown82"},
+ {83, nullptr, "Unknown83"},
+ {84, nullptr, "Unknown84"},
+ {85, nullptr, "Unknown85"},
+ {86, nullptr, "Unknown86"},
+ {87, nullptr, "Unknown87"},
+ {88, nullptr, "Unknown88"},
+ {89, nullptr, "Unknown89"},
+ {90, nullptr, "Unknown90"},
+ {91, nullptr, "Unknown91"},
+ {92, nullptr, "Unknown92"},
+ {93, nullptr, "Unknown93"},
+ {94, nullptr, "Unknown94"},
+ {95, nullptr, "Unknown95"},
+ {96, nullptr, "Unknown96"},
+ {97, nullptr, "Unknown97"},
+ {98, nullptr, "Unknown98"},
+ {99, nullptr, "Unknown99"},
+ {100, nullptr, "Unknown100"},
+ {101, nullptr, "Unknown101"},
+ {102, nullptr, "Unknown102"},
+ {103, nullptr, "Unknown103"},
+ {104, nullptr, "Unknown104"},
+ {105, nullptr, "Unknown105"},
+ {106, nullptr, "Unknown106"},
+ {107, nullptr, "Unknown107"},
+ {108, nullptr, "Unknown108"},
+ {109, nullptr, "Unknown109"},
+ {110, nullptr, "Unknown110"},
+ {111, nullptr, "Unknown111"},
+ {112, nullptr, "Unknown112"},
+ {113, nullptr, "Unknown113"},
+ {114, nullptr, "Unknown114"},
+ {115, nullptr, "Unknown115"},
+ {116, nullptr, "Unknown116"},
+ {117, nullptr, "Unknown117"},
+ {118, nullptr, "Unknown118"},
};
// clang-format on
@@ -78,15 +210,24 @@ public:
explicit NIM_ECA() : ServiceFramework{"nim:eca"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "CreateServerInterface"},
+ {0, &NIM_ECA::CreateServerInterface, "CreateServerInterface"},
{1, nullptr, "RefreshDebugAvailability"},
{2, nullptr, "ClearDebugResponse"},
{3, nullptr, "RegisterDebugResponse"},
+ {4, nullptr, "IsLargeResourceAvailable"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void CreateServerInterface(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IShopServiceAccessServer>();
+ }
};
class NIM_SHP final : public ServiceFramework<NIM_SHP> {
@@ -96,6 +237,8 @@ public:
static const FunctionInfo functions[] = {
{0, nullptr, "RequestDeviceAuthenticationToken"},
{1, nullptr, "RequestCachedDeviceAuthenticationToken"},
+ {2, nullptr, "RequestEdgeToken"},
+ {3, nullptr, "RequestCachedEdgeToken"},
{100, nullptr, "RequestRegisterDeviceAccount"},
{101, nullptr, "RequestUnregisterDeviceAccount"},
{102, nullptr, "RequestDeviceAccountStatus"},
@@ -113,7 +256,8 @@ public:
{305, nullptr, "RequestCreateVirtualAccount"},
{306, nullptr, "RequestDeviceLinkStatus"},
{400, nullptr, "GetAccountByVirtualAccount"},
- {500, nullptr, "RequestSyncTicket"},
+ {401, nullptr, "GetVirtualAccount"},
+ {500, nullptr, "RequestSyncTicketLegacy"},
{501, nullptr, "RequestDownloadTicket"},
{502, nullptr, "RequestDownloadTicketForPrepurchasedContents"},
{503, nullptr, "RequestSyncTicket"},
diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp
index aa171473b..8fa16fb08 100644
--- a/src/core/hle/service/npns/npns.cpp
+++ b/src/core/hle/service/npns/npns.cpp
@@ -30,6 +30,7 @@ public:
{23, nullptr, "DestroyToken"},
{24, nullptr, "DestroyTokenWithApplicationId"},
{25, nullptr, "QueryIsTokenValid"},
+ {26, nullptr, "ListenToMyApplicationId"},
{31, nullptr, "UploadTokenToBaaS"},
{32, nullptr, "DestroyTokenForBaaS"},
{33, nullptr, "CreateTokenForBaaS"},
@@ -48,6 +49,8 @@ public:
{151, nullptr, "GetStateWithHandover"},
{152, nullptr, "GetStateChangeEventWithHandover"},
{153, nullptr, "GetDropEventWithHandover"},
+ {161, nullptr, "GetRequestChangeStateCancelEvent"},
+ {162, nullptr, "RequestChangeStateForceTimedWithCancelEvent"},
{201, nullptr, "RequestChangeStateForceTimed"},
{202, nullptr, "RequestChangeStateForceAsync"},
};
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index fdab3cf78..2594e6839 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -3,8 +3,10 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/ns/errors.h"
@@ -28,8 +30,8 @@ IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountPro
IAccountProxyInterface::~IAccountProxyInterface() = default;
-IApplicationManagerInterface::IApplicationManagerInterface()
- : ServiceFramework{"IApplicationManagerInterface"} {
+IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_)
+ : ServiceFramework{"IApplicationManagerInterface"}, system{system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ListApplicationRecord"},
@@ -104,12 +106,16 @@ IApplicationManagerInterface::IApplicationManagerInterface()
{94, nullptr, "LaunchApplication"},
{95, nullptr, "GetApplicationLaunchInfo"},
{96, nullptr, "AcquireApplicationLaunchInfo"},
- {97, nullptr, "GetMainApplicationProgramIndex2"},
+ {97, nullptr, "GetMainApplicationProgramIndexByApplicationLaunchInfo"},
{98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{99, nullptr, "LaunchDevMenu"},
{100, nullptr, "ResetToFactorySettings"},
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
+ {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
+ {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
+ {105, nullptr, "RequestResetToFactorySettingsSecurely"},
+ {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
{200, nullptr, "CalculateUserSaveDataStatistics"},
{201, nullptr, "DeleteUserSaveDataAll"},
{210, nullptr, "DeleteUserSystemSaveData"},
@@ -191,6 +197,9 @@ IApplicationManagerInterface::IApplicationManagerInterface()
{1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
{1308, nullptr, "DeleteApplicationCompletelyForDebug"},
{1309, nullptr, "CleanupUnavailableAddOnContents"},
+ {1310, nullptr, "RequestMoveApplicationEntity"},
+ {1311, nullptr, "EstimateSizeToMove"},
+ {1312, nullptr, "HasMovableEntity"},
{1400, nullptr, "PrepareShutdown"},
{1500, nullptr, "FormatSdCard"},
{1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
@@ -241,13 +250,13 @@ IApplicationManagerInterface::IApplicationManagerInterface()
{2153, nullptr, "DeactivateRightsEnvironment"},
{2154, nullptr, "ForceActivateRightsContextForExit"},
{2155, nullptr, "UpdateRightsEnvironmentStatus"},
- {2156, nullptr, "CreateRightsEnvironmentForPreomia"},
+ {2156, nullptr, "CreateRightsEnvironmentForMicroApplication"},
{2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
{2161, nullptr, "SetUsersToRightsEnvironment"},
{2170, nullptr, "GetRightsEnvironmentStatus"},
{2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
{2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
- {2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
+ {2181, nullptr, "GetResultOfExtendRightsInRightsEnvironment"},
{2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
{2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
{2199, nullptr, "GetRightsEnvironmentCountForDebug"},
@@ -258,6 +267,7 @@ IApplicationManagerInterface::IApplicationManagerInterface()
{2350, nullptr, "PerformAutoUpdateByApplicationId"},
{2351, nullptr, "RequestNoDownloadRightsErrorResolution"},
{2352, nullptr, "RequestResolveNoDownloadRightsError"},
+ {2353, nullptr, "GetApplicationDownloadTaskInfo"},
{2400, nullptr, "GetPromotionInfo"},
{2401, nullptr, "CountPromotionInfo"},
{2402, nullptr, "ListPromotionInfo"},
@@ -266,9 +276,12 @@ IApplicationManagerInterface::IApplicationManagerInterface()
{2500, nullptr, "ConfirmAvailableTime"},
{2510, nullptr, "CreateApplicationResource"},
{2511, nullptr, "GetApplicationResource"},
- {2513, nullptr, "LaunchPreomia"},
+ {2513, nullptr, "LaunchMicroApplication"},
{2514, nullptr, "ClearTaskOfAsyncTaskManager"},
+ {2515, nullptr, "CleanupAllPlaceHolderAndFragmentsIfNoTask"},
+ {2516, nullptr, "EnsureApplicationCertificate"},
{2800, nullptr, "GetApplicationIdOfPreomia"},
+ {9999, nullptr, "GetApplicationCertificate"},
};
// clang-format on
@@ -286,7 +299,8 @@ void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestC
const auto size = ctx.GetWriteBufferSize();
- const FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
std::vector<u8> out;
@@ -355,15 +369,21 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
// Get language code from settings
- const auto language_code = Set::GetLanguageCodeFromIndex(Settings::values.language_index);
+ const auto language_code =
+ Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
// Convert to application language, get priority list
const auto application_language = ConvertToApplicationLanguage(language_code);
if (application_language == std::nullopt) {
+ LOG_ERROR(Service_NS, "Could not convert application language! language_code={}",
+ language_code);
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
}
const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
if (!priority_list) {
+ LOG_ERROR(Service_NS,
+ "Could not find application language priorities! application_language={}",
+ *application_language);
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
}
@@ -375,6 +395,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
}
}
+ LOG_ERROR(Service_NS, "Could not find a valid language! supported_languages={:08X}",
+ supported_languages);
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
}
@@ -399,6 +421,7 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag
const auto language_code =
ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
if (language_code == std::nullopt) {
+ LOG_ERROR(Service_NS, "Language not found! application_language={}", application_language);
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
}
@@ -426,8 +449,8 @@ IApplicationVersionInterface::IApplicationVersionInterface()
IApplicationVersionInterface::~IApplicationVersionInterface() = default;
-IContentManagerInterface::IContentManagerInterface()
- : ServiceFramework{"IContentManagerInterface"} {
+IContentManagementInterface::IContentManagementInterface()
+ : ServiceFramework{"IContentManagementInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{11, nullptr, "CalculateApplicationOccupiedSize"},
@@ -444,7 +467,7 @@ IContentManagerInterface::IContentManagerInterface()
RegisterHandlers(functions);
}
-IContentManagerInterface::~IContentManagerInterface() = default;
+IContentManagementInterface::~IContentManagementInterface() = default;
IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
// clang-format off
@@ -505,6 +528,10 @@ IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
{100, nullptr, "ResetToFactorySettings"},
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
+ {103, nullptr, "ResetToFactorySettingsWithPlatformRegion"},
+ {104, nullptr, "ResetToFactorySettingsWithPlatformRegionAuthentication"},
+ {105, nullptr, "RequestResetToFactorySettingsSecurely"},
+ {106, nullptr, "RequestResetToFactorySettingsWithPlatformRegionAuthenticationSecurely"},
};
// clang-format on
@@ -513,16 +540,16 @@ IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
IFactoryResetInterface::~IFactoryResetInterface() = default;
-NS::NS(const char* name) : ServiceFramework{name} {
+NS::NS(const char* name, Core::System& system_) : ServiceFramework{name}, system{system_} {
// clang-format off
static const FunctionInfo functions[] = {
{7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"},
{7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"},
{7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"},
{7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"},
- {7996, &NS::PushInterface<IApplicationManagerInterface>, "GetApplicationManagerInterface"},
+ {7996, &NS::PushIApplicationManagerInterface, "GetApplicationManagerInterface"},
{7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
- {7998, &NS::PushInterface<IContentManagerInterface>, "GetContentManagementInterface"},
+ {7998, &NS::PushInterface<IContentManagementInterface>, "GetContentManagementInterface"},
{7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
};
// clang-format on
@@ -533,7 +560,7 @@ NS::NS(const char* name) : ServiceFramework{name} {
NS::~NS() = default;
std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const {
- return GetInterface<IApplicationManagerInterface>();
+ return GetInterface<IApplicationManagerInterface>(system);
}
class NS_DEV final : public ServiceFramework<NS_DEV> {
@@ -549,10 +576,13 @@ public:
{6, nullptr, "TerminateApplication"},
{7, nullptr, "PrepareLaunchProgramFromHost"},
{8, nullptr, "LaunchApplication"},
- {9, nullptr, "LaunchApplicationWithStorageId"},
- {10, nullptr, "TerminateApplication2"},
- {11, nullptr, "GetRunningApplicationProcessId"},
+ {9, nullptr, "LaunchApplicationWithStorageIdForDevelop"},
+ {10, nullptr, "IsSystemMemoryResourceLimitBoosted"},
+ {11, nullptr, "GetRunningApplicationProcessIdForDevelop"},
{12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActive"},
+ {13, nullptr, "CreateApplicationResourceForDevelop"},
+ {14, nullptr, "IsPreomiaForDevelop"},
+ {15, nullptr, "GetApplicationProgramIdFromHost"},
};
// clang-format on
@@ -610,6 +640,10 @@ public:
{9, nullptr, "GetSystemUpdateNotificationEventForContentDelivery"},
{10, nullptr, "NotifySystemUpdateForContentDelivery"},
{11, nullptr, "PrepareShutdown"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {14, nullptr, "Unknown14"},
+ {15, nullptr, "Unknown15"},
{16, nullptr, "DestroySystemUpdateTask"},
{17, nullptr, "RequestSendSystemUpdate"},
{18, nullptr, "GetSendSystemUpdateProgress"},
@@ -646,11 +680,11 @@ public:
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- std::make_shared<NS>("ns:am2")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:ec")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:rid")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:rt")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:web")->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:am2", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:ec", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:rid", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:rt", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:web", system)->InstallAsService(service_manager);
std::make_shared<NS_DEV>()->InstallAsService(service_manager);
std::make_shared<NS_SU>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 13a64ad88..c90ccd755 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service {
namespace FileSystem {
@@ -22,7 +26,7 @@ public:
class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
public:
- explicit IApplicationManagerInterface();
+ explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override;
ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
@@ -32,6 +36,8 @@ private:
void GetApplicationControlData(Kernel::HLERequestContext& ctx);
void GetApplicationDesiredLanguage(Kernel::HLERequestContext& ctx);
void ConvertApplicationLanguageToLanguageCode(Kernel::HLERequestContext& ctx);
+
+ Core::System& system;
};
class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
@@ -40,10 +46,10 @@ public:
~IApplicationVersionInterface() override;
};
-class IContentManagerInterface final : public ServiceFramework<IContentManagerInterface> {
+class IContentManagementInterface final : public ServiceFramework<IContentManagementInterface> {
public:
- explicit IContentManagerInterface();
- ~IContentManagerInterface() override;
+ explicit IContentManagementInterface();
+ ~IContentManagementInterface() override;
};
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
@@ -72,13 +78,13 @@ public:
class NS final : public ServiceFramework<NS> {
public:
- explicit NS(const char* name);
+ explicit NS(const char* name, Core::System& system_);
~NS() override;
std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const;
private:
- template <typename T>
+ template <typename T, typename... Args>
void PushInterface(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
@@ -87,13 +93,23 @@ private:
rb.PushIpcInterface<T>();
}
- template <typename T>
- std::shared_ptr<T> GetInterface() const {
+ void PushIApplicationManagerInterface(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationManagerInterface>(system);
+ }
+
+ template <typename T, typename... Args>
+ std::shared_ptr<T> GetInterface(Args&&... args) const {
static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
"Not a base of ServiceFrameworkBase");
- return std::make_shared<T>();
+ return std::make_shared<T>(std::forward<Args>(args)...);
}
+
+ Core::System& system;
};
/// Registers all NS services with the specified service manager.
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 8da4e52c5..5ccec2637 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -19,6 +19,7 @@
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_memory.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -49,19 +50,9 @@ constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
};
-constexpr std::array<const char*, 7> SHARED_FONTS_TTF{
- "FontStandard.ttf",
- "FontChineseSimplified.ttf",
- "FontExtendedChineseSimplified.ttf",
- "FontChineseTraditional.ttf",
- "FontKorean.ttf",
- "FontNintendoExtended.ttf",
- "FontNintendoExtended2.ttf",
-};
-
// The below data is specific to shared font data dumped from Switch on f/w 2.2
// Virtual address and offsets/sizes likely will vary by dump
-constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
+[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
@@ -162,7 +153,8 @@ PL_U::PL_U(Core::System& system)
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
{6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
{100, nullptr, "RequestApplicationFunctionAuthorization"},
- {101, nullptr, "RequestApplicationFunctionAuthorizationForSystem"},
+ {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
+ {102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
{1000, nullptr, "LoadNgWordDataForPlatformRegionChina"},
{1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"},
};
@@ -265,16 +257,13 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
// Map backing memory for the font data
LOG_DEBUG(Service_NS, "called");
- system.CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0,
- SHARED_FONT_MEM_SIZE,
- Kernel::MemoryState::Shared);
// Create shared font memory object
auto& kernel = system.Kernel();
- impl->shared_font_mem = Kernel::SharedMemory::Create(
- kernel, system.CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
- "PL_U:shared_font_mem");
+ impl->shared_font_mem = SharedFrom(&kernel.GetFontSharedMem());
+
+ std::memcpy(impl->shared_font_mem->GetPointer(), impl->shared_font->data(),
+ impl->shared_font->size());
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 1b52511a5..5681599ba 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -21,27 +21,40 @@ namespace Service::Nvidia::Devices {
/// implement the ioctl interface.
class nvdevice {
public:
- explicit nvdevice(Core::System& system) : system{system} {};
+ explicit nvdevice(Core::System& system) : system{system} {}
virtual ~nvdevice() = default;
- union Ioctl {
- u32_le raw;
- BitField<0, 8, u32> cmd;
- BitField<8, 8, u32> group;
- BitField<16, 14, u32> length;
- BitField<30, 1, u32> is_in;
- BitField<31, 1, u32> is_out;
- };
/**
- * Handles an ioctl request.
+ * Handles an ioctl1 request.
* @param command The ioctl command id.
* @param input A buffer containing the input data for the ioctl.
* @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl.
*/
- virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) = 0;
+ virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) = 0;
+
+ /**
+ * Handles an ioctl2 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param inline_input A buffer containing the input data for the ioctl which has been inlined.
+ * @param output A buffer where the output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) = 0;
+
+ /**
+ * Handles an ioctl3 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param output A buffer where the output data will be written to.
+ * @param inline_output A buffer where the inlined output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) = 0;
protected:
Core::System& system;
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 3f7b8e670..ce615c758 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -18,11 +18,22 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default;
-u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 6fcdeee84..55a33b7e4 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -20,9 +20,11 @@ public:
explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 195421cc0..6b062e10e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -16,107 +16,152 @@
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-namespace NvErrCodes {
-enum {
- InvalidNmapHandle = -22,
-};
-}
nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
-u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocInitalizeExCommand:
- return InitalizeEx(input, output);
- case IoctlCommand::IocAllocateSpaceCommand:
- return AllocateSpace(input, output);
- case IoctlCommand::IocMapBufferExCommand:
- return MapBufferEx(input, output);
- case IoctlCommand::IocBindChannelCommand:
- return BindChannel(input, output);
- case IoctlCommand::IocGetVaRegionsCommand:
- return GetVARegions(input, output);
- case IoctlCommand::IocUnmapBufferCommand:
- return UnmapBuffer(input, output);
+NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'A':
+ switch (command.cmd) {
+ case 0x1:
+ return BindChannel(input, output);
+ case 0x2:
+ return AllocateSpace(input, output);
+ case 0x3:
+ return FreeSpace(input, output);
+ case 0x5:
+ return UnmapBuffer(input, output);
+ case 0x6:
+ return MapBufferEx(input, output);
+ case 0x8:
+ return GetVARegions(input, output);
+ case 0x9:
+ return InitalizeEx(input, output);
+ case 0x14:
+ return Remap(input, output);
+ default:
+ break;
+ }
+ break;
default:
break;
}
- if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
- return Remap(input, output);
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- UNIMPLEMENTED_MSG("Unimplemented ioctl command");
- return 0;
+NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ switch (command.group) {
+ case 'A':
+ switch (command.cmd) {
+ case 0x8:
+ return GetVARegions(input, output, inline_output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlInitalizeEx params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocSpace params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
- auto& gpu = system.GPU();
- const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
- if (params.flags & 1) {
- params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1);
+ const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
+ if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
+ params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
} else {
- params.offset = gpu.MemoryManager().AllocateSpace(size, params.align);
+ params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
+ }
+
+ auto result = NvResult::Success;
+ if (!params.offset) {
+ LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
+ result = NvResult::InsufficientMemory;
}
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return result;
}
-u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
- std::size_t num_entries = input.size() / sizeof(IoctlRemapEntry);
+NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlFreeSpace params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
+ params.pages, params.page_size);
+
+ system.GPU().MemoryManager().Unmap(params.offset,
+ static_cast<std::size_t>(params.pages) * params.page_size);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+}
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries);
+NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
+ const auto num_entries = input.size() / sizeof(IoctlRemapEntry);
+ LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
+
+ auto result = NvResult::Success;
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
- auto& gpu = system.GPU();
for (const auto& entry : entries) {
- LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
- entry.offset, entry.nvmap_handle, entry.pages);
- GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10;
- auto object = nvmap_dev->GetObject(entry.nvmap_handle);
+ LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
+ entry.offset, entry.nvmap_handle, entry.pages);
+
+ const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
if (!object) {
- LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
- std::memcpy(output.data(), entries.data(), output.size());
- return static_cast<u32>(NvErrCodes::InvalidNmapHandle);
+ LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
+ result = NvResult::InvalidState;
+ break;
}
- ASSERT(object->status == nvmap::Object::Status::Allocated);
+ const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
+ const auto size{static_cast<u64>(entry.pages) << 0x10};
+ const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
+ const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
- const u64 size = static_cast<u64>(entry.pages) << 0x10;
- ASSERT(size <= object->size);
- const u64 map_offset = static_cast<u64>(entry.map_offset) << 0x10;
-
- const GPUVAddr returned =
- gpu.MemoryManager().MapBufferEx(object->addr + map_offset, offset, size);
- ASSERT(returned == offset);
+ if (!addr) {
+ LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
+ result = NvResult::InvalidState;
+ break;
+ }
}
+
std::memcpy(output.data(), entries.data(), output.size());
- return 0;
+ return result;
}
-u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlMapBufferEx params{};
std::memcpy(&params, input.data(), input.size());
@@ -126,79 +171,107 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
params.offset);
- if (!params.nvmap_handle) {
- return 0;
+ const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
+ if (!object) {
+ LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
}
- auto object = nvmap_dev->GetObject(params.nvmap_handle);
- ASSERT(object);
-
- // We can only map objects that have already been assigned a CPU address.
- ASSERT(object->status == nvmap::Object::Status::Allocated);
-
- ASSERT(params.buffer_offset == 0);
-
// The real nvservices doesn't make a distinction between handles and ids, and
// object can only have one handle and it will be the same as its id. Assert that this is the
// case to prevent unexpected behavior.
ASSERT(object->id == params.nvmap_handle);
-
auto& gpu = system.GPU();
- if (params.flags & 1) {
- params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size);
- } else {
- params.offset = gpu.MemoryManager().MapBufferEx(object->addr, object->size);
+ u64 page_size{params.page_size};
+ if (!page_size) {
+ page_size = object->align;
+ }
+
+ if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
+ if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
+ const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
+ const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
+
+ if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
+ LOG_CRITICAL(Service_NVDRV,
+ "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
+ "mapping_size = {}, offset={}",
+ params.flags, params.nvmap_handle, params.buffer_offset,
+ params.mapping_size, params.offset);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+ } else {
+ LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
}
- // Create a new mapping entry for this operation.
- ASSERT_MSG(buffer_mappings.find(params.offset) == buffer_mappings.end(),
- "Offset is already mapped");
+ // We can only map objects that have already been assigned a CPU address.
+ ASSERT(object->status == nvmap::Object::Status::Allocated);
- BufferMapping mapping{};
- mapping.nvmap_handle = params.nvmap_handle;
- mapping.offset = params.offset;
- mapping.size = object->size;
+ const auto physical_address{object->addr + params.buffer_offset};
+ u64 size{params.mapping_size};
+ if (!size) {
+ size = object->size;
+ }
+
+ const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
+ if (is_alloc) {
+ params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
+ } else {
+ params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
+ }
- buffer_mappings[params.offset] = mapping;
+ auto result = NvResult::Success;
+ if (!params.offset) {
+ LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
+ result = NvResult::InvalidState;
+ } else {
+ AddBufferMap(params.offset, size, physical_address, is_alloc);
+ }
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return result;
}
-u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlUnmapBuffer params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
- const auto itr = buffer_mappings.find(params.offset);
- if (itr == buffer_mappings.end()) {
- LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
- // Hardware tests shows that unmapping an already unmapped buffer always returns successful
- // and doesn't fail.
- return 0;
+ if (const auto size{RemoveBufferMap(params.offset)}; size) {
+ system.GPU().MemoryManager().Unmap(params.offset, *size);
+ } else {
+ LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
}
- params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size);
- buffer_mappings.erase(itr->second.offset);
-
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
channel = params.fd;
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
@@ -210,9 +283,67 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o
params.regions[1].offset = 0x04000000;
params.regions[1].page_size = 0x10000;
params.regions[1].pages = 0x1bffff;
+
// TODO(ogniK): This probably can stay stubbed but should add support way way later
+
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
+}
+
+NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ IoctlGetVaRegions params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
+
+ params.buf_size = 0x30;
+ params.regions[0].offset = 0x04000000;
+ params.regions[0].page_size = 0x1000;
+ params.regions[0].pages = 0x3fbfff;
+
+ params.regions[1].offset = 0x04000000;
+ params.regions[1].page_size = 0x10000;
+ params.regions[1].pages = 0x1bffff;
+
+ // TODO(ogniK): This probably can stay stubbed but should add support way way later
+
+ std::memcpy(output.data(), &params, output.size());
+ std::memcpy(inline_output.data(), &params.regions, inline_output.size());
+ return NvResult::Success;
+}
+
+std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
+ const auto end{buffer_mappings.upper_bound(gpu_addr)};
+ for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
+ if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
+ return iter->second;
+ }
+ }
+
+ return std::nullopt;
+}
+
+void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated) {
+ buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
+}
+
+std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
+ if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
+ std::size_t size{};
+
+ if (iter->second.IsAllocated()) {
+ size = iter->second.Size();
+ }
+
+ buffer_mappings.erase(iter);
+
+ return size;
+ }
+
+ return std::nullopt;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index f79fcc065..08035fa0e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -4,9 +4,12 @@
#pragma once
+#include <map>
#include <memory>
-#include <unordered_map>
+#include <optional>
#include <vector>
+
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
@@ -15,41 +18,79 @@ namespace Service::Nvidia::Devices {
class nvmap;
+enum class AddressSpaceFlags : u32 {
+ None = 0x0,
+ FixedOffset = 0x1,
+ Remap = 0x100,
+};
+DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
+
class nvhost_as_gpu final : public nvdevice {
public:
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocInitalizeExCommand = 0x40284109,
- IocAllocateSpaceCommand = 0xC0184102,
- IocRemapCommand = 0x00000014,
- IocMapBufferExCommand = 0xC0284106,
- IocBindChannelCommand = 0x40044101,
- IocGetVaRegionsCommand = 0xC0404108,
- IocUnmapBufferCommand = 0xC0084105,
+ class BufferMap final {
+ public:
+ constexpr BufferMap() = default;
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size)
+ : start_addr{start_addr}, end_addr{start_addr + size} {}
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated)
+ : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr},
+ is_allocated{is_allocated} {}
+
+ constexpr VAddr StartAddr() const {
+ return start_addr;
+ }
+
+ constexpr VAddr EndAddr() const {
+ return end_addr;
+ }
+
+ constexpr std::size_t Size() const {
+ return end_addr - start_addr;
+ }
+
+ constexpr VAddr CpuAddr() const {
+ return cpu_addr;
+ }
+
+ constexpr bool IsAllocated() const {
+ return is_allocated;
+ }
+
+ private:
+ GPUVAddr start_addr{};
+ GPUVAddr end_addr{};
+ VAddr cpu_addr{};
+ bool is_allocated{};
};
struct IoctlInitalizeEx {
- u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default
- s32_le as_fd; // ignored; passes 0
- u32_le flags; // passes 0
- u32_le reserved; // ignored; passes 0
- u64_le unk0;
- u64_le unk1;
- u64_le unk2;
+ u32_le big_page_size{}; // depends on GPU's available_big_page_sizes; 0=default
+ s32_le as_fd{}; // ignored; passes 0
+ u32_le flags{}; // passes 0
+ u32_le reserved{}; // ignored; passes 0
+ u64_le unk0{};
+ u64_le unk1{};
+ u64_le unk2{};
};
static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size");
struct IoctlAllocSpace {
- u32_le pages;
- u32_le page_size;
- u32_le flags;
+ u32_le pages{};
+ u32_le page_size{};
+ AddressSpaceFlags flags{};
INSERT_PADDING_WORDS(1);
union {
u64_le offset;
@@ -58,74 +99,83 @@ private:
};
static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size");
+ struct IoctlFreeSpace {
+ u64_le offset{};
+ u32_le pages{};
+ u32_le page_size{};
+ };
+ static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
+
struct IoctlRemapEntry {
- u16_le flags;
- u16_le kind;
- u32_le nvmap_handle;
- u32_le map_offset;
- u32_le offset;
- u32_le pages;
+ u16_le flags{};
+ u16_le kind{};
+ u32_le nvmap_handle{};
+ u32_le map_offset{};
+ u32_le offset{};
+ u32_le pages{};
};
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
struct IoctlMapBufferEx {
- u32_le flags; // bit0: fixed_offset, bit2: cacheable
- u32_le kind; // -1 is default
- u32_le nvmap_handle;
- u32_le page_size; // 0 means don't care
- u64_le buffer_offset;
- u64_le mapping_size;
- u64_le offset;
+ AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
+ u32_le kind{}; // -1 is default
+ u32_le nvmap_handle{};
+ u32_le page_size{}; // 0 means don't care
+ s64_le buffer_offset{};
+ u64_le mapping_size{};
+ s64_le offset{};
};
static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size");
struct IoctlUnmapBuffer {
- u64_le offset;
+ s64_le offset{};
};
static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size");
struct IoctlBindChannel {
- u32_le fd;
+ s32_le fd{};
};
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
struct IoctlVaRegion {
- u64_le offset;
- u32_le page_size;
+ u64_le offset{};
+ u32_le page_size{};
INSERT_PADDING_WORDS(1);
- u64_le pages;
+ u64_le pages{};
};
static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
struct IoctlGetVaRegions {
- u64_le buf_addr; // (contained output user ptr on linux, ignored)
- u32_le buf_size; // forced to 2*sizeof(struct va_region)
- u32_le reserved;
- IoctlVaRegion regions[2];
+ u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
+ u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
+ u32_le reserved{};
+ IoctlVaRegion regions[2]{};
};
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- struct BufferMapping {
- u64 offset;
- u64 size;
- u32 nvmap_handle;
- };
+ s32 channel{};
- /// Map containing the nvmap object mappings in GPU memory.
- std::unordered_map<u64, BufferMapping> buffer_mappings;
+ NvResult InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
- u32 channel{};
+ NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
- u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
- u32 Remap(const std::vector<u8>& input, std::vector<u8>& output);
- u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
- u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
- u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+ std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
+ void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
+ std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
std::shared_ptr<nvmap> nvmap_dev;
+
+ // This is expected to be ordered, therefore we must use a map, not unordered_map
+ std::map<GPUVAddr, BufferMap> buffer_mappings;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index b27ee0502..d90cf90a8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -15,45 +15,59 @@
namespace Service::Nvidia::Devices {
-nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
- : nvdevice(system), events_interface{events_interface} {}
+nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface,
+ SyncpointManager& syncpoint_manager)
+ : nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {}
nvhost_ctrl::~nvhost_ctrl() = default;
-u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocGetConfigCommand:
- return NvOsGetConfigU32(input, output);
- case IoctlCommand::IocCtrlEventWaitCommand:
- return IocCtrlEventWait(input, output, false, ctrl);
- case IoctlCommand::IocCtrlEventWaitAsyncCommand:
- return IocCtrlEventWait(input, output, true, ctrl);
- case IoctlCommand::IocCtrlEventRegisterCommand:
- return IocCtrlEventRegister(input, output);
- case IoctlCommand::IocCtrlEventUnregisterCommand:
- return IocCtrlEventUnregister(input, output);
- case IoctlCommand::IocCtrlEventSignalCommand:
- return IocCtrlEventSignal(input, output);
+NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1b:
+ return NvOsGetConfigU32(input, output);
+ case 0x1c:
+ return IocCtrlClearEventWait(input, output);
+ case 0x1d:
+ return IocCtrlEventWait(input, output, false);
+ case 0x1e:
+ return IocCtrlEventWait(input, output, true);
+ case 0x1f:
+ return IocCtrlEventRegister(input, output);
+ case 0x20:
+ return IocCtrlEventUnregister(input, output);
+ }
+ break;
default:
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ break;
}
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetConfigParams params{};
std::memcpy(&params, input.data(), sizeof(params));
LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
params.param_str.data());
- return 0x30006; // Returns error on production mode
+ return NvResult::ConfigVarNotFound; // Returns error on production mode
}
-u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
- bool is_async, IoctlCtrl& ctrl) {
+NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+ bool is_async) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
@@ -70,19 +84,33 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
return NvResult::BadParameter;
}
+ if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
+ params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Success;
+ }
+
+ if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
+ syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
+ params.value = new_value;
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Success;
+ }
+
auto event = events_interface.events[event_id];
auto& gpu = system.GPU();
+
// This is mostly to take into account unimplemented features. As synced
// gpu is always synced.
if (!gpu.IsAsync()) {
- event.writable->Signal();
+ event.event.writable->Signal();
return NvResult::Success;
}
auto lock = gpu.LockSync();
- const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id);
+ const u32 current_syncpoint_value = event.fence.value;
const s32 diff = current_syncpoint_value - params.threshold;
if (diff >= 0) {
- event.writable->Signal();
+ event.event.writable->Signal();
params.value = current_syncpoint_value;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
@@ -109,12 +137,9 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
}
params.value |= event_id;
- event.writable->Clear();
+ event.event.writable->Clear();
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
- if (!is_async && ctrl.fresh_call) {
- ctrl.must_delay = true;
- ctrl.timeout = params.timeout;
- ctrl.event_id = event_id;
+ if (!is_async) {
return NvResult::Timeout;
}
std::memcpy(output.data(), &params, sizeof(params));
@@ -124,7 +149,7 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
return NvResult::BadParameter;
}
-u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventRegisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
@@ -139,7 +164,8 @@ u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<
return NvResult::Success;
}
-u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
+ std::vector<u8>& output) {
IocCtrlEventUnregisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
@@ -154,24 +180,22 @@ u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vecto
return NvResult::Success;
}
-u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventSignalParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization
- // It is believed from RE to cancel the GPU Event. However, better research is required
- u32 event_id = params.user_event_id & 0x00FF;
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id);
+
+ u32 event_id = params.event_id & 0x00FF;
+ LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
+
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
if (events_interface.status[event_id] == EventState::Waiting) {
- auto& gpu = system.GPU();
- if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id],
- events_interface.assigned_value[event_id])) {
- events_interface.LiberateEvent(event_id);
- events_interface.events[event_id].writable->Signal();
- }
+ events_interface.LiberateEvent(event_id);
}
+
+ syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
+
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 9898623de..c5aa1362a 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -14,137 +14,120 @@ namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
- explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
+ explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface,
+ SyncpointManager& syncpoint_manager);
~nvhost_ctrl() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSyncptReadCommand = 0xC0080014,
- IocSyncptIncrCommand = 0x40040015,
- IocSyncptWaitCommand = 0xC00C0016,
- IocModuleMutexCommand = 0x40080017,
- IocModuleRegRDWRCommand = 0xC0180018,
- IocSyncptWaitexCommand = 0xC0100019,
- IocSyncptReadMaxCommand = 0xC008001A,
- IocGetConfigCommand = 0xC183001B,
- IocCtrlEventSignalCommand = 0xC004001C,
- IocCtrlEventWaitCommand = 0xC010001D,
- IocCtrlEventWaitAsyncCommand = 0xC010001E,
- IocCtrlEventRegisterCommand = 0xC004001F,
- IocCtrlEventUnregisterCommand = 0xC0040020,
- IocCtrlEventKillCommand = 0x40080021,
- };
struct IocSyncptReadParams {
- u32_le id;
- u32_le value;
+ u32_le id{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size");
struct IocSyncptIncrParams {
- u32_le id;
+ u32_le id{};
};
static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size");
struct IocSyncptWaitParams {
- u32_le id;
- u32_le thresh;
- s32_le timeout;
+ u32_le id{};
+ u32_le thresh{};
+ s32_le timeout{};
};
static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size");
struct IocModuleMutexParams {
- u32_le id;
- u32_le lock; // (0 = unlock and 1 = lock)
+ u32_le id{};
+ u32_le lock{}; // (0 = unlock and 1 = lock)
};
static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size");
struct IocModuleRegRDWRParams {
- u32_le id;
- u32_le num_offsets;
- u32_le block_size;
- u32_le offsets;
- u32_le values;
- u32_le write;
+ u32_le id{};
+ u32_le num_offsets{};
+ u32_le block_size{};
+ u32_le offsets{};
+ u32_le values{};
+ u32_le write{};
};
static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size");
struct IocSyncptWaitexParams {
- u32_le id;
- u32_le thresh;
- s32_le timeout;
- u32_le value;
+ u32_le id{};
+ u32_le thresh{};
+ s32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size");
struct IocSyncptReadMaxParams {
- u32_le id;
- u32_le value;
+ u32_le id{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size");
struct IocGetConfigParams {
- std::array<char, 0x41> domain_str;
- std::array<char, 0x41> param_str;
- std::array<char, 0x101> config_str;
+ std::array<char, 0x41> domain_str{};
+ std::array<char, 0x41> param_str{};
+ std::array<char, 0x101> config_str{};
};
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
struct IocCtrlEventSignalParams {
- u32_le user_event_id;
+ u32_le event_id{};
};
static_assert(sizeof(IocCtrlEventSignalParams) == 4,
"IocCtrlEventSignalParams is incorrect size");
struct IocCtrlEventWaitParams {
- u32_le syncpt_id;
- u32_le threshold;
- s32_le timeout;
- u32_le value;
+ u32_le syncpt_id{};
+ u32_le threshold{};
+ s32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
struct IocCtrlEventWaitAsyncParams {
- u32_le syncpt_id;
- u32_le threshold;
- u32_le timeout;
- u32_le value;
+ u32_le syncpt_id{};
+ u32_le threshold{};
+ u32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
"IocCtrlEventWaitAsyncParams is incorrect size");
struct IocCtrlEventRegisterParams {
- u32_le user_event_id;
+ u32_le user_event_id{};
};
static_assert(sizeof(IocCtrlEventRegisterParams) == 4,
"IocCtrlEventRegisterParams is incorrect size");
struct IocCtrlEventUnregisterParams {
- u32_le user_event_id;
+ u32_le user_event_id{};
};
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
"IocCtrlEventUnregisterParams is incorrect size");
struct IocCtrlEventKill {
- u64_le user_events;
+ u64_le user_events{};
};
static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
- u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async,
- IoctlCtrl& ctrl);
-
- u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+ NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
EventInterface& events_interface;
+ SyncpointManager& syncpoint_manager;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index cc2192e5c..2d7ea433c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -15,39 +15,66 @@ namespace Service::Nvidia::Devices {
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
-u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
- const std::vector<u8>& input2, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocGetCharacteristicsCommand:
- return GetCharacteristics(input, output, output2, version);
- case IoctlCommand::IocGetTPCMasksCommand:
- return GetTPCMasks(input, output);
- case IoctlCommand::IocGetActiveSlotMaskCommand:
- return GetActiveSlotMask(input, output);
- case IoctlCommand::IocZcullGetCtxSizeCommand:
- return ZCullGetCtxSize(input, output);
- case IoctlCommand::IocZcullGetInfo:
- return ZCullGetInfo(input, output);
- case IoctlCommand::IocZbcSetTable:
- return ZBCSetTable(input, output);
- case IoctlCommand::IocZbcQueryTable:
- return ZBCQueryTable(input, output);
- case IoctlCommand::IocFlushL2:
- return FlushL2(input, output);
- case IoctlCommand::IocGetGpuTime:
- return GetGpuTime(input, output);
+NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'G':
+ switch (command.cmd) {
+ case 0x1:
+ return ZCullGetCtxSize(input, output);
+ case 0x2:
+ return ZCullGetInfo(input, output);
+ case 0x3:
+ return ZBCSetTable(input, output);
+ case 0x4:
+ return ZBCQueryTable(input, output);
+ case 0x5:
+ return GetCharacteristics(input, output);
+ case 0x6:
+ return GetTPCMasks(input, output);
+ case 0x7:
+ return FlushL2(input, output);
+ case 0x14:
+ return GetActiveSlotMask(input, output);
+ case 0x1c:
+ return GetGpuTime(input, output);
+ default:
+ break;
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output) {
+ switch (command.group) {
+ case 'G':
+ switch (command.cmd) {
+ case 0x5:
+ return GetCharacteristics(input, output, inline_output);
+ case 0x6:
+ return GetTPCMasks(input, output, inline_output);
+ default:
+ break;
+ }
+ break;
default:
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlVersion version) {
+NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input,
+ std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlCharacteristics params{};
std::memcpy(&params, input.data(), input.size());
@@ -88,31 +115,83 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto
params.gc.gr_compbit_store_base_hw = 0x0;
params.gpu_characteristics_buf_size = 0xA0;
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+}
- if (version == IoctlVersion::Version3) {
- std::memcpy(output.data(), input.data(), output.size());
- std::memcpy(output2.data(), &params.gc, output2.size());
- } else {
- std::memcpy(output.data(), &params, output.size());
+NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlCharacteristics params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.gc.arch = 0x120;
+ params.gc.impl = 0xb;
+ params.gc.rev = 0xa1;
+ params.gc.num_gpc = 0x1;
+ params.gc.l2_cache_size = 0x40000;
+ params.gc.on_board_video_memory_size = 0x0;
+ params.gc.num_tpc_per_gpc = 0x2;
+ params.gc.bus_type = 0x20;
+ params.gc.big_page_size = 0x20000;
+ params.gc.compression_page_size = 0x20000;
+ params.gc.pde_coverage_bit_count = 0x1B;
+ params.gc.available_big_page_sizes = 0x30000;
+ params.gc.gpc_mask = 0x1;
+ params.gc.sm_arch_sm_version = 0x503;
+ params.gc.sm_arch_spa_version = 0x503;
+ params.gc.sm_arch_warp_count = 0x80;
+ params.gc.gpu_va_bit_count = 0x28;
+ params.gc.reserved = 0x0;
+ params.gc.flags = 0x55;
+ params.gc.twod_class = 0x902D;
+ params.gc.threed_class = 0xB197;
+ params.gc.compute_class = 0xB1C0;
+ params.gc.gpfifo_class = 0xB06F;
+ params.gc.inline_to_memory_class = 0xA140;
+ params.gc.dma_copy_class = 0xB0B5;
+ params.gc.max_fbps_count = 0x1;
+ params.gc.fbp_en_mask = 0x0;
+ params.gc.max_ltc_per_fbp = 0x2;
+ params.gc.max_lts_per_ltc = 0x1;
+ params.gc.max_tex_per_tpc = 0x0;
+ params.gc.max_gpc_count = 0x1;
+ params.gc.rop_l2_en_mask_0 = 0x21D70;
+ params.gc.rop_l2_en_mask_1 = 0x0;
+ params.gc.chipname = 0x6230326D67;
+ params.gc.gr_compbit_store_base_hw = 0x0;
+ params.gpu_characteristics_buf_size = 0xA0;
+ params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
+
+ std::memcpy(output.data(), input.data(), output.size());
+ std::memcpy(inline_output.data(), &params.gc, inline_output.size());
+ return NvResult::Success;
+}
+
+NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGpuGetTpcMasksArgs params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
+ if (params.mask_buffer_size != 0) {
+ params.tcp_mask = 3;
}
- return 0;
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
IoctlGpuGetTpcMasksArgs params{};
std::memcpy(&params, input.data(), input.size());
- LOG_INFO(Service_NVDRV, "called, mask=0x{:X}, mask_buf_addr=0x{:X}", params.mask_buf_size,
- params.mask_buf_addr);
- // TODO(ogniK): Confirm value on hardware
- if (params.mask_buf_size)
- params.tpc_mask_size = 4 * 1; // 4 * num_gpc
- else
- params.tpc_mask_size = 0;
- std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
+ if (params.mask_buffer_size != 0) {
+ params.tcp_mask = 3;
+ }
+ std::memcpy(output.data(), &params, output.size());
+ std::memcpy(inline_output.data(), &params.tcp_mask, inline_output.size());
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlActiveSlotMask params{};
@@ -122,10 +201,10 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector
params.slot = 0x07;
params.mask = 0x01;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlZcullGetCtxSize params{};
@@ -134,10 +213,10 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u
}
params.size = 0x1;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlNvgpuGpuZcullGetInfoArgs params{};
@@ -157,48 +236,47 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>&
params.subregion_height_align_pixels = 0x40;
params.subregion_count = 0x10;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlZbcSetTable params{};
std::memcpy(&params, input.data(), input.size());
// TODO(ogniK): What does this even actually do?
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlZbcQueryTable params{};
std::memcpy(&params, input.data(), input.size());
// TODO : To implement properly
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlFlushL2 params{};
std::memcpy(&params, input.data(), input.size());
// TODO : To implement properly
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlGetGpuTime params{};
std::memcpy(&params, input.data(), input.size());
- const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks());
- params.gpu_time = static_cast<u64_le>(ns.count());
+ params.gpu_time = static_cast<u64_le>(system.CoreTiming().GetGlobalTimeNs().count());
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 642b0a2cb..137b88238 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -16,32 +16,13 @@ public:
explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocGetCharacteristicsCommand = 0xC0B04705,
- IocGetTPCMasksCommand = 0xC0184706,
- IocGetActiveSlotMaskCommand = 0x80084714,
- IocZcullGetCtxSizeCommand = 0x80044701,
- IocZcullGetInfo = 0x80284702,
- IocZbcSetTable = 0x402C4703,
- IocZbcQueryTable = 0xC0344704,
- IocFlushL2 = 0x40084707,
- IocInvalICache = 0x4008470D,
- IocSetMmudebugMode = 0x4008470E,
- IocSetSmDebugMode = 0x4010470F,
- IocWaitForPause = 0xC0084710,
- IocGetTcpExceptionEnStatus = 0x80084711,
- IocNumVsms = 0x80084712,
- IocVsmsMapping = 0xC0044713,
- IocGetErrorChannelUserData = 0xC008471B,
- IocGetGpuTime = 0xC010471C,
- IocGetCpuTimeCorrelationInfo = 0xC108471D,
- };
-
struct IoctlGpuCharacteristics {
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B)
@@ -92,16 +73,11 @@ private:
"IoctlCharacteristics is incorrect size");
struct IoctlGpuGetTpcMasksArgs {
- /// [in] TPC mask buffer size reserved by userspace. Should be at least
- /// sizeof(__u32) * fls(gpc_mask) to receive TPC mask for each GPC.
- /// [out] full kernel buffer size
- u32_le mask_buf_size;
- u32_le reserved;
-
- /// [in] pointer to TPC mask buffer. It will receive one 32-bit TPC mask per GPC or 0 if
- /// GPC is not enabled or not present. This parameter is ignored if mask_buf_size is 0.
- u64_le mask_buf_addr;
- u64_le tpc_mask_size; // Nintendo add this?
+ u32_le mask_buffer_size{};
+ INSERT_PADDING_WORDS(1);
+ u64_le mask_buffer_address{};
+ u32_le tcp_mask{};
+ INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(IoctlGpuGetTpcMasksArgs) == 24,
"IoctlGpuGetTpcMasksArgs is incorrect size");
@@ -159,20 +135,26 @@ private:
static_assert(sizeof(IoctlFlushL2) == 8, "IoctlFlushL2 is incorrect size");
struct IoctlGetGpuTime {
- u64_le gpu_time;
+ u64_le gpu_time{};
+ INSERT_PADDING_WORDS(2);
};
- static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size");
-
- u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlVersion version);
- u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
- u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
+ static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size");
+
+ NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
+
+ NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
+
+ NvResult GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index f1966ac0e..af8b3d9f1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -7,117 +7,148 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
namespace Service::Nvidia::Devices {
-nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
- : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
+nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager{syncpoint_manager} {
+ channel_fence.id = syncpoint_manager.AllocateSyncpoint();
+ channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+}
+
nvhost_gpu::~nvhost_gpu() = default;
-u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
- case IoctlCommand::IocSetClientDataCommand:
- return SetClientData(input, output);
- case IoctlCommand::IocGetClientDataCommand:
- return GetClientData(input, output);
- case IoctlCommand::IocZCullBind:
- return ZCullBind(input, output);
- case IoctlCommand::IocSetErrorNotifierCommand:
- return SetErrorNotifier(input, output);
- case IoctlCommand::IocChannelSetPriorityCommand:
- return SetChannelPriority(input, output);
- case IoctlCommand::IocAllocGPFIFOEx2Command:
- return AllocGPFIFOEx2(input, output);
- case IoctlCommand::IocAllocObjCtxCommand:
- return AllocateObjectContext(input, output);
- case IoctlCommand::IocChannelGetWaitbaseCommand:
- return GetWaitbase(input, output);
- case IoctlCommand::IocChannelSetTimeoutCommand:
- return ChannelSetTimeout(input, output);
- case IoctlCommand::IocChannelSetTimeslice:
- return ChannelSetTimeslice(input, output);
- default:
+NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x3:
+ return GetWaitbase(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input, output);
+ case 0x3:
+ return ChannelSetTimeout(input, output);
+ case 0x8:
+ return SubmitGPFIFOBase(input, output, false);
+ case 0x9:
+ return AllocateObjectContext(input, output);
+ case 0xb:
+ return ZCullBind(input, output);
+ case 0xc:
+ return SetErrorNotifier(input, output);
+ case 0xd:
+ return SetChannelPriority(input, output);
+ case 0x1a:
+ return AllocGPFIFOEx2(input, output);
+ case 0x1b:
+ return SubmitGPFIFOBase(input, output, true);
+ case 0x1d:
+ return ChannelSetTimeslice(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'G':
+ switch (command.cmd) {
+ case 0x14:
+ return SetClientData(input, output);
+ case 0x15:
+ return GetClientData(input, output);
+ default:
+ break;
+ }
break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+};
- if (command.group == NVGPU_IOCTL_MAGIC) {
- if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) {
- return SubmitGPFIFO(input, output);
- }
- if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) {
- return KickoffPB(input, output, input2, version);
+NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 'H':
+ switch (command.cmd) {
+ case 0x1b:
+ return SubmitGPFIFOBase(input, inline_input, output);
}
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
-};
+NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
-u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetNvmapFD params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
user_data = params.data;
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
params.data = user_data;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&zcull_params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va,
zcull_params.mode);
std::memcpy(output.data(), &zcull_params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetErrorNotifier params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset,
params.size, params.mem);
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&channel_priority, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocGpfifoEx2 params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV,
@@ -126,15 +157,15 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
- auto& gpu = system.GPU();
- params.fence_out.id = assigned_syncpoints;
- params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints);
- assigned_syncpoints++;
+ channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+
+ params.fence_out = channel_fence;
+
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocObjCtx params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num,
@@ -142,102 +173,149 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<
params.obj_id = 0x0;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) {
- if (input.size() < sizeof(IoctlSubmitGpfifo)) {
- UNIMPLEMENTED();
+static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) {
+ return {
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::SubmissionMode::Increasing),
+ {fence.value},
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::SubmissionMode::Increasing),
+ Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Acquire, fence.id),
+ };
+}
+
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) {
+ std::vector<Tegra::CommandHeader> result{
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::SubmissionMode::Increasing),
+ {}};
+
+ for (u32 count = 0; count < add_increment; ++count) {
+ result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::SubmissionMode::Increasing));
+ result.emplace_back(
+ Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Increment, fence.id));
}
- IoctlSubmitGpfifo params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+
+ return result;
+}
+
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence,
+ u32 add_increment) {
+ std::vector<Tegra::CommandHeader> result{
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
+ Tegra::SubmissionMode::Increasing),
+ {}};
+ const std::vector<Tegra::CommandHeader> increment{
+ BuildIncrementCommandList(fence, add_increment)};
+
+ result.insert(result.end(), increment.begin(), increment.end());
+
+ return result;
+}
+
+NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output,
+ Tegra::CommandList&& entries) {
LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
params.num_entries, params.flags.raw);
- ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
- params.num_entries * sizeof(Tegra::CommandListHeader),
- "Incorrect input size");
+ auto& gpu = system.GPU();
- Tegra::CommandList entries(params.num_entries);
- std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)],
- params.num_entries * sizeof(Tegra::CommandListHeader));
+ params.fence_out.id = channel_fence.id;
- UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
- UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
+ if (params.flags.add_wait.Value() &&
+ !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
+ gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
+ }
- auto& gpu = system.GPU();
- u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
- if (params.flags.increment.Value()) {
- params.fence_out.value += current_syncpoint_value;
+ if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
+ const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
+ params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
+ params.fence_out.id, params.AddIncrementValue() + increment_value);
} else {
- params.fence_out.value = current_syncpoint_value;
+ params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
}
+
gpu.PushGPUEntries(std::move(entries));
+ if (params.flags.add_increment.Value()) {
+ if (params.flags.suppress_wfi) {
+ gpu.PushGPUEntries(Tegra::CommandList{
+ BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
+ } else {
+ gpu.PushGPUEntries(Tegra::CommandList{
+ BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
+ }
+ }
+
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
- const std::vector<u8>& input2, IoctlVersion version) {
+NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output,
+ bool kickoff) {
if (input.size() < sizeof(IoctlSubmitGpfifo)) {
UNIMPLEMENTED();
+ return NvResult::InvalidSize;
}
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
- params.num_entries, params.flags.raw);
-
Tegra::CommandList entries(params.num_entries);
- if (version == IoctlVersion::Version2) {
- std::memcpy(entries.data(), input2.data(),
- params.num_entries * sizeof(Tegra::CommandListHeader));
- } else {
- system.Memory().ReadBlock(params.address, entries.data(),
- params.num_entries * sizeof(Tegra::CommandListHeader));
- }
- UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
- UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
- auto& gpu = system.GPU();
- u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
- if (params.flags.increment.Value()) {
- params.fence_out.value += current_syncpoint_value;
+ if (kickoff) {
+ system.Memory().ReadBlock(params.address, entries.command_lists.data(),
+ params.num_entries * sizeof(Tegra::CommandListHeader));
} else {
- params.fence_out.value = current_syncpoint_value;
+ std::memcpy(entries.command_lists.data(), &input[sizeof(IoctlSubmitGpfifo)],
+ params.num_entries * sizeof(Tegra::CommandListHeader));
}
- gpu.PushGPUEntries(std::move(entries));
- std::memcpy(output.data(), &params, output.size());
- return 0;
+ return SubmitGPFIFOImpl(params, output, std::move(entries));
+}
+
+NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input,
+ const std::vector<u8>& input_inline,
+ std::vector<u8>& output) {
+ if (input.size() < sizeof(IoctlSubmitGpfifo)) {
+ UNIMPLEMENTED();
+ return NvResult::InvalidSize;
+ }
+ IoctlSubmitGpfifo params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+ Tegra::CommandList entries(params.num_entries);
+ std::memcpy(entries.command_lists.data(), input_inline.data(), input_inline.size());
+ return SubmitGPFIFOImpl(params, output, std::move(entries));
}
-u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetWaitbase params{};
std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
params.value = 0; // Seems to be hard coded at 0
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlChannelSetTimeout params{};
std::memcpy(&params, input.data(), sizeof(IoctlChannelSetTimeout));
LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetTimeslice params{};
std::memcpy(&params, input.data(), sizeof(IoctlSetTimeslice));
LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice);
channel_timeslice = params.timeslice;
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 2ac74743f..e0298b4fe 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -11,46 +11,28 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "video_core/dma_pusher.h"
+
+namespace Service::Nvidia {
+class SyncpointManager;
+}
namespace Service::Nvidia::Devices {
class nvmap;
-constexpr u32 NVGPU_IOCTL_MAGIC('H');
-constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8);
-constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b);
-
class nvhost_gpu final : public nvdevice {
public:
- explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
+ explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager);
~nvhost_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- IocAllocGPFIFOCommand = 0x40084805,
- IocSetClientDataCommand = 0x40084714,
- IocGetClientDataCommand = 0x80084715,
- IocZCullBind = 0xc010480b,
- IocSetErrorNotifierCommand = 0xC018480C,
- IocChannelSetPriorityCommand = 0x4004480D,
- IocEnableCommand = 0x0000480E,
- IocDisableCommand = 0x0000480F,
- IocPreemptCommand = 0x00004810,
- IocForceResetCommand = 0x00004811,
- IocEventIdControlCommand = 0x40084812,
- IocGetErrorNotificationCommand = 0xC0104817,
- IocAllocGPFIFOExCommand = 0x40204818,
- IocAllocGPFIFOEx2Command = 0xC020481A,
- IocAllocObjCtxCommand = 0xC0104809,
- IocChannelGetWaitbaseCommand = 0xC0080003,
- IocChannelSetTimeoutCommand = 0x40044803,
- IocChannelSetTimeslice = 0xC004481D,
- };
-
enum class CtxObjects : u32_le {
Ctx2D = 0x902D,
Ctx3D = 0xB197,
@@ -61,63 +43,63 @@ private:
};
struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
+ s32_le nvmap_fd{};
};
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
struct IoctlChannelSetTimeout {
- u32_le timeout;
+ u32_le timeout{};
};
static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size");
struct IoctlAllocGPFIFO {
- u32_le num_entries;
- u32_le flags;
+ u32_le num_entries{};
+ u32_le flags{};
};
static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size");
struct IoctlClientData {
- u64_le data;
+ u64_le data{};
};
static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size");
struct IoctlZCullBind {
- u64_le gpu_va;
- u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
+ u64_le gpu_va{};
+ u32_le mode{}; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size");
struct IoctlSetErrorNotifier {
- u64_le offset;
- u64_le size;
- u32_le mem; // nvmap object handle
+ u64_le offset{};
+ u64_le size{};
+ u32_le mem{}; // nvmap object handle
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");
struct IoctlChannelSetPriority {
- u32_le priority;
+ u32_le priority{};
};
static_assert(sizeof(IoctlChannelSetPriority) == 4,
"IoctlChannelSetPriority is incorrect size");
struct IoctlSetTimeslice {
- u32_le timeslice;
+ u32_le timeslice{};
};
static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size");
struct IoctlEventIdControl {
- u32_le cmd; // 0=disable, 1=enable, 2=clear
- u32_le id;
+ u32_le cmd{}; // 0=disable, 1=enable, 2=clear
+ u32_le id{};
};
static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size");
struct IoctlGetErrorNotification {
- u64_le timestamp;
- u32_le info32;
- u16_le info16;
- u16_le status; // always 0xFFFF
+ u64_le timestamp{};
+ u32_le info32{};
+ u16_le info16{};
+ u16_le status{}; // always 0xFFFF
};
static_assert(sizeof(IoctlGetErrorNotification) == 16,
"IoctlGetErrorNotification is incorrect size");
@@ -125,80 +107,89 @@ private:
static_assert(sizeof(Fence) == 8, "Fence is incorrect size");
struct IoctlAllocGpfifoEx {
- u32_le num_entries;
- u32_le flags;
- u32_le unk0;
- u32_le unk1;
- u32_le unk2;
- u32_le unk3;
- u32_le unk4;
- u32_le unk5;
+ u32_le num_entries{};
+ u32_le flags{};
+ u32_le unk0{};
+ u32_le unk1{};
+ u32_le unk2{};
+ u32_le unk3{};
+ u32_le unk4{};
+ u32_le unk5{};
};
static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");
struct IoctlAllocGpfifoEx2 {
- u32_le num_entries; // in
- u32_le flags; // in
- u32_le unk0; // in (1 works)
- Fence fence_out; // out
- u32_le unk1; // in
- u32_le unk2; // in
- u32_le unk3; // in
+ u32_le num_entries{}; // in
+ u32_le flags{}; // in
+ u32_le unk0{}; // in (1 works)
+ Fence fence_out{}; // out
+ u32_le unk1{}; // in
+ u32_le unk2{}; // in
+ u32_le unk3{}; // in
};
static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size");
struct IoctlAllocObjCtx {
- u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
- // 0xB06F=channel_gpfifo
- u32_le flags;
- u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
+ u32_le class_num{}; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
+ // 0xB06F=channel_gpfifo
+ u32_le flags{};
+ u64_le obj_id{}; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
};
static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size");
struct IoctlSubmitGpfifo {
- u64_le address; // pointer to gpfifo entry structs
- u32_le num_entries; // number of fence objects being submitted
+ u64_le address{}; // pointer to gpfifo entry structs
+ u32_le num_entries{}; // number of fence objects being submitted
union {
u32_le raw;
BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
BitField<1, 1, u32_le> add_increment; // append an increment to the list
- BitField<2, 1, u32_le> new_hw_format; // Mostly ignored
+ BitField<2, 1, u32_le> new_hw_format; // mostly ignored
+ BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
BitField<8, 1, u32_le> increment; // increment the returned fence
} flags;
- Fence fence_out; // returned new fence object for others to wait on
+ Fence fence_out{}; // returned new fence object for others to wait on
+
+ u32 AddIncrementValue() const {
+ return flags.add_increment.Value() << 1;
+ }
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
- u32 unknown; // seems to be ignored? Nintendo added this
- u32 value;
+ u32 unknown{}; // seems to be ignored? Nintendo added this
+ u32 value{};
};
static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size");
- u32_le nvmap_fd{};
+ s32_le nvmap_fd{};
u64_le user_data{};
IoctlZCullBind zcull_params{};
u32_le channel_priority{};
u32_le channel_timeslice{};
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetClientData(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullBind(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
- u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
- const std::vector<u8>& input2, IoctlVersion version);
- u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullBind(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output,
+ Tegra::CommandList&& entries);
+ NvResult SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output,
+ bool kickoff = false);
+ NvResult SubmitGPFIFOBase(const std::vector<u8>& input, const std::vector<u8>& input_inline,
+ std::vector<u8>& output);
+ NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
- u32 assigned_syncpoints{};
+ SyncpointManager& syncpoint_manager;
+ Fence channel_fence;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index bdae8b887..d8735491c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -2,39 +2,71 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cstring>
-
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
+nvhost_nvdec::nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvhost_nvdec_common(system, std::move(nvmap_dev)) {}
nvhost_nvdec::~nvhost_nvdec() = default;
-u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1:
+ return Submit(input, output);
+ case 0x2:
+ return GetSyncpoint(input, output);
+ case 0x3:
+ return GetWaitbase(input, output);
+ case 0x7:
+ return SetSubmitTimeout(input, output);
+ case 0x9:
+ return MapBuffer(input, output);
+ case 0xa: {
+ if (command.length == 0x1c) {
+ LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
+ Tegra::ChCommandHeaderList cmdlist(1);
+ cmdlist[0] = Tegra::ChCommandHeader{0xDEADB33F};
+ system.GPU().PushCommandBuffer(cmdlist);
+ }
+ return UnmapBuffer(input, output);
+ }
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input);
+ default:
+ break;
+ }
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- nvmap_fd = params.nvmap_fd;
- return 0;
+NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index cbdac8069..79b8b6de1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -4,35 +4,21 @@
#pragma once
-#include <vector>
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include <memory>
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
namespace Service::Nvidia::Devices {
-class nvhost_nvdec final : public nvdevice {
+class nvhost_nvdec final : public nvhost_nvdec_common {
public:
- explicit nvhost_nvdec(Core::System& system);
+ explicit nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_nvdec() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
-private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
- struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
- };
- static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
-
- u32_le nvmap_fd{};
-
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
new file mode 100644
index 000000000..b49cecb42
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -0,0 +1,229 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/memory.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
+
+namespace Service::Nvidia::Devices {
+
+namespace {
+// Splice vectors will copy count amount of type T from the input vector into the dst vector.
+template <typename T>
+std::size_t SpliceVectors(const std::vector<u8>& input, std::vector<T>& dst, std::size_t count,
+ std::size_t offset) {
+ std::memcpy(dst.data(), input.data() + offset, count * sizeof(T));
+ offset += count * sizeof(T);
+ return offset;
+}
+
+// Write vectors will write data to the output buffer
+template <typename T>
+std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::size_t offset) {
+ std::memcpy(dst.data() + offset, src.data(), src.size() * sizeof(T));
+ offset += src.size() * sizeof(T);
+ return offset;
+}
+} // Anonymous namespace
+
+nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
+nvhost_nvdec_common::~nvhost_nvdec_common() = default;
+
+NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
+ IoctlSetNvmapFD params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSetNvmapFD));
+ LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+
+ nvmap_fd = params.nvmap_fd;
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlSubmit params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSubmit));
+ LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count);
+
+ // Instantiate param buffers
+ std::size_t offset = sizeof(IoctlSubmit);
+ std::vector<CommandBuffer> command_buffers(params.cmd_buffer_count);
+ std::vector<Reloc> relocs(params.relocation_count);
+ std::vector<u32> reloc_shifts(params.relocation_count);
+ std::vector<SyncptIncr> syncpt_increments(params.syncpoint_count);
+ std::vector<SyncptIncr> wait_checks(params.syncpoint_count);
+ std::vector<Fence> fences(params.fence_count);
+
+ // Splice input into their respective buffers
+ offset = SpliceVectors(input, command_buffers, params.cmd_buffer_count, offset);
+ offset = SpliceVectors(input, relocs, params.relocation_count, offset);
+ offset = SpliceVectors(input, reloc_shifts, params.relocation_count, offset);
+ offset = SpliceVectors(input, syncpt_increments, params.syncpoint_count, offset);
+ offset = SpliceVectors(input, wait_checks, params.syncpoint_count, offset);
+ offset = SpliceVectors(input, fences, params.fence_count, offset);
+
+ // TODO(ameerj): For async gpu, utilize fences for syncpoint 'max' increment
+
+ auto& gpu = system.GPU();
+
+ for (const auto& cmd_buffer : command_buffers) {
+ auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
+ ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
+ const auto map = FindBufferMap(object->dma_map_addr);
+ if (!map) {
+ LOG_ERROR(Service_NVDRV, "Tried to submit an invalid offset 0x{:X} dma 0x{:X}",
+ object->addr, object->dma_map_addr);
+ return NvResult::Success;
+ }
+ Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
+ gpu.MemoryManager().ReadBlock(map->StartAddr() + cmd_buffer.offset, cmdlist.data(),
+ cmdlist.size() * sizeof(u32));
+ gpu.PushCommandBuffer(cmdlist);
+ }
+
+ std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
+ // Some games expect command_buffers to be written back
+ offset = sizeof(IoctlSubmit);
+ offset = WriteVectors(output, command_buffers, offset);
+ offset = WriteVectors(output, relocs, offset);
+ offset = WriteVectors(output, reloc_shifts, offset);
+ offset = WriteVectors(output, syncpt_increments, offset);
+ offset = WriteVectors(output, wait_checks, offset);
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetSyncpoint params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
+ LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
+
+ // We found that implementing this causes deadlocks with async gpu, along with degraded
+ // performance. TODO: RE the nvdec async implementation
+ params.value = 0;
+ std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetWaitbase params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
+ params.value = 0; // Seems to be hard coded at 0
+ std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+
+ SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+
+ auto& gpu = system.GPU();
+
+ for (auto& cmf_buff : cmd_buffer_handles) {
+ auto object{nvmap_dev->GetObject(cmf_buff.map_handle)};
+ if (!object) {
+ LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
+ if (object->dma_map_addr == 0) {
+ // NVDEC and VIC memory is in the 32-bit address space
+ // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
+ const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size);
+ object->dma_map_addr = static_cast<u32>(low_addr);
+ // Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
+ ASSERT(object->dma_map_addr == low_addr);
+ }
+ if (!object->dma_map_addr) {
+ LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
+ } else {
+ cmf_buff.map_address = object->dma_map_addr;
+ AddBufferMap(object->dma_map_addr, object->size, object->addr,
+ object->status == nvmap::Object::Status::Allocated);
+ }
+ }
+ std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
+ std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
+ cmd_buffer_handles.size() * sizeof(MapBufferEntry));
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+ SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+
+ auto& gpu = system.GPU();
+
+ for (auto& cmf_buff : cmd_buffer_handles) {
+ const auto object{nvmap_dev->GetObject(cmf_buff.map_handle)};
+ if (!object) {
+ LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
+ if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) {
+ gpu.MemoryManager().Unmap(object->dma_map_addr, *size);
+ } else {
+ // This occurs quite frequently, however does not seem to impact functionality
+ LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr,
+ object->dma_map_addr);
+ }
+ object->dma_map_addr = 0;
+ }
+ std::memset(output.data(), 0, output.size());
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ std::memcpy(&submit_timeout, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ return NvResult::Success;
+}
+
+std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap(
+ GPUVAddr gpu_addr) const {
+ const auto it = std::find_if(
+ buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) {
+ return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr());
+ });
+
+ ASSERT(it != buffer_mappings.end());
+ return it->second;
+}
+
+void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated) {
+ buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated});
+}
+
+std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) {
+ const auto iter{buffer_mappings.find(gpu_addr)};
+ if (iter == buffer_mappings.end()) {
+ return std::nullopt;
+ }
+ std::size_t size = 0;
+ if (iter->second.IsAllocated()) {
+ size = iter->second.Size();
+ }
+ buffer_mappings.erase(iter);
+ return size;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
new file mode 100644
index 000000000..86ba3a4d1
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -0,0 +1,196 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service::Nvidia::Devices {
+class nvmap;
+
+class nvhost_nvdec_common : public nvdevice {
+public:
+ explicit nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
+ ~nvhost_nvdec_common() override;
+
+ /**
+ * Handles an ioctl1 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param output A buffer where the output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) = 0;
+
+ /**
+ * Handles an ioctl2 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param inline_input A buffer containing the input data for the ioctl which has been inlined.
+ * @param output A buffer where the output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) = 0;
+
+ /**
+ * Handles an ioctl3 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param output A buffer where the output data will be written to.
+ * @param inline_output A buffer where the inlined output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) = 0;
+
+protected:
+ class BufferMap final {
+ public:
+ constexpr BufferMap() = default;
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size)
+ : start_addr{start_addr}, end_addr{start_addr + size} {}
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated)
+ : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr},
+ is_allocated{is_allocated} {}
+
+ constexpr VAddr StartAddr() const {
+ return start_addr;
+ }
+
+ constexpr VAddr EndAddr() const {
+ return end_addr;
+ }
+
+ constexpr std::size_t Size() const {
+ return end_addr - start_addr;
+ }
+
+ constexpr VAddr CpuAddr() const {
+ return cpu_addr;
+ }
+
+ constexpr bool IsAllocated() const {
+ return is_allocated;
+ }
+
+ private:
+ GPUVAddr start_addr{};
+ GPUVAddr end_addr{};
+ VAddr cpu_addr{};
+ bool is_allocated{};
+ };
+
+ struct IoctlSetNvmapFD {
+ s32_le nvmap_fd{};
+ };
+ static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
+
+ struct IoctlSubmitCommandBuffer {
+ u32_le id{};
+ u32_le offset{};
+ u32_le count{};
+ };
+ static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC,
+ "IoctlSubmitCommandBuffer is incorrect size");
+ struct IoctlSubmit {
+ u32_le cmd_buffer_count{};
+ u32_le relocation_count{};
+ u32_le syncpoint_count{};
+ u32_le fence_count{};
+ };
+ static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size");
+
+ struct CommandBuffer {
+ s32 memory_id{};
+ u32 offset{};
+ s32 word_count{};
+ };
+ static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size");
+
+ struct Reloc {
+ s32 cmdbuffer_memory{};
+ s32 cmdbuffer_offset{};
+ s32 target{};
+ s32 target_offset{};
+ };
+ static_assert(sizeof(Reloc) == 0x10, "CommandBuffer has incorrect size");
+
+ struct SyncptIncr {
+ u32 id{};
+ u32 increments{};
+ };
+ static_assert(sizeof(SyncptIncr) == 0x8, "CommandBuffer has incorrect size");
+
+ struct Fence {
+ u32 id{};
+ u32 value{};
+ };
+ static_assert(sizeof(Fence) == 0x8, "CommandBuffer has incorrect size");
+
+ struct IoctlGetSyncpoint {
+ // Input
+ u32_le param{};
+ // Output
+ u32_le value{};
+ };
+ static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size");
+
+ struct IoctlGetWaitbase {
+ u32_le unknown{}; // seems to be ignored? Nintendo added this
+ u32_le value{};
+ };
+ static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size");
+
+ struct IoctlMapBuffer {
+ u32_le num_entries{};
+ u32_le data_address{}; // Ignored by the driver.
+ u32_le attach_host_ch_das{};
+ };
+ static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");
+
+ struct IocGetIdParams {
+ // Input
+ u32_le param{};
+ // Output
+ u32_le value{};
+ };
+ static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
+
+ // Used for mapping and unmapping command buffers
+ struct MapBufferEntry {
+ u32_le map_handle{};
+ u32_le map_address{};
+ };
+ static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");
+
+ /// Ioctl command implementations
+ NvResult SetNVMAPfd(const std::vector<u8>& input);
+ NvResult Submit(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
+
+ std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
+ void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
+ std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+
+ s32_le nvmap_fd{};
+ u32_le submit_timeout{};
+ std::shared_ptr<nvmap> nvmap_dev;
+
+ // This is expected to be ordered, therefore we must use a map, not unordered_map
+ std::map<GPUVAddr, BufferMap> buffer_mappings;
+};
+}; // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index 96e7b7dab..2d06955c0 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -13,28 +13,44 @@ namespace Service::Nvidia::Devices {
nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default;
-u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input, output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetNvmapFD params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 98dcac52f..43948d18d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -16,23 +16,21 @@ public:
explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
+ s32_le nvmap_fd{};
};
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
- u32_le nvmap_fd{};
+ s32_le nvmap_fd{};
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index c695b8863..805fe86ae 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -2,39 +2,63 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cstring>
-
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
+nvhost_vic::nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvhost_nvdec_common(system, std::move(nvmap_dev)) {}
-nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
nvhost_vic::~nvhost_vic() = default;
-u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1:
+ return Submit(input, output);
+ case 0x2:
+ return GetSyncpoint(input, output);
+ case 0x3:
+ return GetWaitbase(input, output);
+ case 0x9:
+ return MapBuffer(input, output);
+ case 0xa:
+ return UnmapBuffer(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- nvmap_fd = params.nvmap_fd;
- return 0;
+NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index bec32bea1..b2e11f4d4 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -4,35 +4,20 @@
#pragma once
-#include <vector>
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
namespace Service::Nvidia::Devices {
+class nvmap;
-class nvhost_vic final : public nvdevice {
+class nvhost_vic final : public nvhost_nvdec_common {
public:
- explicit nvhost_vic(Core::System& system);
- ~nvhost_vic() override;
-
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
-private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
- struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
- };
- static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
-
- u32_le nvmap_fd{};
-
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ explicit nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
+ ~nvhost_vic();
+
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
};
-
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 8c742316c..4015a2740 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -11,16 +11,54 @@
namespace Service::Nvidia::Devices {
-namespace NvErrCodes {
-enum {
- OperationNotPermitted = -1,
- InvalidValue = -22,
-};
+nvmap::nvmap(Core::System& system) : nvdevice(system) {
+ // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
+ // represent this.
+ CreateObject(0);
}
-nvmap::nvmap(Core::System& system) : nvdevice(system) {}
nvmap::~nvmap() = default;
+NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x1:
+ switch (command.cmd) {
+ case 0x1:
+ return IocCreate(input, output);
+ case 0x3:
+ return IocFromId(input, output);
+ case 0x4:
+ return IocAlloc(input, output);
+ case 0x5:
+ return IocFree(input, output);
+ case 0x9:
+ return IocParam(input, output);
+ case 0xe:
+ return IocGetId(input, output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvmap::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvmap::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
VAddr nvmap::GetObjectAddress(u32 handle) const {
auto object = GetObject(handle);
ASSERT(object);
@@ -28,66 +66,50 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
return object->addr;
}
-u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::Create:
- return IocCreate(input, output);
- case IoctlCommand::Alloc:
- return IocAlloc(input, output);
- case IoctlCommand::GetId:
- return IocGetId(input, output);
- case IoctlCommand::FromId:
- return IocFromId(input, output);
- case IoctlCommand::Param:
- return IocParam(input, output);
- case IoctlCommand::Free:
- return IocFree(input, output);
- }
+u32 nvmap::CreateObject(u32 size) {
+ // Create a new nvmap object and obtain a handle to it.
+ auto object = std::make_shared<Object>();
+ object->id = next_id++;
+ object->size = size;
+ object->status = Object::Status::Created;
+ object->refcount = 1;
+
+ const u32 handle = next_handle++;
+
+ handles.insert_or_assign(handle, std::move(object));
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ return handle;
}
-u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
if (!params.size) {
LOG_ERROR(Service_NVDRV, "Size is 0");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
- // Create a new nvmap object and obtain a handle to it.
- auto object = std::make_shared<Object>();
- object->id = next_id++;
- object->size = params.size;
- object->status = Object::Status::Created;
- object->refcount = 1;
-
- u32 handle = next_handle++;
- handles[handle] = std::move(object);
- params.handle = handle;
+ params.handle = CreateObject(params.size);
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
if (!params.handle) {
LOG_ERROR(Service_NVDRV, "Handle is 0");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if ((params.align - 1) & params.align) {
LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
const u32 min_alignment = 0x1000;
@@ -98,12 +120,12 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (object->status == Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::InsufficientMemory;
}
object->flags = params.flags;
@@ -113,10 +135,10 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
object->status = Object::Status::Allocated;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
@@ -124,22 +146,22 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
if (!params.handle) {
LOG_ERROR(Service_NVDRV, "Handle is zero");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::BadValue;
}
params.id = object->id;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
@@ -149,13 +171,13 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
[&](const auto& entry) { return entry.second->id == params.id; });
if (itr == handles.end()) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
auto& object = itr->second;
if (object->status != Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
itr->second->refcount++;
@@ -164,10 +186,10 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
params.handle = itr->first;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 };
IocParamParams params;
@@ -178,12 +200,12 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (object->status != Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::BadValue;
}
switch (static_cast<ParamTypes>(params.param)) {
@@ -205,10 +227,10 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
}
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
// TODO(Subv): These flags are unconfirmed.
enum FreeFlags {
Freed = 0,
@@ -223,14 +245,14 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
auto itr = handles.find(params.handle);
if (itr == handles.end()) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (!itr->second->refcount) {
LOG_ERROR(
Service_NVDRV,
"There is no references to this object. The object is already freed. handle={:08X}",
params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
itr->second->refcount--;
@@ -250,7 +272,7 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
handles.erase(params.handle);
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 73c2e8809..4484bd79f 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -19,13 +19,15 @@ public:
explicit nvmap(Core::System& system);
~nvmap() override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
+
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
/// Represents an nvmap object.
struct Object {
enum class Status { Created, Allocated };
@@ -37,6 +39,7 @@ public:
VAddr addr;
Status status;
u32 refcount;
+ u32 dma_map_addr;
};
std::shared_ptr<Object> GetObject(u32 handle) const {
@@ -49,82 +52,76 @@ public:
private:
/// Id to use for the next handle that is created.
- u32 next_handle = 1;
+ u32 next_handle = 0;
/// Id to use for the next object that is created.
- u32 next_id = 1;
+ u32 next_id = 0;
/// Mapping of currently allocated handles to the objects they represent.
std::unordered_map<u32, std::shared_ptr<Object>> handles;
- enum class IoctlCommand : u32 {
- Create = 0xC0080101,
- FromId = 0xC0080103,
- Alloc = 0xC0200104,
- Free = 0xC0180105,
- Param = 0xC00C0109,
- GetId = 0xC008010E,
- };
struct IocCreateParams {
// Input
- u32_le size;
+ u32_le size{};
// Output
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size");
struct IocFromIdParams {
// Input
- u32_le id;
+ u32_le id{};
// Output
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size");
struct IocAllocParams {
// Input
- u32_le handle;
- u32_le heap_mask;
- u32_le flags;
- u32_le align;
- u8 kind;
+ u32_le handle{};
+ u32_le heap_mask{};
+ u32_le flags{};
+ u32_le align{};
+ u8 kind{};
INSERT_PADDING_BYTES(7);
- u64_le addr;
+ u64_le addr{};
};
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
struct IocFreeParams {
- u32_le handle;
+ u32_le handle{};
INSERT_PADDING_BYTES(4);
- u64_le address;
- u32_le size;
- u32_le flags;
+ u64_le address{};
+ u32_le size{};
+ u32_le flags{};
};
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
struct IocParamParams {
// Input
- u32_le handle;
- u32_le param;
+ u32_le handle{};
+ u32_le param{};
// Output
- u32_le result;
+ u32_le result{};
};
static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size");
struct IocGetIdParams {
// Output
- u32_le id;
+ u32_le id{};
// Input
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
- u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocParam(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocFree(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 CreateObject(u32 size);
+
+ NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index c8ea6c661..f6c38e853 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -23,138 +23,184 @@ void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
const auto& buffer = ctx.ReadBuffer();
- std::string device_name(buffer.begin(), buffer.end());
+ const std::string device_name(buffer.begin(), buffer.end());
+ DeviceFD fd = nvdrv->Open(device_name);
- u32 fd = nvdrv->Open(device_name);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0);
+ rb.Push<DeviceFD>(fd);
+ rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed);
+}
+
+void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(result);
}
-void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) {
+void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
- u32 command = rp.Pop<u32>();
-
- /// Ioctl 3 has 2 outputs, first in the input params, second is the result
- std::vector<u8> output(ctx.GetWriteBufferSize(0));
- std::vector<u8> output2;
- if (version == IoctlVersion::Version3) {
- output2.resize((ctx.GetWriteBufferSize(1)));
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
}
- /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer.
- /// KickOfPB uses this
- auto input = ctx.ReadBuffer(0);
+ // Check device
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+ const auto input_buffer = ctx.ReadBuffer(0);
- std::vector<u8> input2;
- if (version == IoctlVersion::Version2) {
- input2 = ctx.ReadBuffer(1);
- }
+ const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer);
- IoctlCtrl ctrl{};
-
- u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version);
-
- if (ctrl.must_delay) {
- ctrl.fresh_call = false;
- ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout,
- [=](std::shared_ptr<Kernel::Thread> thread,
- Kernel::HLERequestContext& ctx,
- Kernel::ThreadWakeupReason reason) {
- IoctlCtrl ctrl2{ctrl};
- std::vector<u8> tmp_output = output;
- std::vector<u8> tmp_output2 = output2;
- u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
- tmp_output2, ctrl2, version);
- ctx.WriteBuffer(tmp_output, 0);
- if (version == IoctlVersion::Version3) {
- ctx.WriteBuffer(tmp_output2, 1);
- }
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(result);
- },
- nvdrv->GetEventWriteable(ctrl.event_id));
- } else {
- ctx.WriteBuffer(output);
- if (version == IoctlVersion::Version3) {
- ctx.WriteBuffer(output2, 1);
- }
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer);
}
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(result);
-}
-
-void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version1);
+ rb.PushEnum(nv_result);
}
void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version2);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto input_buffer = ctx.ReadBuffer(0);
+ const auto input_inlined_buffer = ctx.ReadBuffer(1);
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+
+ const auto nv_result =
+ nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer);
+
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(nv_result);
}
void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version3);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto input_buffer = ctx.ReadBuffer(0);
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+ std::vector<u8> output_buffer_inline(ctx.GetWriteBufferSize(1));
+
+ const auto nv_result =
+ nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline);
+
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer, 0);
+ ctx.WriteBuffer(output_buffer_inline, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(nv_result);
}
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
- IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
- auto result = nvdrv->Close(fd);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto result = nvdrv->Close(fd);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(result);
}
void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ is_initialized = true;
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.PushEnum(NvResult::Success);
}
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
- // TODO(Blinkhawk): Figure the meaning of the flag at bit 16
- u32 event_id = rp.Pop<u32>() & 0x000000FF;
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto event_id = rp.Pop<u32>() & 0x00FF;
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
- IPC::ResponseBuilder rb{ctx, 3, 1};
- rb.Push(RESULT_SUCCESS);
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto nv_result = nvdrv->VerifyFD(fd);
+ if (nv_result != NvResult::Success) {
+ LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
+ ServiceError(ctx, nv_result);
+ return;
+ }
+
if (event_id < MaxNvEvents) {
+ IPC::ResponseBuilder rb{ctx, 3, 1};
+ rb.Push(RESULT_SUCCESS);
auto event = nvdrv->GetEvent(event_id);
event->Clear();
rb.PushCopyObjects(event);
- rb.Push<u32>(NvResult::Success);
+ rb.PushEnum(NvResult::Success);
} else {
- rb.Push<u32>(0);
- rb.Push<u32>(NvResult::BadParameter);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(NvResult::BadParameter);
}
}
-void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) {
+void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
pid = rp.Pop<u64>();
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.PushEnum(NvResult::Success);
}
-void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) {
+void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
@@ -164,8 +210,9 @@ void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) {
void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(NvResult::Success);
}
void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) {
@@ -181,19 +228,20 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
: ServiceFramework(name), nvdrv(std::move(nvdrv)) {
static const FunctionInfo functions[] = {
{0, &NVDRV::Open, "Open"},
- {1, &NVDRV::Ioctl, "Ioctl"},
+ {1, &NVDRV::Ioctl1, "Ioctl"},
{2, &NVDRV::Close, "Close"},
{3, &NVDRV::Initialize, "Initialize"},
{4, &NVDRV::QueryEvent, "QueryEvent"},
{5, nullptr, "MapSharedMem"},
{6, &NVDRV::GetStatus, "GetStatus"},
- {7, nullptr, "ForceSetClientPID"},
- {8, &NVDRV::SetClientPID, "SetClientPID"},
+ {7, nullptr, "SetAruidForTest"},
+ {8, &NVDRV::SetAruid, "SetAruid"},
{9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"},
{10, nullptr, "InitializeDevtools"},
{11, &NVDRV::Ioctl2, "Ioctl2"},
{12, &NVDRV::Ioctl3, "Ioctl3"},
- {13, &NVDRV::FinishInitialize, "FinishInitialize"},
+ {13, &NVDRV::SetGraphicsFirmwareMemoryMarginEnabled,
+ "SetGraphicsFirmwareMemoryMarginEnabled"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 9269ce00c..e05f905ae 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -23,21 +23,23 @@ public:
private:
void Open(Kernel::HLERequestContext& ctx);
- void Ioctl(Kernel::HLERequestContext& ctx);
+ void Ioctl1(Kernel::HLERequestContext& ctx);
void Ioctl2(Kernel::HLERequestContext& ctx);
void Ioctl3(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
void Initialize(Kernel::HLERequestContext& ctx);
void QueryEvent(Kernel::HLERequestContext& ctx);
- void SetClientPID(Kernel::HLERequestContext& ctx);
- void FinishInitialize(Kernel::HLERequestContext& ctx);
+ void SetAruid(Kernel::HLERequestContext& ctx);
+ void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx);
void GetStatus(Kernel::HLERequestContext& ctx);
void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx);
- void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version);
+
+ void ServiceError(Kernel::HLERequestContext& ctx, NvResult result);
std::shared_ptr<Module> nvdrv;
u64 pid{};
+ bool is_initialized{};
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index 529b03471..3294bc0e7 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -1,12 +1,16 @@
#pragma once
#include <array>
+#include "common/bit_field.h"
#include "common/common_types.h"
namespace Service::Nvidia {
constexpr u32 MaxSyncPoints = 192;
constexpr u32 MaxNvEvents = 64;
+using DeviceFD = s32;
+
+constexpr DeviceFD INVALID_NVDRV_FD = -1;
struct Fence {
s32 id;
@@ -20,11 +24,61 @@ struct MultiFence {
std::array<Fence, 4> fences;
};
-enum NvResult : u32 {
- Success = 0,
- BadParameter = 4,
- Timeout = 5,
- ResourceError = 15,
+enum class NvResult : u32 {
+ Success = 0x0,
+ NotImplemented = 0x1,
+ NotSupported = 0x2,
+ NotInitialized = 0x3,
+ BadParameter = 0x4,
+ Timeout = 0x5,
+ InsufficientMemory = 0x6,
+ ReadOnlyAttribute = 0x7,
+ InvalidState = 0x8,
+ InvalidAddress = 0x9,
+ InvalidSize = 0xA,
+ BadValue = 0xB,
+ AlreadyAllocated = 0xD,
+ Busy = 0xE,
+ ResourceError = 0xF,
+ CountMismatch = 0x10,
+ OverFlow = 0x11,
+ InsufficientTransferMemory = 0x1000,
+ InsufficientVideoMemory = 0x10000,
+ BadSurfaceColorScheme = 0x10001,
+ InvalidSurface = 0x10002,
+ SurfaceNotSupported = 0x10003,
+ DispInitFailed = 0x20000,
+ DispAlreadyAttached = 0x20001,
+ DispTooManyDisplays = 0x20002,
+ DispNoDisplaysAttached = 0x20003,
+ DispModeNotSupported = 0x20004,
+ DispNotFound = 0x20005,
+ DispAttachDissallowed = 0x20006,
+ DispTypeNotSupported = 0x20007,
+ DispAuthenticationFailed = 0x20008,
+ DispNotAttached = 0x20009,
+ DispSamePwrState = 0x2000A,
+ DispEdidFailure = 0x2000B,
+ DispDsiReadAckError = 0x2000C,
+ DispDsiReadInvalidResp = 0x2000D,
+ FileWriteFailed = 0x30000,
+ FileReadFailed = 0x30001,
+ EndOfFile = 0x30002,
+ FileOperationFailed = 0x30003,
+ DirOperationFailed = 0x30004,
+ EndOfDirList = 0x30005,
+ ConfigVarNotFound = 0x30006,
+ InvalidConfigVar = 0x30007,
+ LibraryNotFound = 0x30008,
+ SymbolNotFound = 0x30009,
+ MemoryMapFailed = 0x3000A,
+ IoctlFailed = 0x3000F,
+ AccessDenied = 0x30010,
+ DeviceNotFound = 0x30011,
+ KernelDriverNotFound = 0x30012,
+ FileNotFound = 0x30013,
+ PathAlreadyExists = 0x30014,
+ ModuleNotPresent = 0xA000E,
};
enum class EventState {
@@ -34,21 +88,13 @@ enum class EventState {
Busy = 3,
};
-enum class IoctlVersion : u32 {
- Version1,
- Version2,
- Version3,
-};
-
-struct IoctlCtrl {
- // First call done to the servioce for services that call itself again after a call.
- bool fresh_call{true};
- // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep
- bool must_delay{};
- // Timeout for the delay
- s64 timeout{};
- // NV Event Id
- s32 event_id{-1};
+union Ioctl {
+ u32_le raw;
+ BitField<0, 8, u32> cmd;
+ BitField<8, 8, u32> group;
+ BitField<16, 14, u32> length;
+ BitField<30, 1, u32> is_in;
+ BitField<31, 1, u32> is_out;
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 197c77db0..bdbbedd0d 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -5,6 +5,7 @@
#include <utility>
#include <fmt/format.h>
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
@@ -21,6 +22,7 @@
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvmemp.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/hle/service/nvflinger/nvflinger.h"
namespace Service::Nvidia {
@@ -36,58 +38,125 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
nvflinger.SetNVDrvInstance(module_);
}
-Module::Module(Core::System& system) {
+Module::Module(Core::System& system) : syncpoint_manager{system.GPU()} {
auto& kernel = system.Kernel();
for (u32 i = 0; i < MaxNvEvents; i++) {
std::string event_label = fmt::format("NVDRV::NvEvent_{}", i);
- events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(kernel, event_label);
+ events_interface.events[i] = {Kernel::WritableEvent::CreateEventPair(kernel, event_label)};
events_interface.status[i] = EventState::Free;
events_interface.registered[i] = false;
}
auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
- devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev);
+ devices["/dev/nvhost-gpu"] =
+ std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
devices["/dev/nvmap"] = nvmap_dev;
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
- devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface);
- devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system);
+ devices["/dev/nvhost-ctrl"] =
+ std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
+ devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev);
devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
- devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system);
+ devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system, nvmap_dev);
}
Module::~Module() = default;
-u32 Module::Open(const std::string& device_name) {
- ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}",
- device_name);
+NvResult Module::VerifyFD(DeviceFD fd) const {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ if (open_files.find(fd) == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return NvResult::Success;
+}
+
+DeviceFD Module::Open(const std::string& device_name) {
+ if (devices.find(device_name) == devices.end()) {
+ LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
+ return INVALID_NVDRV_FD;
+ }
auto device = devices[device_name];
- const u32 fd = next_fd++;
+ const DeviceFD fd = next_fd++;
open_files[fd] = std::move(device);
return fd;
}
-u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- auto itr = open_files.find(fd);
- ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
+NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
- auto& device = itr->second;
- return device->ioctl({command}, input, input2, output, output2, ctrl, version);
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl1(command, input, output);
}
-ResultCode Module::Close(u32 fd) {
- auto itr = open_files.find(fd);
- ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
+NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl2(command, input, inline_input, output);
+}
+
+NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl3(command, input, output, inline_output);
+}
+
+NvResult Module::Close(DeviceFD fd) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
open_files.erase(itr);
- // TODO(flerovium): return correct result code if operation failed.
- return RESULT_SUCCESS;
+ return NvResult::Success;
}
void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
@@ -95,17 +164,17 @@ void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
if (events_interface.assigned_syncpt[i] == syncpoint_id &&
events_interface.assigned_value[i] == value) {
events_interface.LiberateEvent(i);
- events_interface.events[i].writable->Signal();
+ events_interface.events[i].event.writable->Signal();
}
}
}
std::shared_ptr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const {
- return events_interface.events[event_id].readable;
+ return events_interface.events[event_id].event.readable;
}
std::shared_ptr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const {
- return events_interface.events[event_id].writable;
+ return events_interface.events[event_id].event.writable;
}
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index d7a1bef91..7654bb026 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -22,15 +23,23 @@ class NVFlinger;
namespace Service::Nvidia {
+class SyncpointManager;
+
namespace Devices {
class nvdevice;
}
+/// Represents an Nvidia event
+struct NvEvent {
+ Kernel::EventPair event;
+ Fence fence{};
+};
+
struct EventInterface {
// Mask representing currently busy events
u64 events_mask{};
// Each kernel event associated to an NV event
- std::array<Kernel::EventPair, MaxNvEvents> events;
+ std::array<NvEvent, MaxNvEvents> events;
// The status of the current NVEvent
std::array<EventState, MaxNvEvents> status{};
// Tells if an NVEvent is registered or not
@@ -54,7 +63,7 @@ struct EventInterface {
}
mask = mask >> 1;
}
- return {};
+ return std::nullopt;
}
void SetEventStatus(const u32 event_id, EventState new_status) {
EventState old_status = status[event_id];
@@ -103,14 +112,23 @@ public:
return std::static_pointer_cast<T>(itr->second);
}
+ NvResult VerifyFD(DeviceFD fd) const;
+
/// Opens a device node and returns a file descriptor to it.
- u32 Open(const std::string& device_name);
+ DeviceFD Open(const std::string& device_name);
+
/// Sends an ioctl command to the specified file descriptor.
- u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version);
+ NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output);
+
+ NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output);
+
+ NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output);
+
/// Closes a device file descriptor and returns operation success.
- ResultCode Close(u32 fd);
+ NvResult Close(DeviceFD fd);
void SignalSyncpt(const u32 syncpoint_id, const u32 value);
@@ -119,11 +137,14 @@ public:
std::shared_ptr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const;
private:
+ /// Manages syncpoints on the host
+ SyncpointManager syncpoint_manager;
+
/// Id to use for the next open file descriptor.
- u32 next_fd = 1;
+ DeviceFD next_fd = 1;
/// Mapping of file descriptors to the devices they reference.
- std::unordered_map<u32, std::shared_ptr<Devices::nvdevice>> open_files;
+ std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
/// Mapping of device node names to their implementation.
std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp
index b7b8b7a1b..73b37e805 100644
--- a/src/core/hle/service/nvdrv/nvmemp.cpp
+++ b/src/core/hle/service/nvdrv/nvmemp.cpp
@@ -10,19 +10,19 @@ namespace Service::Nvidia {
NVMEMP::NVMEMP() : ServiceFramework("nvmemp") {
static const FunctionInfo functions[] = {
- {0, &NVMEMP::Cmd0, "Cmd0"},
- {1, &NVMEMP::Cmd1, "Cmd1"},
+ {0, &NVMEMP::Open, "Open"},
+ {1, &NVMEMP::GetAruid, "GetAruid"},
};
RegisterHandlers(functions);
}
NVMEMP::~NVMEMP() = default;
-void NVMEMP::Cmd0(Kernel::HLERequestContext& ctx) {
+void NVMEMP::Open(Kernel::HLERequestContext& ctx) {
UNIMPLEMENTED();
}
-void NVMEMP::Cmd1(Kernel::HLERequestContext& ctx) {
+void NVMEMP::GetAruid(Kernel::HLERequestContext& ctx) {
UNIMPLEMENTED();
}
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
index 6eafb1346..c453ee4db 100644
--- a/src/core/hle/service/nvdrv/nvmemp.h
+++ b/src/core/hle/service/nvdrv/nvmemp.h
@@ -14,8 +14,8 @@ public:
~NVMEMP() override;
private:
- void Cmd0(Kernel::HLERequestContext& ctx);
- void Cmd1(Kernel::HLERequestContext& ctx);
+ void Open(Kernel::HLERequestContext& ctx);
+ void GetAruid(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
new file mode 100644
index 000000000..0151a03b7
--- /dev/null
+++ b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
@@ -0,0 +1,39 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "video_core/gpu.h"
+
+namespace Service::Nvidia {
+
+SyncpointManager::SyncpointManager(Tegra::GPU& gpu) : gpu{gpu} {}
+
+SyncpointManager::~SyncpointManager() = default;
+
+u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
+ syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
+ return GetSyncpointMin(syncpoint_id);
+}
+
+u32 SyncpointManager::AllocateSyncpoint() {
+ for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
+ if (!syncpoints[syncpoint_id].is_allocated) {
+ syncpoints[syncpoint_id].is_allocated = true;
+ return syncpoint_id;
+ }
+ }
+ UNREACHABLE_MSG("No more available syncpoints!");
+ return {};
+}
+
+u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
+ for (u32 index = 0; index < value; ++index) {
+ syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ return GetSyncpointMax(syncpoint_id);
+}
+
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h
new file mode 100644
index 000000000..4168b6c7e
--- /dev/null
+++ b/src/core/hle/service/nvdrv/syncpoint_manager.h
@@ -0,0 +1,85 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <atomic>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra {
+class GPU;
+}
+
+namespace Service::Nvidia {
+
+class SyncpointManager final {
+public:
+ explicit SyncpointManager(Tegra::GPU& gpu);
+ ~SyncpointManager();
+
+ /**
+ * Returns true if the specified syncpoint is expired for the given value.
+ * @param syncpoint_id Syncpoint ID to check.
+ * @param value Value to check against the specified syncpoint.
+ * @returns True if the specified syncpoint is expired for the given value, otherwise False.
+ */
+ bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
+ return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
+ }
+
+ /**
+ * Gets the lower bound for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to get the lower bound for.
+ * @returns The lower bound for the specified syncpoint.
+ */
+ u32 GetSyncpointMin(u32 syncpoint_id) const {
+ return syncpoints[syncpoint_id].min.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Gets the uper bound for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to get the upper bound for.
+ * @returns The upper bound for the specified syncpoint.
+ */
+ u32 GetSyncpointMax(u32 syncpoint_id) const {
+ return syncpoints[syncpoint_id].max.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Refreshes the minimum value for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to be refreshed.
+ * @returns The new syncpoint minimum value.
+ */
+ u32 RefreshSyncpoint(u32 syncpoint_id);
+
+ /**
+ * Allocates a new syncoint.
+ * @returns The syncpoint ID for the newly allocated syncpoint.
+ */
+ u32 AllocateSyncpoint();
+
+ /**
+ * Increases the maximum value for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to be increased.
+ * @param value Value to increase the specified syncpoint by.
+ * @returns The new syncpoint maximum value.
+ */
+ u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
+
+private:
+ struct Syncpoint {
+ std::atomic<u32> min;
+ std::atomic<u32> max;
+ std::atomic<bool> is_allocated;
+ };
+
+ std::array<Syncpoint, MaxSyncPoints> syncpoints{};
+
+ Tegra::GPU& gpu;
+};
+
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index f1e3d832a..b89a2d41b 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -24,13 +24,17 @@ BufferQueue::~BufferQueue() = default;
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
- Buffer buffer{};
- buffer.slot = slot;
- buffer.igbp_buffer = igbp_buffer;
- buffer.status = Buffer::Status::Free;
free_buffers.push_back(slot);
+ queue.push_back({
+ .slot = slot,
+ .status = Buffer::Status::Free,
+ .igbp_buffer = igbp_buffer,
+ .transform = {},
+ .crop_rect = {},
+ .swap_interval = 0,
+ .multi_fence = {},
+ });
- queue.emplace_back(buffer);
buffer_wait_event.writable->Signal();
}
@@ -38,7 +42,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
u32 height) {
if (free_buffers.empty()) {
- return {};
+ return std::nullopt;
}
auto f_itr = free_buffers.begin();
@@ -69,7 +73,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
}
if (itr == queue.end()) {
- return {};
+ return std::nullopt;
}
itr->status = Buffer::Status::Dequeued;
@@ -99,18 +103,33 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
queue_sequence.push_back(slot);
}
+void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) {
+ const auto itr = std::find_if(queue.begin(), queue.end(),
+ [slot](const Buffer& buffer) { return buffer.slot == slot; });
+ ASSERT(itr != queue.end());
+ ASSERT(itr->status != Buffer::Status::Free);
+ itr->status = Buffer::Status::Free;
+ itr->multi_fence = multi_fence;
+ itr->swap_interval = 0;
+
+ free_buffers.push_back(slot);
+
+ buffer_wait_event.writable->Signal();
+}
+
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
auto itr = queue.end();
// Iterate to find a queued buffer matching the requested slot.
while (itr == queue.end() && !queue_sequence.empty()) {
- u32 slot = queue_sequence.front();
+ const u32 slot = queue_sequence.front();
itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
});
queue_sequence.pop_front();
}
- if (itr == queue.end())
- return {};
+ if (itr == queue.end()) {
+ return std::nullopt;
+ }
itr->status = Buffer::Status::Acquired;
return *itr;
}
@@ -138,9 +157,7 @@ u32 BufferQueue::Query(QueryType type) {
switch (type) {
case QueryType::NativeWindowFormat:
- // TODO(Subv): Use an enum for this
- static constexpr u32 FormatABGR8 = 1;
- return FormatABGR8;
+ return static_cast<u32>(PixelFormat::RGBA8888);
}
UNIMPLEMENTED();
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index d5f31e567..e7517c7e1 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -66,6 +66,16 @@ public:
Rotate270 = 0x07,
};
+ enum class PixelFormat : u32 {
+ RGBA8888 = 1,
+ RGBX8888 = 2,
+ RGB888 = 3,
+ RGB565 = 4,
+ BGRA8888 = 5,
+ RGBA5551 = 6,
+ RRGBA4444 = 7,
+ };
+
struct Buffer {
enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 };
@@ -85,6 +95,7 @@ public:
void QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence);
+ void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot);
void Disconnect();
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 437bc5dee..44aa2bdae 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
+#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
@@ -27,8 +28,34 @@
namespace Service::NVFlinger {
-constexpr s64 frame_ticks = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60);
-constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 30);
+constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
+
+void NVFlinger::VSyncThread(NVFlinger& nv_flinger) {
+ nv_flinger.SplitVSync();
+}
+
+void NVFlinger::SplitVSync() {
+ system.RegisterHostThread();
+ std::string name = "yuzu:VSyncThread";
+ MicroProfileOnThreadCreate(name.c_str());
+ Common::SetCurrentThreadName(name.c_str());
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ s64 delay = 0;
+ while (is_running) {
+ guard->lock();
+ const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count();
+ Compose();
+ const auto ticks = GetNextTicks();
+ const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count();
+ const s64 time_passed = time_end - time_start;
+ const s64 next_time = std::max<s64>(0, ticks - time_passed - delay);
+ guard->unlock();
+ if (next_time > 0) {
+ wait_event->WaitFor(std::chrono::nanoseconds{next_time});
+ }
+ delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time;
+ }
+}
NVFlinger::NVFlinger(Core::System& system) : system(system) {
displays.emplace_back(0, "Default", system);
@@ -36,22 +63,40 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
displays.emplace_back(2, "Edid", system);
displays.emplace_back(3, "Internal", system);
displays.emplace_back(4, "Null", system);
+ guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
- composition_event =
- Core::Timing::CreateEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) {
+ composition_event = Core::Timing::CreateEvent(
+ "ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
+ const auto guard = Lock();
Compose();
- const auto ticks =
- Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks();
- this->system.CoreTiming().ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late),
- composition_event);
+
+ const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
+ const auto ticks_delta = ticks - ns_late;
+ const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
+
+ this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
});
- system.CoreTiming().ScheduleEvent(frame_ticks, composition_event);
+ if (system.IsMulticore()) {
+ is_running = true;
+ wait_event = std::make_unique<Common::Event>();
+ vsync_thread = std::make_unique<std::thread>(VSyncThread, std::ref(*this));
+ } else {
+ system.CoreTiming().ScheduleEvent(frame_ns, composition_event);
+ }
}
NVFlinger::~NVFlinger() {
- system.CoreTiming().UnscheduleEvent(composition_event, 0);
+ if (system.IsMulticore()) {
+ is_running = false;
+ wait_event->Set();
+ vsync_thread->join();
+ vsync_thread.reset();
+ wait_event.reset();
+ } else {
+ system.CoreTiming().UnscheduleEvent(composition_event, 0);
+ }
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
@@ -69,7 +114,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
[&](const VI::Display& display) { return display.GetName() == name; });
if (itr == displays.end()) {
- return {};
+ return std::nullopt;
}
return itr->GetID();
@@ -79,7 +124,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
- return {};
+ return std::nullopt;
}
const u64 layer_id = next_layer_id++;
@@ -99,7 +144,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) co
const auto* const layer = FindLayer(display_id, layer_id);
if (layer == nullptr) {
- return {};
+ return std::nullopt;
}
return layer->GetBufferQueue().GetId();
@@ -197,12 +242,18 @@ void NVFlinger::Compose() {
const auto& igbp_buffer = buffer->get().igbp_buffer;
+ if (!system.IsPoweredOn()) {
+ return; // We are likely shutting down
+ }
+
auto& gpu = system.GPU();
const auto& multi_fence = buffer->get().multi_fence;
+ guard->unlock();
for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
const auto& fence = multi_fence.fences[fence_id];
gpu.WaitFence(fence.id, fence.value);
}
+ guard->lock();
MicroProfileFlip();
@@ -223,7 +274,7 @@ void NVFlinger::Compose() {
s64 NVFlinger::GetNextTicks() const {
constexpr s64 max_hertz = 120LL;
- return (Core::Hardware::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz;
+ return (1000000000 * (1LL << swap_interval)) / max_hertz;
}
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 57a21f33b..1ebe949c0 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -4,15 +4,22 @@
#pragma once
+#include <atomic>
#include <memory>
+#include <mutex>
#include <optional>
#include <string>
#include <string_view>
+#include <thread>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
+namespace Common {
+class Event;
+} // namespace Common
+
namespace Core::Timing {
class CoreTiming;
struct EventType;
@@ -47,12 +54,12 @@ public:
/// Opens the specified display and returns the ID.
///
/// If an invalid display name is provided, then an empty optional is returned.
- std::optional<u64> OpenDisplay(std::string_view name);
+ [[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
- std::optional<u64> CreateLayer(u64 display_id);
+ [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
/// Closes a layer on all displays for the given layer ID.
void CloseLayer(u64 layer_id);
@@ -60,37 +67,45 @@ public:
/// Finds the buffer queue ID of the specified layer in the specified display.
///
/// If an invalid display ID or layer ID is provided, then an empty optional is returned.
- std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const;
+ [[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const;
/// Gets the vsync event for the specified display.
///
/// If an invalid display ID is provided, then nullptr is returned.
- std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
+ [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
/// Obtains a buffer queue identified by the ID.
- BufferQueue& FindBufferQueue(u32 id);
+ [[nodiscard]] BufferQueue& FindBufferQueue(u32 id);
/// Obtains a buffer queue identified by the ID.
- const BufferQueue& FindBufferQueue(u32 id) const;
+ [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const;
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
void Compose();
- s64 GetNextTicks() const;
+ [[nodiscard]] s64 GetNextTicks() const;
+
+ [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
+ return std::unique_lock{*guard};
+ }
private:
/// Finds the display identified by the specified ID.
- VI::Display* FindDisplay(u64 display_id);
+ [[nodiscard]] VI::Display* FindDisplay(u64 display_id);
/// Finds the display identified by the specified ID.
- const VI::Display* FindDisplay(u64 display_id) const;
+ [[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const;
/// Finds the layer identified by the specified ID in the desired display.
- VI::Layer* FindLayer(u64 display_id, u64 layer_id);
+ [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
/// Finds the layer identified by the specified ID in the desired display.
- const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
+ [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
+
+ static void VSyncThread(NVFlinger& nv_flinger);
+
+ void SplitVSync();
std::shared_ptr<Nvidia::Module> nvdrv;
@@ -108,7 +123,13 @@ private:
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> composition_event;
+ std::shared_ptr<std::mutex> guard;
+
Core::System& system;
+
+ std::unique_ptr<std::thread> vsync_thread;
+ std::unique_ptr<Common::Event> wait_event;
+ std::atomic<bool> is_running{};
};
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp
new file mode 100644
index 000000000..aad4ca706
--- /dev/null
+++ b/src/core/hle/service/olsc/olsc.cpp
@@ -0,0 +1,69 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/olsc/olsc.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::OLSC {
+
+class OLSC final : public ServiceFramework<OLSC> {
+public:
+ explicit OLSC() : ServiceFramework{"olsc:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &OLSC::Initialize, "Initialize"},
+ {10, nullptr, "VerifySaveDataBackupLicenseAsync"},
+ {13, nullptr, "GetSaveDataBackupSetting"},
+ {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
+ {15, nullptr, "SetCustomData"},
+ {16, nullptr, "DeleteSaveDataBackupSetting"},
+ {18, nullptr, "GetSaveDataBackupInfoCache"},
+ {19, nullptr, "UpdateSaveDataBackupInfoCacheAsync"},
+ {22, nullptr, "DeleteSaveDataBackupAsync"},
+ {25, nullptr, "ListDownloadableSaveDataBackupInfoAsync"},
+ {26, nullptr, "DownloadSaveDataBackupAsync"},
+ {9010, nullptr, "VerifySaveDataBackupLicenseAsyncForDebug"},
+ {9013, nullptr, "GetSaveDataBackupSettingForDebug"},
+ {9014, nullptr, "SetSaveDataBackupSettingEnabledForDebug"},
+ {9015, nullptr, "SetCustomDataForDebug"},
+ {9016, nullptr, "DeleteSaveDataBackupSettingForDebug"},
+ {9018, nullptr, "GetSaveDataBackupInfoCacheForDebug"},
+ {9019, nullptr, "UpdateSaveDataBackupInfoCacheAsyncForDebug"},
+ {9022, nullptr, "DeleteSaveDataBackupAsyncForDebug"},
+ {9025, nullptr, "ListDownloadableSaveDataBackupInfoAsyncForDebug"},
+ {9026, nullptr, "DownloadSaveDataBackupAsyncForDebug"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_OLSC, "(STUBBED) called");
+
+ initialized = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetSaveDataBackupSettingEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_OLSC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ bool initialized{};
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<OLSC>()->InstallAsService(service_manager);
+}
+
+} // namespace Service::OLSC
diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h
new file mode 100644
index 000000000..edee4376b
--- /dev/null
+++ b/src/core/hle/service/olsc/olsc.h
@@ -0,0 +1,16 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::OLSC {
+
+/// Registers all SSL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::OLSC
diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp
index 39cf05eba..c568a0adc 100644
--- a/src/core/hle/service/pcie/pcie.cpp
+++ b/src/core/hle/service/pcie/pcie.cpp
@@ -36,6 +36,9 @@ public:
{18, nullptr, "ReleaseIrq"},
{19, nullptr, "SetIrqEnable"},
{20, nullptr, "SetAspmEnable"},
+ {21, nullptr, "SetResetUponResumeEnable"},
+ {22, nullptr, "Unknown22"},
+ {23, nullptr, "Unknown23"},
};
// clang-format on
diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp
index c75b4ee34..caf14ed61 100644
--- a/src/core/hle/service/pctl/module.cpp
+++ b/src/core/hle/service/pctl/module.cpp
@@ -31,6 +31,8 @@ public:
{1014, nullptr, "ConfirmPlayableApplicationVideoOld"},
{1015, nullptr, "ConfirmPlayableApplicationVideo"},
{1016, nullptr, "ConfirmShowNewsPermission"},
+ {1017, nullptr, "EndFreeCommunication"},
+ {1018, nullptr, "IsFreeCommunicationAvailable"},
{1031, nullptr, "IsRestrictionEnabled"},
{1032, nullptr, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"},
diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp
index d6891a659..8bfc0276e 100644
--- a/src/core/hle/service/pcv/pcv.cpp
+++ b/src/core/hle/service/pcv/pcv.cpp
@@ -42,6 +42,9 @@ public:
{24, nullptr, "GetModuleStateTable"},
{25, nullptr, "GetPowerDomainStateTable"},
{26, nullptr, "GetFuseInfo"},
+ {27, nullptr, "GetDramId"},
+ {28, nullptr, "IsPoweredOn"},
+ {29, nullptr, "GetVoltage"},
};
// clang-format on
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index 809eca0ab..a771a51b4 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -78,13 +79,13 @@ public:
: ServiceFramework{"pm:dmnt"}, kernel(kernel) {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetDebugProcesses"},
- {1, nullptr, "StartDebugProcess"},
- {2, &DebugMonitor::GetTitlePid, "GetTitlePid"},
- {3, nullptr, "EnableDebugForTitleId"},
- {4, &DebugMonitor::GetApplicationPid, "GetApplicationPid"},
- {5, nullptr, "EnableDebugForApplication"},
- {6, nullptr, "DisableDebug"},
+ {0, nullptr, "GetJitDebugProcessIdList"},
+ {1, nullptr, "StartProcess"},
+ {2, &DebugMonitor::GetProcessId, "GetProcessId"},
+ {3, nullptr, "HookToCreateProcess"},
+ {4, &DebugMonitor::GetApplicationProcessId, "GetApplicationProcessId"},
+ {5, nullptr, "HookToCreateApplicationProgress"},
+ {6, nullptr, "ClearHook"},
};
// clang-format on
@@ -92,7 +93,7 @@ public:
}
private:
- void GetTitlePid(Kernel::HLERequestContext& ctx) {
+ void GetProcessId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
@@ -114,7 +115,7 @@ private:
rb.Push((*process)->GetProcessID());
}
- void GetApplicationPid(Kernel::HLERequestContext& ctx) {
+ void GetApplicationProcessId(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PM, "called");
GetApplicationPidGeneric(ctx, kernel.GetProcessList());
}
@@ -163,15 +164,15 @@ public:
: ServiceFramework{"pm:shell"}, kernel(kernel) {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "LaunchProcess"},
- {1, nullptr, "TerminateProcessByPid"},
- {2, nullptr, "TerminateProcessByTitleId"},
- {3, nullptr, "GetProcessEventWaiter"},
- {4, nullptr, "GetProcessEventType"},
+ {0, nullptr, "LaunchProgram"},
+ {1, nullptr, "TerminateProcess"},
+ {2, nullptr, "TerminateProgram"},
+ {3, nullptr, "GetProcessEventHandle"},
+ {4, nullptr, "GetProcessEventInfo"},
{5, nullptr, "NotifyBootFinished"},
- {6, &Shell::GetApplicationPid, "GetApplicationPid"},
+ {6, &Shell::GetApplicationProcessIdForShell, "GetApplicationProcessIdForShell"},
{7, nullptr, "BoostSystemMemoryResourceLimit"},
- {8, nullptr, "EnableAdditionalSystemThreads"},
+ {8, nullptr, "BoostApplicationThreadResourceLimit"},
{9, nullptr, "GetBootFinishedEventHandle"},
};
// clang-format on
@@ -180,7 +181,7 @@ public:
}
private:
- void GetApplicationPid(Kernel::HLERequestContext& ctx) {
+ void GetApplicationProcessIdForShell(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PM, "called");
GetApplicationPidGeneric(ctx, kernel.GetProcessList());
}
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 8f1be0e48..b9ef86b72 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -4,6 +4,7 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -21,8 +22,10 @@ public:
static const FunctionInfo functions[] = {
{10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"},
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
- {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
- {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
+ {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
+ {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
+ {10104, nullptr, "SaveReport"},
+ {10105, nullptr, "SaveReportWithUser"},
{10200, nullptr, "RequestImmediateTransmission"},
{10300, nullptr, "GetTransmissionStatus"},
{10400, nullptr, "GetSystemSessionId"},
@@ -35,9 +38,16 @@ public:
{30400, nullptr, "GetStatistics"},
{30401, nullptr, "GetThroughputHistory"},
{30500, nullptr, "GetLastUploadError"},
+ {30600, nullptr, "GetApplicationUploadSummary"},
{40100, nullptr, "IsUserAgreementCheckEnabled"},
{40101, nullptr, "SetUserAgreementCheckEnabled"},
+ {50100, nullptr, "ReadAllApplicationReportFiles"},
{90100, nullptr, "ReadAllReportFiles"},
+ {90101, nullptr, "Unknown90101"},
+ {90102, nullptr, "Unknown90102"},
+ {90200, nullptr, "GetStatistics"},
+ {90201, nullptr, "GetThroughputHistory"},
+ {90300, nullptr, "GetLastUploadError"},
};
// clang-format on
@@ -51,7 +61,7 @@ private:
const auto process_id = rp.PopRaw<u64>();
std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
- if (Type == Core::Reporter::PlayReportType::New) {
+ if constexpr (Type == Core::Reporter::PlayReportType::Old2) {
data.emplace_back(ctx.ReadBuffer(1));
}
@@ -71,8 +81,13 @@ private:
const auto user_id = rp.PopRaw<u128>();
const auto process_id = rp.PopRaw<u64>();
std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
- if (Type == Core::Reporter::PlayReportType::New) {
- data.emplace_back(ctx.ReadBuffer(1));
+
+ if constexpr (Type == Core::Reporter::PlayReportType::Old2) {
+ const auto read_buffer_count =
+ ctx.BufferDescriptorX().size() + ctx.BufferDescriptorA().size();
+ if (read_buffer_count > 1) {
+ data.emplace_back(ctx.ReadBuffer(1));
+ }
}
LOG_DEBUG(
diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp
index 53ec6b031..99e1c9042 100644
--- a/src/core/hle/service/psc/psc.cpp
+++ b/src/core/hle/service/psc/psc.cpp
@@ -24,6 +24,8 @@ public:
{4, nullptr, "Cancel"},
{5, nullptr, "PrintModuleInformation"},
{6, nullptr, "GetModuleInformation"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
};
// clang-format on
diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp
index c2d5fda94..6d9e6bd09 100644
--- a/src/core/hle/service/ptm/psm.cpp
+++ b/src/core/hle/service/ptm/psm.cpp
@@ -12,9 +12,6 @@
namespace Service::PSM {
-constexpr u32 BATTERY_FULLY_CHARGED = 100; // 100% Full
-constexpr u32 BATTERY_CURRENTLY_CHARGING = 1; // Plugged into an official dock
-
class PSM final : public ServiceFramework<PSM> {
public:
explicit PSM() : ServiceFramework{"psm"} {
@@ -38,6 +35,7 @@ public:
{15, nullptr, "GetBatteryAgePercentage"},
{16, nullptr, "GetBatteryChargeInfoEvent"},
{17, nullptr, "GetBatteryChargeInfoFields"},
+ {18, nullptr, "GetBatteryChargeCalibratedEvent"},
};
// clang-format on
@@ -48,20 +46,30 @@ public:
private:
void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_PSM, "(STUBBED) called");
+ LOG_DEBUG(Service_PSM, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(BATTERY_FULLY_CHARGED);
+ rb.Push<u32>(battery_charge_percentage);
}
void GetChargerType(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_PSM, "(STUBBED) called");
+ LOG_DEBUG(Service_PSM, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(BATTERY_CURRENTLY_CHARGING);
+ rb.PushEnum(charger_type);
}
+
+ enum class ChargerType : u32 {
+ Unplugged = 0,
+ RegularCharger = 1,
+ LowPowerCharger = 2,
+ Unknown = 3,
+ };
+
+ u32 battery_charge_percentage{100}; // 100%
+ ChargerType charger_type{ChargerType::RegularCharger};
};
void InstallInterfaces(SM::ServiceManager& sm) {
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index fa5347af9..fbfda2d5b 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -51,6 +51,7 @@
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/olsc/olsc.h"
#include "core/hle/service/pcie/pcie.h"
#include "core/hle/service/pctl/module.h"
#include "core/hle/service/pcv/pcv.h"
@@ -89,8 +90,6 @@ namespace Service {
return function_string;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
InvokerFn* handler_invoker)
: service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
@@ -105,10 +104,9 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
port_installed = true;
}
-void ServiceFrameworkBase::InstallAsNamedPort() {
+void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
ASSERT(!port_installed);
- auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
@@ -116,10 +114,9 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
port_installed = true;
}
-std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
+std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) {
ASSERT(!port_installed);
- auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
auto port = MakeResult(std::move(server_port)).Unwrap();
@@ -191,9 +188,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
return RESULT_SUCCESS;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Module interface
-
/// Initialize ServiceManager
void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
@@ -238,6 +232,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
NPNS::InstallInterfaces(*sm);
NS::InstallInterfaces(*sm, system);
Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
+ OLSC::InstallInterfaces(*sm);
PCIe::InstallInterfaces(*sm);
PCTL::InstallInterfaces(*sm);
PCV::InstallInterfaces(*sm);
@@ -246,7 +241,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
PSC::InstallInterfaces(*sm);
PSM::InstallInterfaces(*sm);
Set::InstallInterfaces(*sm);
- Sockets::InstallInterfaces(*sm);
+ Sockets::InstallInterfaces(*sm, system);
SPL::InstallInterfaces(*sm);
SSL::InstallInterfaces(*sm);
Time::InstallInterfaces(system);
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:
/// Creates a port pair and registers this service with the given ServiceManager.
void InstallAsService(SM::ServiceManager& service_manager);
/// Creates a port pair and registers it on the kernel's global port registry.
- void InstallAsNamedPort();
+ void InstallAsNamedPort(Kernel::KernelCore& kernel);
/// Creates and returns an unregistered port for the service.
- std::shared_ptr<Kernel::ClientPort> CreatePort();
+ std::shared_ptr<Kernel::ClientPort> CreatePort(Kernel::KernelCore& kernel);
void InvokeRequest(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 9e12c76fc..ffbf90b00 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <chrono>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
@@ -31,6 +32,44 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
LanguageCode::ZH_HANT,
}};
+enum class KeyboardLayout : u64 {
+ Japanese = 0,
+ EnglishUs = 1,
+ EnglishUsInternational = 2,
+ EnglishUk = 3,
+ French = 4,
+ FrenchCa = 5,
+ Spanish = 6,
+ SpanishLatin = 7,
+ German = 8,
+ Italian = 9,
+ Portuguese = 10,
+ Russian = 11,
+ Korean = 12,
+ ChineseSimplified = 13,
+ ChineseTraditional = 14,
+};
+
+constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_layout{{
+ {LanguageCode::JA, KeyboardLayout::Japanese},
+ {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
+ {LanguageCode::FR, KeyboardLayout::French},
+ {LanguageCode::DE, KeyboardLayout::German},
+ {LanguageCode::IT, KeyboardLayout::Italian},
+ {LanguageCode::ES, KeyboardLayout::Spanish},
+ {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::KO, KeyboardLayout::Korean},
+ {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
+ {LanguageCode::PT, KeyboardLayout::Portuguese},
+ {LanguageCode::RU, KeyboardLayout::Russian},
+ {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
+ {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
+ {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
+ {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
+}};
+
constexpr std::size_t pre4_0_0_max_entries = 15;
constexpr std::size_t post4_0_0_max_entries = 17;
@@ -50,6 +89,25 @@ void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t m
ctx.WriteBuffer(available_language_codes.data(), copy_size);
PushResponseLanguageCode(ctx, copy_amount);
}
+
+void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
+ const auto language_code = available_language_codes[Settings::values.language_index.GetValue()];
+ const auto key_code =
+ std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
+ [=](const auto& element) { return element.first == language_code; });
+ KeyboardLayout layout = KeyboardLayout::EnglishUs;
+ if (key_code == language_to_layout.cend()) {
+ LOG_ERROR(Service_SET,
+ "Could not find keyboard layout for language index {}, defaulting to English us",
+ Settings::values.language_index.GetValue());
+ } else {
+ layout = key_code->second;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ ctx.WriteBuffer(layout);
+}
} // Anonymous namespace
LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
@@ -67,6 +125,7 @@ void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) {
const auto index = rp.Pop<u32>();
if (index >= available_language_codes.size()) {
+ LOG_ERROR(Service_SET, "Invalid language code index! index={}", index);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_LANGUAGE);
return;
@@ -104,11 +163,11 @@ void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) {
}
void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index);
+ LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index.GetValue());
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.PushEnum(available_language_codes[Settings::values.language_index]);
+ rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]);
}
void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
@@ -116,7 +175,17 @@ void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(Settings::values.region_index);
+ rb.Push(Settings::values.region_index.GetValue());
+}
+
+void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
+ GetKeyCodeMapImpl(ctx);
+}
+
+void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
+ GetKeyCodeMapImpl(ctx);
}
SET::SET() : ServiceFramework("set") {
@@ -129,10 +198,11 @@ SET::SET() : ServiceFramework("set") {
{4, &SET::GetRegionCode, "GetRegionCode"},
{5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
- {7, nullptr, "GetKeyCodeMap"},
+ {7, &SET::GetKeyCodeMap, "GetKeyCodeMap"},
{8, &SET::GetQuestFlag, "GetQuestFlag"},
- {9, nullptr, "GetKeyCodeMap2"},
+ {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
{10, nullptr, "GetFirmwareVersionForDebug"},
+ {11, nullptr, "GetDeviceNickName"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 6084b345d..8ac9c169d 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -44,6 +44,8 @@ private:
void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
void GetQuestFlag(Kernel::HLERequestContext& ctx);
void GetRegionCode(Kernel::HLERequestContext& ctx);
+ void GetKeyCodeMap(Kernel::HLERequestContext& ctx);
+ void GetKeyCodeMap2(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
index 1398a4a48..3fbfecc9e 100644
--- a/src/core/hle/service/set/set_cal.cpp
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -50,6 +50,8 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
{39, nullptr, "GetConsoleSixAxisSensorModuleType"},
{40, nullptr, "GetConsoleSixAxisSensorHorizontalOffset"},
{41, nullptr, "GetBatteryVersion"},
+ {42, nullptr, "GetDeviceId"},
+ {43, nullptr, "GetConsoleSixAxisSensorMountType"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index b7c9ea74b..080b5743e 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -288,6 +288,20 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
{186, nullptr, "GetMemoryUsageRateFlag"},
{187, nullptr, "GetTouchScreenMode"},
{188, nullptr, "SetTouchScreenMode"},
+ {189, nullptr, "GetButtonConfigSettingsFull"},
+ {190, nullptr, "SetButtonConfigSettingsFull"},
+ {191, nullptr, "GetButtonConfigSettingsEmbedded"},
+ {192, nullptr, "SetButtonConfigSettingsEmbedded"},
+ {193, nullptr, "GetButtonConfigSettingsLeft"},
+ {194, nullptr, "SetButtonConfigSettingsLeft"},
+ {195, nullptr, "GetButtonConfigSettingsRight"},
+ {196, nullptr, "SetButtonConfigSettingsRight"},
+ {197, nullptr, "GetButtonConfigRegisteredSettingsEmbedded"},
+ {198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"},
+ {199, nullptr, "GetButtonConfigRegisteredSettings"},
+ {200, nullptr, "SetButtonConfigRegisteredSettings"},
+ {201, nullptr, "GetFieldTestingFlag"},
+ {202, nullptr, "SetFieldTestingFlag"},
};
// clang-format on
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index 9cca84b31..972aaa6d9 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -12,7 +12,7 @@
namespace Service::SM {
-void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
+void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain");
LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetObjectId());
ctx.Session()->ConvertToDomain();
@@ -22,7 +22,7 @@ void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(1); // Converted sessions start with 1 request handler
}
-void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
+void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {
// TODO(bunnei): This is just creating a new handle to the same Session. I assume this is wrong
// and that we probably want to actually make an entirely new Session, but we still need to
// verify this on hardware.
@@ -33,10 +33,10 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
rb.PushMoveObjects(ctx.Session()->GetParent()->Client());
}
-void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called, using DuplicateSession");
+void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called, using CloneCurrentObject");
- DuplicateSession(ctx);
+ CloneCurrentObject(ctx);
}
void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
@@ -47,13 +47,14 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
rb.Push<u16>(0x1000);
}
+// https://switchbrew.org/wiki/IPC_Marshalling
Controller::Controller() : ServiceFramework("IpcController") {
static const FunctionInfo functions[] = {
- {0x00000000, &Controller::ConvertSessionToDomain, "ConvertSessionToDomain"},
- {0x00000001, nullptr, "ConvertDomainToSession"},
- {0x00000002, &Controller::DuplicateSession, "DuplicateSession"},
- {0x00000003, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"},
- {0x00000004, &Controller::DuplicateSessionEx, "DuplicateSessionEx"},
+ {0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"},
+ {1, nullptr, "CopyFromCurrentDomain"},
+ {2, &Controller::CloneCurrentObject, "CloneCurrentObject"},
+ {3, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"},
+ {4, &Controller::CloneCurrentObjectEx, "CloneCurrentObjectEx"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h
index dc66c9e37..180c6da50 100644
--- a/src/core/hle/service/sm/controller.h
+++ b/src/core/hle/service/sm/controller.h
@@ -14,9 +14,9 @@ public:
~Controller() override;
private:
- void ConvertSessionToDomain(Kernel::HLERequestContext& ctx);
- void DuplicateSession(Kernel::HLERequestContext& ctx);
- void DuplicateSessionEx(Kernel::HLERequestContext& ctx);
+ void ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx);
+ void CloneCurrentObject(Kernel::HLERequestContext& ctx);
+ void CloneCurrentObjectEx(Kernel::HLERequestContext& ctx);
void QueryPointerBufferSize(Kernel::HLERequestContext& ctx);
};
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 88909504d..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);
constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6);
constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
-ServiceManager::ServiceManager() = default;
+ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
ServiceManager::~ServiceManager() = default;
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
@@ -27,10 +27,12 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
}
static ResultCode ValidateServiceName(const std::string& name) {
- if (name.size() <= 0 || name.size() > 8) {
+ if (name.empty() || name.size() > 8) {
+ LOG_ERROR(Service_SM, "Invalid service name! service={}", name);
return ERR_INVALID_NAME;
}
- if (name.find('\0') != std::string::npos) {
+ if (name.rfind('\0') != std::string::npos) {
+ LOG_ERROR(Service_SM, "A non null terminated service was passed");
return ERR_INVALID_NAME;
}
return RESULT_SUCCESS;
@@ -41,20 +43,21 @@ void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
ASSERT(self->sm_interface.expired());
auto sm = std::make_shared<SM>(self, kernel);
- sm->InstallAsNamedPort();
+ sm->InstallAsNamedPort(kernel);
self->sm_interface = sm;
self->controller_interface = std::make_unique<Controller>();
}
-ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
- std::string name, unsigned int max_sessions) {
+ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(std::string name,
+ u32 max_sessions) {
CASCADE_CODE(ValidateServiceName(name));
- if (registered_services.find(name) != registered_services.end())
+ if (registered_services.find(name) != registered_services.end()) {
+ LOG_ERROR(Service_SM, "Service is already registered! service={}", name);
return ERR_ALREADY_REGISTERED;
+ }
- auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
@@ -66,9 +69,10 @@ ResultCode ServiceManager::UnregisterService(const std::string& name) {
CASCADE_CODE(ValidateServiceName(name));
const auto iter = registered_services.find(name);
- if (iter == registered_services.end())
+ if (iter == registered_services.end()) {
+ LOG_ERROR(Service_SM, "Server is not registered! service={}", name);
return ERR_SERVICE_NOT_REGISTERED;
-
+ }
registered_services.erase(iter);
return RESULT_SUCCESS;
}
@@ -79,6 +83,7 @@ ResultVal<std::shared_ptr<Kernel::ClientPort>> ServiceManager::GetServicePort(
CASCADE_CODE(ValidateServiceName(name));
auto it = registered_services.find(name);
if (it == registered_services.end()) {
+ LOG_ERROR(Service_SM, "Server is not registered! service={}", name);
return ERR_SERVICE_NOT_REGISTERED;
}
@@ -136,7 +141,7 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
}
// Wake the threads waiting on the ServerPort
- server_port->WakeupAllWaitingThreads();
+ server_port->Signal();
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, client->GetObjectId());
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index b06d2f103..6790c86f0 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -9,6 +9,7 @@
#include <type_traits>
#include <unordered_map>
+#include "common/concepts.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
@@ -47,19 +48,17 @@ class ServiceManager {
public:
static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
- ServiceManager();
+ explicit ServiceManager(Kernel::KernelCore& kernel_);
~ServiceManager();
ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name,
- unsigned int max_sessions);
+ u32 max_sessions);
ResultCode UnregisterService(const std::string& name);
ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
- template <typename T>
+ template <Common::DerivedFrom<Kernel::SessionRequestHandler> T>
std::shared_ptr<T> GetService(const std::string& service_name) const {
- static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
- "Not a base of ServiceFrameworkBase");
auto service = registered_services.find(service_name);
if (service == registered_services.end()) {
LOG_DEBUG(Service, "Can't find service: {}", service_name);
@@ -80,6 +79,9 @@ private:
/// Map of registered services, retrieved using GetServicePort or ConnectToService.
std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
+
+ /// Kernel context
+ Kernel::KernelCore& kernel;
};
} // namespace Service::SM
diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h
new file mode 100644
index 000000000..2d53e52b6
--- /dev/null
+++ b/src/core/hle/service/sockets/blocking_worker.h
@@ -0,0 +1,161 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <variant>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/assert.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/writable_event.h"
+
+namespace Service::Sockets {
+
+/**
+ * Worker abstraction to execute blocking calls on host without blocking the guest thread
+ *
+ * @tparam Service Service where the work is executed
+ * @tparam Types Types of work to execute
+ */
+template <class Service, class... Types>
+class BlockingWorker {
+ using This = BlockingWorker<Service, Types...>;
+ using WorkVariant = std::variant<std::monostate, Types...>;
+
+public:
+ /// Create a new worker
+ static std::unique_ptr<This> Create(Core::System& system, Service* service,
+ std::string_view name) {
+ return std::unique_ptr<This>(new This(system, service, name));
+ }
+
+ ~BlockingWorker() {
+ while (!is_available.load(std::memory_order_relaxed)) {
+ // Busy wait until work is finished
+ std::this_thread::yield();
+ }
+ // Monostate means to exit the thread
+ work = std::monostate{};
+ work_event.Set();
+ thread.join();
+ }
+
+ /**
+ * Try to capture the worker to send work after a success
+ * @returns True when the worker has been successfully captured
+ */
+ bool TryCapture() {
+ bool expected = true;
+ return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+
+ /**
+ * Send work to this worker abstraction
+ * @see TryCapture must be called before attempting to call this function
+ */
+ template <class Work>
+ void SendWork(Work new_work) {
+ ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured");
+ work = std::move(new_work);
+ work_event.Set();
+ }
+
+ /// Generate a callback for @see SleepClientThread
+ template <class Work>
+ auto Callback() {
+ return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
+ Kernel::ThreadWakeupReason reason) {
+ ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
+ std::get<Work>(work).Response(ctx);
+ is_available.store(true);
+ };
+ }
+
+ /// Get kernel event that will be signalled by the worker when the host operation finishes
+ std::shared_ptr<Kernel::WritableEvent> KernelEvent() const {
+ return kernel_event;
+ }
+
+private:
+ explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) {
+ auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name));
+ kernel_event = std::move(pair.writable);
+ thread = std::thread([this, &system, service, name] { Run(system, service, name); });
+ }
+
+ void Run(Core::System& system, Service* service, std::string_view name) {
+ system.RegisterHostThread();
+
+ const std::string thread_name = fmt::format("yuzu:{}", name);
+ MicroProfileOnThreadCreate(thread_name.c_str());
+ Common::SetCurrentThreadName(thread_name.c_str());
+
+ bool keep_running = true;
+ while (keep_running) {
+ work_event.Wait();
+
+ const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
+ if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
+ keep_running = false;
+ } else {
+ w.Execute(service);
+ }
+ };
+ std::visit(visit_fn, work);
+
+ kernel_event->Signal();
+ }
+ }
+
+ std::thread thread;
+ WorkVariant work;
+ Common::Event work_event;
+ std::shared_ptr<Kernel::WritableEvent> kernel_event;
+ std::atomic_bool is_available{true};
+};
+
+template <class Service, class... Types>
+class BlockingWorkerPool {
+ using Worker = BlockingWorker<Service, Types...>;
+
+public:
+ explicit BlockingWorkerPool(Core::System& system_, Service* service_)
+ : system{system_}, service{service_} {}
+
+ /// Returns a captured worker thread, creating new ones if necessary
+ Worker* CaptureWorker() {
+ for (auto& worker : workers) {
+ if (worker->TryCapture()) {
+ return worker.get();
+ }
+ }
+ auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size()));
+ [[maybe_unused]] const bool success = new_worker->TryCapture();
+ ASSERT(success);
+
+ return workers.emplace_back(std::move(new_worker)).get();
+ }
+
+private:
+ Core::System& system;
+ Service* const service;
+
+ std::vector<std::unique_ptr<Worker>> workers;
+};
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index f67fab2f9..a74be9370 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -2,18 +2,138 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/thread.h"
#include "core/hle/service/sockets/bsd.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+#include "core/network/sockets.h"
namespace Service::Sockets {
+namespace {
+
+bool IsConnectionBased(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return true;
+ case Type::DGRAM:
+ return false;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ return false;
+ }
+}
+
+} // Anonymous namespace
+
+void BSD::PollWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout);
+}
+
+void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::AcceptWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer);
+}
+
+void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::ConnectWork::Execute(BSD* bsd) {
+ bsd_errno = bsd->ConnectImpl(fd, addr);
+}
+
+void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message);
+}
+
+void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvFromWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr);
+}
+
+void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message, 0);
+ if (!addr.empty()) {
+ ctx.WriteBuffer(addr, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(addr.size()));
+}
+
+void BSD::SendWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message);
+}
+
+void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::SendToWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr);
+}
+
+void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(0); // bsd errno
}
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
@@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
void BSD::Socket(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
+ const u32 domain = rp.Pop<u32>();
+ const u32 type = rp.Pop<u32>();
+ const u32 protocol = rp.Pop<u32>();
- u32 domain = rp.Pop<u32>();
- u32 type = rp.Pop<u32>();
- u32 protocol = rp.Pop<u32>();
-
- LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol);
+ LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
- u32 fd = next_fd++;
+ const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
+ static_cast<Protocol>(protocol));
IPC::ResponseBuilder rb{ctx, 4};
-
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(fd);
+ rb.PushEnum(bsd_errno);
}
void BSD::Select(Kernel::HLERequestContext& ctx) {
@@ -52,67 +171,664 @@ void BSD::Select(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
+void BSD::Poll(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 nfds = rp.Pop<s32>();
+ const s32 timeout = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
+
+ ExecuteWork(ctx, "BSD:Poll", timeout != 0,
+ PollWork{
+ .nfds = nfds,
+ .timeout = timeout,
+ .read_buffer = ctx.ReadBuffer(),
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::Accept(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd),
+ AcceptWork{
+ .fd = fd,
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
void BSD::Bind(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer()));
}
void BSD::Connect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd),
+ ConnectWork{
+ .fd = fd,
+ .addr = ctx.ReadBuffer(),
+ });
+}
+
+void BSD::GetPeerName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::GetSockName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetSockNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
}
void BSD::Listen(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 backlog = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog);
+
+ BuildErrnoResponse(ctx, ListenImpl(fd, backlog));
+}
+
+void BSD::Fcntl(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 cmd = rp.Pop<s32>();
+ const s32 arg = rp.Pop<s32>();
+ LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
+
+ const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
}
void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
- IPC::ResponseBuilder rb{ctx, 4};
+ const s32 fd = rp.Pop<s32>();
+ const u32 level = rp.Pop<u32>();
+ const OptName optname = static_cast<OptName>(rp.Pop<u32>());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ const std::vector<u8> buffer = ctx.ReadBuffer();
+ const u8* optval = buffer.empty() ? nullptr : buffer.data();
+ size_t optlen = buffer.size();
+
+ std::array<u64, 2> values;
+ if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) {
+ std::memcpy(values.data(), buffer.data(), sizeof(values));
+ optlen = sizeof(values);
+ optval = reinterpret_cast<const u8*>(values.data());
+ }
+
+ LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
+ static_cast<u32>(optname), optlen);
+
+ BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval));
+}
+
+void BSD::Shutdown(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const s32 how = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={} how={}", fd, how);
+
+ BuildErrnoResponse(ctx, ShutdownImpl(fd, how));
+}
+
+void BSD::Recv(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
+
+ ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd),
+ RecvWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
+ ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
+
+ ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd),
+ RecvFromWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
+ .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
+ });
+}
+
+void BSD::Send(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd),
+ SendWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
+ ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
+
+ ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd),
+ SendToWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(0),
+ .addr = ctx.ReadBuffer(1),
+ });
+}
- IPC::ResponseBuilder rb{ctx, 4};
+void BSD::Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd),
+ SendWork{
+ .fd = fd,
+ .flags = 0,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::Close(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ BuildErrnoResponse(ctx, CloseImpl(fd));
+}
+
+template <typename Work>
+void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
+ bool is_blocking, Work work) {
+ if (!is_blocking) {
+ work.Execute(this);
+ work.Response(ctx);
+ return;
+ }
+
+ // Signal a dummy response to make IPC validation happy
+ // This will be overwritten by the SleepClientThread callback
+ work.Response(ctx);
+
+ auto worker = worker_pool.CaptureWorker();
+
+ ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(),
+ worker->Callback<Work>(), worker->KernelEvent());
+
+ worker->SendWork(std::move(work));
+}
+
+std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
+ if (type == Type::SEQPACKET) {
+ UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management");
+ } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) {
+ UNIMPLEMENTED_MSG("SOCK_RAW errno management");
+ }
+
+ [[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0;
+ UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
+ type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000);
+
+ const s32 fd = FindFreeFileDescriptorHandle();
+ if (fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ FileDescriptor& descriptor = file_descriptors[fd].emplace();
+ // ENONMEM might be thrown here
+
+ LOG_INFO(Service, "New socket fd={}", fd);
+
+ descriptor.socket = std::make_unique<Network::Socket>();
+ descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
+ descriptor.is_connection_based = IsConnectionBased(type);
+
+ return {fd, Errno::SUCCESS};
+}
+std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout) {
+ if (write_buffer.size() < nfds * sizeof(PollFD)) {
+ return {-1, Errno::INVAL};
+ }
+
+ if (nfds == 0) {
+ // When no entries are provided, -1 is returned with errno zero
+ return {-1, Errno::SUCCESS};
+ }
+
+ const size_t length = std::min(read_buffer.size(), write_buffer.size());
+ std::vector<PollFD> fds(nfds);
+ std::memcpy(fds.data(), read_buffer.data(), length);
+
+ if (timeout >= 0) {
+ const s64 seconds = timeout / 1000;
+ const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000);
+
+ if (seconds < 0) {
+ return {-1, Errno::INVAL};
+ }
+ if (nanoseconds > 999'999'999) {
+ return {-1, Errno::INVAL};
+ }
+ } else if (timeout != -1) {
+ return {-1, Errno::INVAL};
+ }
+
+ for (PollFD& pollfd : fds) {
+ ASSERT(pollfd.revents == 0);
+
+ if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
+ LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
+ pollfd.revents = 0;
+ return {0, Errno::SUCCESS};
+ }
+
+ const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
+ if (!descriptor) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
+ pollfd.revents = POLL_NVAL;
+ return {0, Errno::SUCCESS};
+ }
+ }
+
+ std::vector<Network::PollFD> host_pollfds(fds.size());
+ std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
+ Network::PollFD result;
+ result.socket = file_descriptors[pollfd.fd]->socket.get();
+ result.events = TranslatePollEventsToHost(pollfd.events);
+ result.revents = 0;
+ return result;
+ });
+
+ const auto result = Network::Poll(host_pollfds, timeout);
+
+ const size_t num = host_pollfds.size();
+ for (size_t i = 0; i < num; ++i) {
+ fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents);
+ }
+ std::memcpy(write_buffer.data(), fds.data(), length);
+
+ return Translate(result);
+}
+
+std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ const s32 new_fd = FindFreeFileDescriptorHandle();
+ if (new_fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+ auto [result, bsd_errno] = descriptor.socket->Accept();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return {-1, Translate(bsd_errno)};
+ }
+
+ FileDescriptor& new_descriptor = file_descriptors[new_fd].emplace();
+ new_descriptor.socket = std::move(result.socket);
+ new_descriptor.is_connection_based = descriptor.is_connection_based;
+
+ ASSERT(write_buffer.size() == sizeof(SockAddrIn));
+ const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
+ std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in));
+
+ return {new_fd, Errno::SUCCESS};
+}
+
+Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
+}
+
+Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
+}
+
+Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::ListenImpl(s32 fd, s32 backlog) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ return Translate(file_descriptors[fd]->socket->Listen(backlog));
+}
+
+std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ switch (cmd) {
+ case FcntlCmd::GETFL:
+ ASSERT(arg == 0);
+ return {descriptor.flags, Errno::SUCCESS};
+ case FcntlCmd::SETFL: {
+ const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
+ const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
+ if (bsd_errno != Errno::SUCCESS) {
+ return {-1, bsd_errno};
+ }
+ descriptor.flags = arg;
+ return {0, Errno::SUCCESS};
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented cmd={}", static_cast<int>(cmd));
+ return {-1, Errno::SUCCESS};
+ }
+}
+
+Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
+ UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET
+
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ Network::Socket* const socket = file_descriptors[fd]->socket.get();
+
+ if (optname == OptName::LINGER) {
+ ASSERT(optlen == sizeof(Linger));
+ Linger linger;
+ std::memcpy(&linger, optval, sizeof(linger));
+ ASSERT(linger.onoff == 0 || linger.onoff == 1);
+
+ return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
+ }
+
+ ASSERT(optlen == sizeof(u32));
+ u32 value;
+ std::memcpy(&value, optval, sizeof(value));
+
+ switch (optname) {
+ case OptName::REUSEADDR:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetReuseAddr(value != 0));
+ case OptName::BROADCAST:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetBroadcast(value != 0));
+ case OptName::SNDBUF:
+ return Translate(socket->SetSndBuf(value));
+ case OptName::RCVBUF:
+ return Translate(socket->SetRcvBuf(value));
+ case OptName::SNDTIMEO:
+ return Translate(socket->SetSndTimeo(value));
+ case OptName::RCVTIMEO:
+ return Translate(socket->SetRcvTimeo(value));
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented optname={}", static_cast<int>(optname));
+ return Errno::SUCCESS;
+ }
+}
+
+Errno BSD::ShutdownImpl(s32 fd, s32 how) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how));
+ return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
+}
+
+std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Recv(flags, message));
+}
+
+std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+ std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ Network::SockAddrIn addr_in{};
+ Network::SockAddrIn* p_addr_in = nullptr;
+ if (descriptor.is_connection_based) {
+ // Connection based file descriptors (e.g. TCP) zero addr
+ addr.clear();
+ } else {
+ p_addr_in = &addr_in;
+ }
+
+ // Apply flags
+ if ((flags & FLAG_MSG_DONTWAIT) != 0) {
+ flags &= ~FLAG_MSG_DONTWAIT;
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(true);
+ }
+ }
+
+ const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in));
+
+ // Restore original state
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(false);
+ }
+
+ if (p_addr_in) {
+ if (ret < 0) {
+ addr.clear();
+ } else {
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ const SockAddrIn result = Translate(addr_in);
+ std::memcpy(addr.data(), &result, sizeof(result));
+ }
+ }
+
+ return {ret, bsd_errno};
+}
+
+std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Send(message, flags));
+}
+
+std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ Network::SockAddrIn addr_in;
+ Network::SockAddrIn* p_addr_in = nullptr;
+ if (!addr.empty()) {
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ SockAddrIn guest_addr_in;
+ std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
+ addr_in = Translate(guest_addr_in);
+ p_addr_in = &addr_in;
+ }
+
+ return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
+}
+
+Errno BSD::CloseImpl(s32 fd) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close());
+ if (bsd_errno != Errno::SUCCESS) {
+ return bsd_errno;
+ }
+
+ LOG_INFO(Service, "Close socket fd={}", fd);
+
+ file_descriptors[fd].reset();
+ return bsd_errno;
+}
+
+s32 BSD::FindFreeFileDescriptorHandle() noexcept {
+ for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) {
+ if (!file_descriptors[fd]) {
+ return fd;
+ }
+ }
+ return -1;
+}
+
+bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
+ if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
+ LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
+ return false;
+ }
+ if (!file_descriptors[fd]) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd);
+ return false;
+ }
+ return true;
+}
+
+bool BSD::IsBlockingSocket(s32 fd) const noexcept {
+ // Inform invalid sockets as non-blocking
+ // This way we avoid using a worker thread as it will fail without blocking host
+ if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
+ return false;
+ }
+ if (!file_descriptors[fd]) {
+ return false;
+ }
+ return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0;
+}
+
+void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
}
-BSD::BSD(const char* name) : ServiceFramework(name) {
+BSD::BSD(Core::System& system, const char* name)
+ : ServiceFramework(name), worker_pool{system, this} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
@@ -121,25 +837,25 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{3, nullptr, "SocketExempt"},
{4, nullptr, "Open"},
{5, &BSD::Select, "Select"},
- {6, nullptr, "Poll"},
+ {6, &BSD::Poll, "Poll"},
{7, nullptr, "Sysctl"},
- {8, nullptr, "Recv"},
- {9, nullptr, "RecvFrom"},
- {10, nullptr, "Send"},
+ {8, &BSD::Recv, "Recv"},
+ {9, &BSD::RecvFrom, "RecvFrom"},
+ {10, &BSD::Send, "Send"},
{11, &BSD::SendTo, "SendTo"},
- {12, nullptr, "Accept"},
+ {12, &BSD::Accept, "Accept"},
{13, &BSD::Bind, "Bind"},
{14, &BSD::Connect, "Connect"},
- {15, nullptr, "GetPeerName"},
- {16, nullptr, "GetSockName"},
+ {15, &BSD::GetPeerName, "GetPeerName"},
+ {16, &BSD::GetSockName, "GetSockName"},
{17, nullptr, "GetSockOpt"},
{18, &BSD::Listen, "Listen"},
{19, nullptr, "Ioctl"},
- {20, nullptr, "Fcntl"},
+ {20, &BSD::Fcntl, "Fcntl"},
{21, &BSD::SetSockOpt, "SetSockOpt"},
- {22, nullptr, "Shutdown"},
+ {22, &BSD::Shutdown, "Shutdown"},
{23, nullptr, "ShutdownAllSockets"},
- {24, nullptr, "Write"},
+ {24, &BSD::Write, "Write"},
{25, nullptr, "Read"},
{26, &BSD::Close, "Close"},
{27, nullptr, "DuplicateSocket"},
@@ -148,6 +864,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{30, nullptr, "SendMMsg"},
{31, nullptr, "EventFd"},
{32, nullptr, "RegisterResourceStatisticsName"},
+ {33, nullptr, "Initialize2"},
};
// clang-format on
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 3098e3baf..357531951 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -4,30 +4,174 @@
#pragma once
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "common/common_types.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/sockets/blocking_worker.h"
+#include "core/hle/service/sockets/sockets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Network {
+class Socket;
+}
namespace Service::Sockets {
class BSD final : public ServiceFramework<BSD> {
public:
- explicit BSD(const char* name);
+ explicit BSD(Core::System& system, const char* name);
~BSD() override;
private:
+ /// Maximum number of file descriptors
+ static constexpr size_t MAX_FD = 128;
+
+ struct FileDescriptor {
+ std::unique_ptr<Network::Socket> socket;
+ s32 flags = 0;
+ bool is_connection_based = false;
+ };
+
+ struct PollWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 nfds;
+ s32 timeout;
+ std::vector<u8> read_buffer;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct AcceptWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct ConnectWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> addr;
+ Errno bsd_errno{};
+ };
+
+ struct RecvWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct RecvFromWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendToWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
void RegisterClient(Kernel::HLERequestContext& ctx);
void StartMonitoring(Kernel::HLERequestContext& ctx);
void Socket(Kernel::HLERequestContext& ctx);
void Select(Kernel::HLERequestContext& ctx);
+ void Poll(Kernel::HLERequestContext& ctx);
+ void Accept(Kernel::HLERequestContext& ctx);
void Bind(Kernel::HLERequestContext& ctx);
void Connect(Kernel::HLERequestContext& ctx);
+ void GetPeerName(Kernel::HLERequestContext& ctx);
+ void GetSockName(Kernel::HLERequestContext& ctx);
void Listen(Kernel::HLERequestContext& ctx);
+ void Fcntl(Kernel::HLERequestContext& ctx);
void SetSockOpt(Kernel::HLERequestContext& ctx);
+ void Shutdown(Kernel::HLERequestContext& ctx);
+ void Recv(Kernel::HLERequestContext& ctx);
+ void RecvFrom(Kernel::HLERequestContext& ctx);
+ void Send(Kernel::HLERequestContext& ctx);
void SendTo(Kernel::HLERequestContext& ctx);
+ void Write(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
- /// Id to use for the next open file descriptor.
- u32 next_fd = 1;
+ template <typename Work>
+ void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
+ bool is_blocking, Work work);
+
+ std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
+ std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout);
+ std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno BindImpl(s32 fd, const std::vector<u8>& addr);
+ Errno ConnectImpl(s32 fd, const std::vector<u8>& addr);
+ Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno ListenImpl(s32 fd, s32 backlog);
+ std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
+ Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval);
+ Errno ShutdownImpl(s32 fd, s32 how);
+ std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
+ std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+ std::vector<u8>& addr);
+ std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message);
+ std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr);
+ Errno CloseImpl(s32 fd);
+
+ s32 FindFreeFileDescriptorHandle() noexcept;
+ bool IsFileDescriptorValid(s32 fd) const noexcept;
+ bool IsBlockingSocket(s32 fd) const noexcept;
+
+ void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
+
+ std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
+
+ BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
+ SendToWork>
+ worker_pool;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index dc70fd6fe..40d781124 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -14,6 +14,7 @@ NSD::NSD(const char* name) : ServiceFramework(name) {
{12, nullptr, "GetDeviceId"},
{13, nullptr, "DeleteSettings"},
{14, nullptr, "ImportSettings"},
+ {15, nullptr, "SetChangeEnvironmentIdentifierDisabled"},
{20, nullptr, "Resolve"},
{21, nullptr, "ResolveEx"},
{30, nullptr, "GetNasServiceSetting"},
@@ -28,6 +29,11 @@ NSD::NSD(const char* name) : ServiceFramework(name) {
{60, nullptr, "ReadSaveDataFromFsForTest"},
{61, nullptr, "WriteSaveDataToFsForTest"},
{62, nullptr, "DeleteSaveDataOfFsForTest"},
+ {63, nullptr, "IsChangeEnvironmentIdentifierDisabled"},
+ {64, nullptr, "SetWithoutDomainExchangeFqdns"},
+ {100, nullptr, "GetApplicationServerEnvironmentType"},
+ {101, nullptr, "SetApplicationServerEnvironmentType"},
+ {102, nullptr, "DeleteApplicationServerEnvironmentType"},
};
// clang-format on
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 852e71e4b..e3017451f 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -7,7 +7,7 @@
namespace Service::Sockets {
-void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
+void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
struct Parameters {
u8 use_nsd_resolve;
u32 unknown;
@@ -29,15 +29,20 @@ SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
static const FunctionInfo functions[] = {
{0, nullptr, "SetDnsAddressesPrivate"},
{1, nullptr, "GetDnsAddressPrivate"},
- {2, nullptr, "GetHostByName"},
- {3, nullptr, "GetHostByAddr"},
- {4, nullptr, "GetHostStringError"},
- {5, nullptr, "GetGaiStringError"},
- {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
- {7, nullptr, "GetNameInfo"},
- {8, nullptr, "RequestCancelHandle"},
- {9, nullptr, "CancelSocketCall"},
- {11, nullptr, "ClearDnsIpServerAddressArray"},
+ {2, nullptr, "GetHostByNameRequest"},
+ {3, nullptr, "GetHostByAddrRequest"},
+ {4, nullptr, "GetHostStringErrorRequest"},
+ {5, nullptr, "GetGaiStringErrorRequest"},
+ {6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
+ {7, nullptr, "GetNameInfoRequest"},
+ {8, nullptr, "RequestCancelHandleRequest"},
+ {9, nullptr, "CancelRequest"},
+ {10, nullptr, "GetHostByNameRequestWithOptions"},
+ {11, nullptr, "GetHostByAddrRequestWithOptions"},
+ {12, nullptr, "GetAddrInfoRequestWithOptions"},
+ {13, nullptr, "GetNameInfoRequestWithOptions"},
+ {14, nullptr, "ResolverSetOptionRequest"},
+ {15, nullptr, "ResolverGetOptionRequest"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h
index eda432903..acd3647bb 100644
--- a/src/core/hle/service/sockets/sfdnsres.h
+++ b/src/core/hle/service/sockets/sfdnsres.h
@@ -15,7 +15,7 @@ public:
~SFDNSRES() override;
private:
- void GetAddrInfo(Kernel::HLERequestContext& ctx);
+ void GetAddrInfoRequest(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp
index 08d2d306a..1d27f7906 100644
--- a/src/core/hle/service/sockets/sockets.cpp
+++ b/src/core/hle/service/sockets/sockets.cpp
@@ -10,9 +10,9 @@
namespace Service::Sockets {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
- std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager);
+ std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager);
std::make_shared<BSDCFG>()->InstallAsService(service_manager);
std::make_shared<ETHC_C>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index ca8a6a7e0..89a410076 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -4,11 +4,94 @@
#pragma once
+#include "common/common_types.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
+enum class Errno : u32 {
+ SUCCESS = 0,
+ BADF = 9,
+ AGAIN = 11,
+ INVAL = 22,
+ MFILE = 24,
+ NOTCONN = 107,
+};
+
+enum class Domain : u32 {
+ INET = 2,
+};
+
+enum class Type : u32 {
+ STREAM = 1,
+ DGRAM = 2,
+ RAW = 3,
+ SEQPACKET = 5,
+};
+
+enum class Protocol : u32 {
+ UNSPECIFIED = 0,
+ ICMP = 1,
+ TCP = 6,
+ UDP = 17,
+};
+
+enum class OptName : u32 {
+ REUSEADDR = 0x4,
+ BROADCAST = 0x20,
+ LINGER = 0x80,
+ SNDBUF = 0x1001,
+ RCVBUF = 0x1002,
+ SNDTIMEO = 0x1005,
+ RCVTIMEO = 0x1006,
+};
+
+enum class ShutdownHow : s32 {
+ RD = 0,
+ WR = 1,
+ RDWR = 2,
+};
+
+enum class FcntlCmd : s32 {
+ GETFL = 3,
+ SETFL = 4,
+};
+
+struct SockAddrIn {
+ u8 len;
+ u8 family;
+ u16 portno;
+ std::array<u8, 4> ip;
+ std::array<u8, 8> zeroes;
+};
+
+struct PollFD {
+ s32 fd;
+ u16 events;
+ u16 revents;
+};
+
+struct Linger {
+ u32 onoff;
+ u32 linger;
+};
+
+constexpr u16 POLL_IN = 0x01;
+constexpr u16 POLL_PRI = 0x02;
+constexpr u16 POLL_OUT = 0x04;
+constexpr u16 POLL_ERR = 0x08;
+constexpr u16 POLL_HUP = 0x10;
+constexpr u16 POLL_NVAL = 0x20;
+
+constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
+
+constexpr u32 FLAG_O_NONBLOCK = 0x800;
+
/// Registers all Sockets services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
new file mode 100644
index 000000000..2e626fd86
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -0,0 +1,165 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <utility>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+Errno Translate(Network::Errno value) {
+ switch (value) {
+ case Network::Errno::SUCCESS:
+ return Errno::SUCCESS;
+ case Network::Errno::BADF:
+ return Errno::BADF;
+ case Network::Errno::AGAIN:
+ return Errno::AGAIN;
+ case Network::Errno::INVAL:
+ return Errno::INVAL;
+ case Network::Errno::MFILE:
+ return Errno::MFILE;
+ case Network::Errno::NOTCONN:
+ return Errno::NOTCONN;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented errno={}", static_cast<int>(value));
+ return Errno::SUCCESS;
+ }
+}
+
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
+ return {value.first, Translate(value.second)};
+}
+
+Network::Domain Translate(Domain domain) {
+ switch (domain) {
+ case Domain::INET:
+ return Network::Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ return {};
+ }
+}
+
+Domain Translate(Network::Domain domain) {
+ switch (domain) {
+ case Network::Domain::INET:
+ return Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ return {};
+ }
+}
+
+Network::Type Translate(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return Network::Type::STREAM;
+ case Type::DGRAM:
+ return Network::Type::DGRAM;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ }
+}
+
+Network::Protocol Translate(Type type, Protocol protocol) {
+ switch (protocol) {
+ case Protocol::UNSPECIFIED:
+ LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type");
+ switch (type) {
+ case Type::DGRAM:
+ return Network::Protocol::UDP;
+ case Type::STREAM:
+ return Network::Protocol::TCP;
+ default:
+ return Network::Protocol::TCP;
+ }
+ case Protocol::TCP:
+ return Network::Protocol::TCP;
+ case Protocol::UDP:
+ return Network::Protocol::UDP;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol));
+ return Network::Protocol::TCP;
+ }
+}
+
+u16 TranslatePollEventsToHost(u32 flags) {
+ u32 result = 0;
+ const auto translate = [&result, &flags](u32 from, u32 to) {
+ if ((flags & from) != 0) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+ translate(POLL_IN, Network::POLL_IN);
+ translate(POLL_PRI, Network::POLL_PRI);
+ translate(POLL_OUT, Network::POLL_OUT);
+ translate(POLL_ERR, Network::POLL_ERR);
+ translate(POLL_HUP, Network::POLL_HUP);
+ translate(POLL_NVAL, Network::POLL_NVAL);
+
+ UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
+ return static_cast<u16>(result);
+}
+
+u16 TranslatePollEventsToGuest(u32 flags) {
+ u32 result = 0;
+ const auto translate = [&result, &flags](u32 from, u32 to) {
+ if ((flags & from) != 0) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+
+ translate(Network::POLL_IN, POLL_IN);
+ translate(Network::POLL_PRI, POLL_PRI);
+ translate(Network::POLL_OUT, POLL_OUT);
+ translate(Network::POLL_ERR, POLL_ERR);
+ translate(Network::POLL_HUP, POLL_HUP);
+ translate(Network::POLL_NVAL, POLL_NVAL);
+
+ UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
+ return static_cast<u16>(result);
+}
+
+Network::SockAddrIn Translate(SockAddrIn value) {
+ ASSERT(value.len == 0 || value.len == sizeof(value));
+
+ return {
+ .family = Translate(static_cast<Domain>(value.family)),
+ .ip = value.ip,
+ .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
+ };
+}
+
+SockAddrIn Translate(Network::SockAddrIn value) {
+ return {
+ .len = sizeof(SockAddrIn),
+ .family = static_cast<u8>(Translate(value.family)),
+ .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
+ .ip = value.ip,
+ .zeroes = {},
+ };
+}
+
+Network::ShutdownHow Translate(ShutdownHow how) {
+ switch (how) {
+ case ShutdownHow::RD:
+ return Network::ShutdownHow::RD;
+ case ShutdownHow::WR:
+ return Network::ShutdownHow::WR;
+ case ShutdownHow::RDWR:
+ return Network::ShutdownHow::RDWR;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented how={}", static_cast<int>(how));
+ return {};
+ }
+}
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h
new file mode 100644
index 000000000..e498913d4
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+/// Translate abstract errno to guest errno
+Errno Translate(Network::Errno value);
+
+/// Translate abstract return value errno pair to guest return value errno pair
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
+
+/// Translate guest domain to abstract domain
+Network::Domain Translate(Domain domain);
+
+/// Translate abstract domain to guest domain
+Domain Translate(Network::Domain domain);
+
+/// Translate guest type to abstract type
+Network::Type Translate(Type type);
+
+/// Translate guest protocol to abstract protocol
+Network::Protocol Translate(Type type, Protocol protocol);
+
+/// Translate abstract poll event flags to guest poll event flags
+u16 TranslatePollEventsToHost(u32 flags);
+
+/// Translate guest poll event flags to abstract poll event flags
+u16 TranslatePollEventsToGuest(u32 flags);
+
+/// Translate guest socket address structure to abstract socket address structure
+Network::SockAddrIn Translate(SockAddrIn value);
+
+/// Translate abstract socket address structure to guest socket address structure
+SockAddrIn Translate(Network::SockAddrIn value);
+
+/// Translate guest shutdown mode to abstract shutdown mode
+Network::ShutdownHow Translate(ShutdownHow how);
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
index e724d4ab8..865ed3b91 100644
--- a/src/core/hle/service/spl/module.cpp
+++ b/src/core/hle/service/spl/module.cpp
@@ -19,7 +19,7 @@ namespace Service::SPL {
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)),
- rng(Settings::values.rng_seed.value_or(std::time(nullptr))) {}
+ rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {}
Module::Interface::~Interface() = default;
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
index 70cb41905..773551464 100644
--- a/src/core/hle/service/spl/spl.cpp
+++ b/src/core/hle/service/spl/spl.cpp
@@ -9,35 +9,36 @@ namespace Service::SPL {
SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") {
static const FunctionInfo functions[] = {
{0, nullptr, "GetConfig"},
- {1, nullptr, "UserExpMod"},
+ {1, nullptr, "ModularExponentiate"},
{2, nullptr, "GenerateAesKek"},
{3, nullptr, "LoadAesKey"},
{4, nullptr, "GenerateAesKey"},
{5, nullptr, "SetConfig"},
{7, &SPL::GetRandomBytes, "GetRandomBytes"},
- {9, nullptr, "LoadSecureExpModKey"},
- {10, nullptr, "SecureExpMod"},
+ {9, nullptr, "ImportLotusKey"},
+ {10, nullptr, "DecryptLotusMessage"},
{11, nullptr, "IsDevelopment"},
{12, nullptr, "GenerateSpecificAesKey"},
- {13, nullptr, "DecryptPrivk"},
+ {13, nullptr, "DecryptDeviceUniqueData"},
{14, nullptr, "DecryptAesKey"},
- {15, nullptr, "DecryptAesCtr"},
+ {15, nullptr, "CryptAesCtr"},
{16, nullptr, "ComputeCmac"},
- {17, nullptr, "LoadRsaOaepKey"},
- {18, nullptr, "UnwrapRsaOaepWrappedTitleKey"},
+ {17, nullptr, "ImportEsKey"},
+ {18, nullptr, "UnwrapTitleKey"},
{19, nullptr, "LoadTitleKey"},
- {20, nullptr, "UnwrapAesWrappedTitleKey"},
- {21, nullptr, "LockAesEngine"},
- {22, nullptr, "UnlockAesEngine"},
- {23, nullptr, "GetSplWaitEvent"},
- {24, nullptr, "SetSharedData"},
- {25, nullptr, "GetSharedData"},
- {26, nullptr, "ImportSslRsaKey"},
- {27, nullptr, "SecureExpModWithSslKey"},
- {28, nullptr, "ImportEsRsaKey"},
- {29, nullptr, "SecureExpModWithEsKey"},
- {30, nullptr, "EncryptManuRsaKeyForImport"},
- {31, nullptr, "GetPackage2Hash"},
+ {20, nullptr, "PrepareEsCommonKey"},
+ {21, nullptr, "AllocateAesKeyslot"},
+ {22, nullptr, "DeallocateAesKeySlot"},
+ {23, nullptr, "GetAesKeyslotAvailableEvent"},
+ {24, nullptr, "SetBootReason"},
+ {25, nullptr, "GetBootReason"},
+ {26, nullptr, "DecryptAndStoreSslClientCertKey"},
+ {27, nullptr, "ModularExponentiateWithSslClientCertKey"},
+ {28, nullptr, "DecryptAndStoreDrmDeviceCertKey"},
+ {29, nullptr, "ModularExponentiateWithDrmDeviceCertKey"},
+ {30, nullptr, "ReencryptDeviceUniqueData "},
+ {31, nullptr, "PrepareEsArchiveKey"}, // This is also GetPackage2Hash?
+ {32, nullptr, "LoadPreparedAesKey"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index f509653a3..ba8fd6152 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -29,7 +29,7 @@ Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* nam
{300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"},
{400, &Time::GetClockSnapshot, "GetClockSnapshot"},
{401, &Time::GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"},
- {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"},
+ {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"},
{501, &Time::CalculateSpanBetween, "CalculateSpanBetween"},
};
// clang-format on
diff --git a/src/core/hle/service/time/standard_network_system_clock_core.h b/src/core/hle/service/time/standard_network_system_clock_core.h
index 3f505c37c..c993bdf79 100644
--- a/src/core/hle/service/time/standard_network_system_clock_core.h
+++ b/src/core/hle/service/time/standard_network_system_clock_core.h
@@ -23,7 +23,7 @@ public:
standard_network_clock_sufficient_accuracy = value;
}
- bool IsStandardNetworkSystemClockAccuracySufficient(Core::System& system) {
+ bool IsStandardNetworkSystemClockAccuracySufficient(Core::System& system) const {
SystemClockContext context{};
if (GetClockContext(system, context) != RESULT_SUCCESS) {
return {};
diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp
index 1575f0b49..59a272f4a 100644
--- a/src/core/hle/service/time/standard_steady_clock_core.cpp
+++ b/src/core/hle/service/time/standard_steady_clock_core.cpp
@@ -11,9 +11,8 @@
namespace Service::Time::Clock {
TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) {
- const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
- Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
- Core::Hardware::CNTFREQ)};
+ const TimeSpanType ticks_time_span{
+ TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)};
TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds};
if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) {
diff --git a/src/core/hle/service/time/steady_clock_core.h b/src/core/hle/service/time/steady_clock_core.h
index 84af3d105..d80a2385f 100644
--- a/src/core/hle/service/time/steady_clock_core.h
+++ b/src/core/hle/service/time/steady_clock_core.h
@@ -16,6 +16,7 @@ namespace Service::Time::Clock {
class SteadyClockCore {
public:
SteadyClockCore() = default;
+ virtual ~SteadyClockCore() = default;
const Common::UUID& GetClockSourceId() const {
return clock_source_id;
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h
index 6260de6c3..2b0fa7e75 100644
--- a/src/core/hle/service/time/system_clock_context_update_callback.h
+++ b/src/core/hle/service/time/system_clock_context_update_callback.h
@@ -20,7 +20,7 @@ namespace Service::Time::Clock {
class SystemClockContextUpdateCallback {
public:
SystemClockContextUpdateCallback();
- ~SystemClockContextUpdateCallback();
+ virtual ~SystemClockContextUpdateCallback();
bool NeedUpdate(const SystemClockContext& value) const;
diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp
index 1a3ab8cfa..d31d4e2ca 100644
--- a/src/core/hle/service/time/system_clock_core.cpp
+++ b/src/core/hle/service/time/system_clock_core.cpp
@@ -9,7 +9,7 @@
namespace Service::Time::Clock {
SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core)
- : steady_clock_core{steady_clock_core}, is_initialized{} {
+ : steady_clock_core{steady_clock_core} {
context.steady_time_point.clock_source_id = steady_clock_core.GetClockSourceId();
}
diff --git a/src/core/hle/service/time/system_clock_core.h b/src/core/hle/service/time/system_clock_core.h
index 54407a6c5..608dd3b2e 100644
--- a/src/core/hle/service/time/system_clock_core.h
+++ b/src/core/hle/service/time/system_clock_core.h
@@ -22,7 +22,7 @@ class SystemClockContextUpdateCallback;
class SystemClockCore {
public:
explicit SystemClockCore(SteadyClockCore& steady_clock_core);
- ~SystemClockCore();
+ virtual ~SystemClockCore();
SteadyClockCore& GetSteadyClockCore() const {
return steady_clock_core;
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
index 44d5bc651..8baaa2a6a 100644
--- a/src/core/hle/service/time/tick_based_steady_clock_core.cpp
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
@@ -11,9 +11,8 @@
namespace Service::Time::Clock {
SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) {
- const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
- Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
- Core::Hardware::CNTFREQ)};
+ const TimeSpanType ticks_time_span{
+ TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)};
return {ticks_time_span.ToSeconds(), GetClockSourceId()};
}
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index ce859f18d..7d0474e0b 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -10,6 +10,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
@@ -20,8 +21,8 @@ namespace Service::Time {
class ISystemClock final : public ServiceFramework<ISystemClock> {
public:
- ISystemClock(Clock::SystemClockCore& clock_core)
- : ServiceFramework("ISystemClock"), clock_core{clock_core} {
+ explicit ISystemClock(Clock::SystemClockCore& clock_core, Core::System& system)
+ : ServiceFramework("ISystemClock"), clock_core{clock_core}, system{system} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
@@ -46,9 +47,8 @@ private:
}
s64 posix_time{};
- if (const ResultCode result{
- clock_core.GetCurrentTime(Core::System::GetInstance(), posix_time)};
- result != RESULT_SUCCESS) {
+ if (const ResultCode result{clock_core.GetCurrentTime(system, posix_time)};
+ result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -69,9 +69,8 @@ private:
}
Clock::SystemClockContext system_clock_context{};
- if (const ResultCode result{
- clock_core.GetClockContext(Core::System::GetInstance(), system_clock_context)};
- result != RESULT_SUCCESS) {
+ if (const ResultCode result{clock_core.GetClockContext(system, system_clock_context)};
+ result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -83,14 +82,22 @@ private:
}
Clock::SystemClockCore& clock_core;
+ Core::System& system;
};
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
public:
- ISteadyClock(Clock::SteadyClockCore& clock_core)
- : ServiceFramework("ISteadyClock"), clock_core{clock_core} {
+ explicit ISteadyClock(Clock::SteadyClockCore& clock_core, Core::System& system)
+ : ServiceFramework("ISteadyClock"), clock_core{clock_core}, system{system} {
static const FunctionInfo functions[] = {
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
+ {2, nullptr, "GetTestOffset"},
+ {3, nullptr, "SetTestOffset"},
+ {100, nullptr, "GetRtcValue"},
+ {101, nullptr, "IsRtcResetDetected"},
+ {102, nullptr, "GetSetupResultValue"},
+ {200, nullptr, "GetInternalOffset"},
+ {201, nullptr, "SetInternalOffset"},
};
RegisterHandlers(functions);
}
@@ -105,21 +112,21 @@ private:
return;
}
- const Clock::SteadyClockTimePoint time_point{
- clock_core.GetCurrentTimePoint(Core::System::GetInstance())};
+ const Clock::SteadyClockTimePoint time_point{clock_core.GetCurrentTimePoint(system)};
IPC::ResponseBuilder rb{ctx, (sizeof(Clock::SteadyClockTimePoint) / 4) + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(time_point);
}
Clock::SteadyClockCore& clock_core;
+ Core::System& system;
};
ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
Kernel::Thread* thread, Clock::SystemClockContext user_context,
Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) {
- auto& time_manager{module->GetTimeManager()};
+ auto& time_manager{system.GetTimeManager()};
clock_snapshot.is_automatic_correction_enabled =
time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled();
@@ -134,7 +141,7 @@ ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
}
const auto current_time_point{
- time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(Core::System::GetInstance())};
+ time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)};
if (const ResultCode result{Clock::ClockSnapshot::GetCurrentTime(
clock_snapshot.user_time, current_time_point, clock_snapshot.user_context)};
result != RESULT_SUCCESS) {
@@ -176,41 +183,44 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore());
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardUserSystemClockCore(),
+ system);
}
void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore());
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardNetworkSystemClockCore(),
+ system);
}
void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore());
+ rb.PushIpcInterface<ISteadyClock>(system.GetTimeManager().GetStandardSteadyClockCore(), system);
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager());
+ rb.PushIpcInterface<ITimeZoneService>(system.GetTimeManager().GetTimeZoneContentManager());
}
void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardLocalSystemClockCore());
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardLocalSystemClockCore(),
+ system);
}
void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient(
Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()};
+ auto& clock_core{system.GetTimeManager().GetStandardNetworkSystemClockCore()};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system));
@@ -219,7 +229,7 @@ void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient(
void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()};
+ auto& steady_clock_core{system.GetTimeManager().GetStandardSteadyClockCore()};
if (!steady_clock_core.IsInitialized()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_UNINITIALIZED_CLOCK);
@@ -228,13 +238,11 @@ void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERe
IPC::RequestParser rp{ctx};
const auto context{rp.PopRaw<Clock::SystemClockContext>()};
- const auto current_time_point{
- steady_clock_core.GetCurrentTimePoint(Core::System::GetInstance())};
+ const auto current_time_point{steady_clock_core.GetCurrentTimePoint(system)};
if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) {
- const auto ticks{Clock::TimeSpanType::FromTicks(
- Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
- Core::Hardware::CNTFREQ)};
+ const auto ticks{Clock::TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(),
+ Core::Hardware::CNTFREQ)};
const s64 base_time_point{context.offset + current_time_point.time_point -
ticks.ToSeconds()};
IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2};
@@ -254,18 +262,18 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
Clock::SystemClockContext user_context{};
if (const ResultCode result{
- module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(
- Core::System::GetInstance(), user_context)};
- result != RESULT_SUCCESS) {
+ system.GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(system,
+ user_context)};
+ result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
Clock::SystemClockContext network_context{};
if (const ResultCode result{
- module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
- Core::System::GetInstance(), network_context)};
- result != RESULT_SUCCESS) {
+ system.GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
+ system, network_context)};
+ result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -274,7 +282,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
Clock::ClockSnapshot clock_snapshot{};
if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal(
&ctx.GetThread(), user_context, network_context, type, clock_snapshot)};
- result != RESULT_SUCCESS) {
+ result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -282,7 +290,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
+ ctx.WriteBuffer(clock_snapshot);
}
void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx) {
@@ -305,7 +313,30 @@ void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLEReques
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
+ ctx.WriteBuffer(clock_snapshot);
+}
+
+void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
+ Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto snapshot_a = rp.PopRaw<Clock::ClockSnapshot>();
+ const auto snapshot_b = rp.PopRaw<Clock::ClockSnapshot>();
+
+ auto time_span_type{Clock::TimeSpanType::FromSeconds(snapshot_b.user_context.offset -
+ snapshot_a.user_context.offset)};
+
+ if ((snapshot_b.user_context.steady_time_point.clock_source_id !=
+ snapshot_a.user_context.steady_time_point.clock_source_id) ||
+ (snapshot_b.is_automatic_correction_enabled &&
+ snapshot_a.is_automatic_correction_enabled)) {
+ time_span_type.nanoseconds = 0;
+ }
+
+ IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(time_span_type.nanoseconds);
}
void Module::Interface::CalculateSpanBetween(Kernel::HLERequestContext& ctx) {
@@ -341,7 +372,7 @@ void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& c
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder());
+ rb.PushCopyObjects(SharedFrom(&system.Kernel().GetTimeSharedMem()));
}
Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
@@ -350,7 +381,7 @@ Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& syste
Module::Interface::~Interface() = default;
void InstallInterfaces(Core::System& system) {
- auto module{std::make_shared<Module>(system)};
+ auto module{std::make_shared<Module>()};
std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager());
std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager());
std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager());
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 351988468..49f4aac0a 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -16,7 +16,7 @@ namespace Service::Time {
class Module final {
public:
- Module(Core::System& system) : time_manager{system} {}
+ Module() = default;
class Interface : public ServiceFramework<Interface> {
public:
@@ -32,6 +32,7 @@ public:
void CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx);
void GetClockSnapshot(Kernel::HLERequestContext& ctx);
void GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx);
+ void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx);
void CalculateSpanBetween(Kernel::HLERequestContext& ctx);
void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
@@ -45,13 +46,6 @@ public:
std::shared_ptr<Module> module;
Core::System& system;
};
-
- TimeManager& GetTimeManager() {
- return time_manager;
- }
-
-private:
- TimeManager time_manager;
};
/// Registers all Time services with the specified service manager.
diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp
index 9d6c55865..858623e2b 100644
--- a/src/core/hle/service/time/time_manager.cpp
+++ b/src/core/hle/service/time/time_manager.cpp
@@ -5,6 +5,7 @@
#include <chrono>
#include <ctime>
+#include "common/time_zone.h"
#include "core/hle/service/time/ephemeral_network_system_clock_context_writer.h"
#include "core/hle/service/time/local_system_clock_context_writer.h"
#include "core/hle/service/time/network_system_clock_context_writer.h"
@@ -22,116 +23,281 @@ static std::chrono::seconds GetSecondsSinceEpoch() {
}
static s64 GetExternalRtcValue() {
- return GetSecondsSinceEpoch().count();
-}
-
-TimeManager::TimeManager(Core::System& system)
- : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core},
- standard_network_system_clock_core{standard_steady_clock_core},
- standard_user_system_clock_core{standard_local_system_clock_core,
- standard_network_system_clock_core, system},
- ephemeral_network_system_clock_core{tick_based_steady_clock_core},
- local_system_clock_context_writer{
- std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)},
- network_system_clock_context_writer{
- std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)},
- ephemeral_network_system_clock_context_writer{
- std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()},
- time_zone_content_manager{*this, system} {
-
- const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())};
- SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {});
- SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds());
- SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy);
- SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom());
- SetupEphemeralNetworkSystemClock();
+ return GetSecondsSinceEpoch().count() + TimeManager::GetExternalTimeZoneOffset();
}
-TimeManager::~TimeManager() = default;
+struct TimeManager::Impl final {
+ explicit Impl(Core::System& system)
+ : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core},
+ standard_network_system_clock_core{standard_steady_clock_core},
+ standard_user_system_clock_core{standard_local_system_clock_core,
+ standard_network_system_clock_core, system},
+ ephemeral_network_system_clock_core{tick_based_steady_clock_core},
+ local_system_clock_context_writer{
+ std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)},
+ network_system_clock_context_writer{
+ std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)},
+ ephemeral_network_system_clock_context_writer{
+ std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()},
+ time_zone_content_manager{system} {
-void TimeManager::SetupTimeZoneManager(std::string location_name,
- Clock::SteadyClockTimePoint time_zone_updated_time_point,
- std::size_t total_location_name_count,
- u128 time_zone_rule_version,
- FileSys::VirtualFile& vfs_file) {
- if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
- location_name, vfs_file) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
- }
-
- time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point);
- time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount(
- total_location_name_count);
- time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version);
- time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized();
-}
-
-void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
- Clock::TimeSpanType setup_value,
- Clock::TimeSpanType internal_offset,
- bool is_rtc_reset_detected) {
- standard_steady_clock_core.SetClockSourceId(clock_source_id);
- standard_steady_clock_core.SetSetupValue(setup_value);
- standard_steady_clock_core.SetInternalOffset(internal_offset);
- standard_steady_clock_core.MarkAsInitialized();
-
- const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)};
- shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point);
-}
-
-void TimeManager::SetupStandardLocalSystemClock(Core::System& system,
- Clock::SystemClockContext clock_context,
- s64 posix_time) {
- standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer);
-
- const auto current_time_point{
- standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)};
- if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) {
- standard_local_system_clock_core.SetSystemClockContext(clock_context);
- } else {
- if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) {
+ const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())};
+ SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {});
+ SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds());
+ SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy);
+ SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom());
+ SetupEphemeralNetworkSystemClock();
+ }
+
+ ~Impl() = default;
+
+ Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() {
+ return standard_steady_clock_core;
+ }
+
+ const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const {
+ return standard_steady_clock_core;
+ }
+
+ Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() {
+ return standard_local_system_clock_core;
+ }
+
+ const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const {
+ return standard_local_system_clock_core;
+ }
+
+ Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() {
+ return standard_network_system_clock_core;
+ }
+
+ const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const {
+ return standard_network_system_clock_core;
+ }
+
+ Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() {
+ return standard_user_system_clock_core;
+ }
+
+ const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const {
+ return standard_user_system_clock_core;
+ }
+
+ TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() {
+ return time_zone_content_manager;
+ }
+
+ const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const {
+ return time_zone_content_manager;
+ }
+
+ SharedMemory& GetSharedMemory() {
+ return shared_memory;
+ }
+
+ const SharedMemory& GetSharedMemory() const {
+ return shared_memory;
+ }
+
+ void SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count, u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file) {
+ if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
+ location_name, vfs_file) != RESULT_SUCCESS) {
UNREACHABLE();
return;
}
+
+ time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point);
+ time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount(
+ total_location_name_count);
+ time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(
+ time_zone_rule_version);
+ time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized();
}
- standard_local_system_clock_core.MarkAsInitialized();
-}
+ static s64 GetExternalTimeZoneOffset() {
+ // With "auto" timezone setting, we use the external system's timezone offset
+ if (Settings::GetTimeZoneString() == "auto") {
+ return Common::TimeZone::GetCurrentOffsetSeconds().count();
+ }
+ return 0;
+ }
-void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
- Clock::TimeSpanType sufficient_accuracy) {
- standard_network_system_clock_core.SetUpdateCallbackInstance(
- network_system_clock_context_writer);
+ void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
+ Clock::TimeSpanType setup_value,
+ Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected) {
+ standard_steady_clock_core.SetClockSourceId(clock_source_id);
+ standard_steady_clock_core.SetSetupValue(setup_value);
+ standard_steady_clock_core.SetInternalOffset(internal_offset);
+ standard_steady_clock_core.MarkAsInitialized();
- if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
+ const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)};
+ shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point);
}
- standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy(
- sufficient_accuracy);
- standard_network_system_clock_core.MarkAsInitialized();
-}
+ void SetupStandardLocalSystemClock(Core::System& system,
+ Clock::SystemClockContext clock_context, s64 posix_time) {
+ standard_local_system_clock_core.SetUpdateCallbackInstance(
+ local_system_clock_context_writer);
+
+ const auto current_time_point{
+ standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)};
+ if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) {
+ standard_local_system_clock_core.SetSystemClockContext(clock_context);
+ } else {
+ if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) !=
+ RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+ }
+
+ standard_local_system_clock_core.MarkAsInitialized();
+ }
+
+ void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
+ Clock::TimeSpanType sufficient_accuracy) {
+ standard_network_system_clock_core.SetUpdateCallbackInstance(
+ network_system_clock_context_writer);
+
+ if (standard_network_system_clock_core.SetSystemClockContext(clock_context) !=
+ RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy(
+ sufficient_accuracy);
+ standard_network_system_clock_core.MarkAsInitialized();
+ }
+
+ void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled,
+ Clock::SteadyClockTimePoint steady_clock_time_point) {
+ if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
+ system, is_automatic_correction_enabled) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point);
+ standard_user_system_clock_core.MarkAsInitialized();
+ shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+ }
-void TimeManager::SetupStandardUserSystemClock(
- Core::System& system, bool is_automatic_correction_enabled,
- Clock::SteadyClockTimePoint steady_clock_time_point) {
- if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
- system, is_automatic_correction_enabled) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
+ void SetupEphemeralNetworkSystemClock() {
+ ephemeral_network_system_clock_core.SetUpdateCallbackInstance(
+ ephemeral_network_system_clock_context_writer);
+ ephemeral_network_system_clock_core.MarkAsInitialized();
}
- standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point);
- standard_user_system_clock_core.MarkAsInitialized();
- shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+ void UpdateLocalSystemClockTime(Core::System& system, s64 posix_time) {
+ const auto timespan{Service::Time::Clock::TimeSpanType::FromSeconds(posix_time)};
+ if (GetStandardLocalSystemClockCore()
+ .SetCurrentTime(system, timespan.ToSeconds())
+ .IsError()) {
+ UNREACHABLE();
+ return;
+ }
+ }
+
+ SharedMemory shared_memory;
+
+ Clock::StandardSteadyClockCore standard_steady_clock_core;
+ Clock::TickBasedSteadyClockCore tick_based_steady_clock_core;
+ Clock::StandardLocalSystemClockCore standard_local_system_clock_core;
+ Clock::StandardNetworkSystemClockCore standard_network_system_clock_core;
+ Clock::StandardUserSystemClockCore standard_user_system_clock_core;
+ Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core;
+
+ std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer;
+ std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer;
+ std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter>
+ ephemeral_network_system_clock_context_writer;
+
+ TimeZone::TimeZoneContentManager time_zone_content_manager;
+};
+
+TimeManager::TimeManager(Core::System& system) : system{system} {}
+
+TimeManager::~TimeManager() = default;
+
+void TimeManager::Initialize() {
+ impl = std::make_unique<Impl>(system);
+
+ // Time zones can only be initialized after impl is valid
+ impl->time_zone_content_manager.Initialize(*this);
}
-void TimeManager::SetupEphemeralNetworkSystemClock() {
- ephemeral_network_system_clock_core.SetUpdateCallbackInstance(
- ephemeral_network_system_clock_context_writer);
- ephemeral_network_system_clock_core.MarkAsInitialized();
+Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() {
+ return impl->standard_steady_clock_core;
+}
+
+const Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() const {
+ return impl->standard_steady_clock_core;
+}
+
+Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() {
+ return impl->standard_local_system_clock_core;
+}
+
+const Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() const {
+ return impl->standard_local_system_clock_core;
+}
+
+Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore() {
+ return impl->standard_network_system_clock_core;
+}
+
+const Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore()
+ const {
+ return impl->standard_network_system_clock_core;
+}
+
+Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() {
+ return impl->standard_user_system_clock_core;
+}
+
+const Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() const {
+ return impl->standard_user_system_clock_core;
+}
+
+TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() {
+ return impl->time_zone_content_manager;
+}
+
+const TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() const {
+ return impl->time_zone_content_manager;
+}
+
+SharedMemory& TimeManager::GetSharedMemory() {
+ return impl->shared_memory;
+}
+
+const SharedMemory& TimeManager::GetSharedMemory() const {
+ return impl->shared_memory;
+}
+
+void TimeManager::UpdateLocalSystemClockTime(s64 posix_time) {
+ impl->UpdateLocalSystemClockTime(system, posix_time);
+}
+
+void TimeManager::SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count,
+ u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file) {
+ impl->SetupTimeZoneManager(location_name, time_zone_updated_time_point,
+ total_location_name_count, time_zone_rule_version, vfs_file);
+}
+
+/*static*/ s64 TimeManager::GetExternalTimeZoneOffset() {
+ // With "auto" timezone setting, we use the external system's timezone offset
+ if (Settings::GetTimeZoneString() == "auto") {
+ return Common::TimeZone::GetCurrentOffsetSeconds().count();
+ }
+ return 0;
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h
index 8e65f0d22..993c7c288 100644
--- a/src/core/hle/service/time/time_manager.h
+++ b/src/core/hle/service/time/time_manager.h
@@ -5,6 +5,7 @@
#pragma once
#include "common/common_types.h"
+#include "common/time_zone.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/time/clock_types.h"
#include "core/hle/service/time/ephemeral_network_system_clock_core.h"
@@ -32,86 +33,46 @@ public:
explicit TimeManager(Core::System& system);
~TimeManager();
- Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() {
- return standard_steady_clock_core;
- }
+ void Initialize();
- const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const {
- return standard_steady_clock_core;
- }
+ Clock::StandardSteadyClockCore& GetStandardSteadyClockCore();
- Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() {
- return standard_local_system_clock_core;
- }
+ const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const;
- const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const {
- return standard_local_system_clock_core;
- }
+ Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore();
- Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() {
- return standard_network_system_clock_core;
- }
+ const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const;
- const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const {
- return standard_network_system_clock_core;
- }
+ Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore();
- Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() {
- return standard_user_system_clock_core;
- }
+ const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const;
- const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const {
- return standard_user_system_clock_core;
- }
+ Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore();
- TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() {
- return time_zone_content_manager;
- }
+ const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const;
- const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const {
- return time_zone_content_manager;
- }
+ TimeZone::TimeZoneContentManager& GetTimeZoneContentManager();
- SharedMemory& GetSharedMemory() {
- return shared_memory;
- }
+ const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const;
- const SharedMemory& GetSharedMemory() const {
- return shared_memory;
- }
+ void UpdateLocalSystemClockTime(s64 posix_time);
+
+ SharedMemory& GetSharedMemory();
+
+ const SharedMemory& GetSharedMemory() const;
void SetupTimeZoneManager(std::string location_name,
Clock::SteadyClockTimePoint time_zone_updated_time_point,
std::size_t total_location_name_count, u128 time_zone_rule_version,
FileSys::VirtualFile& vfs_file);
+ static s64 GetExternalTimeZoneOffset();
+
private:
- void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
- Clock::TimeSpanType setup_value,
- Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected);
- void SetupStandardLocalSystemClock(Core::System& system,
- Clock::SystemClockContext clock_context, s64 posix_time);
- void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
- Clock::TimeSpanType sufficient_accuracy);
- void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled,
- Clock::SteadyClockTimePoint steady_clock_time_point);
- void SetupEphemeralNetworkSystemClock();
-
- SharedMemory shared_memory;
-
- Clock::StandardSteadyClockCore standard_steady_clock_core;
- Clock::TickBasedSteadyClockCore tick_based_steady_clock_core;
- Clock::StandardLocalSystemClockCore standard_local_system_clock_core;
- Clock::StandardNetworkSystemClockCore standard_network_system_clock_core;
- Clock::StandardUserSystemClockCore standard_user_system_clock_core;
- Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core;
-
- std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer;
- std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer;
- std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter>
- ephemeral_network_system_clock_context_writer;
-
- TimeZone::TimeZoneContentManager time_zone_content_manager;
+ Core::System& system;
+
+ struct Impl;
+ std::unique_ptr<Impl> impl;
};
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index fdaef233f..e0ae9f874 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -6,6 +6,7 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hardware_properties.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/time/clock_types.h"
#include "core/hle/service/time/steady_clock_core.h"
#include "core/hle/service/time/time_sharedmemory.h"
@@ -15,9 +16,7 @@ namespace Service::Time {
static constexpr std::size_t SHARED_MEMORY_SIZE{0x1000};
SharedMemory::SharedMemory(Core::System& system) : system(system) {
- shared_memory_holder = Kernel::SharedMemory::Create(
- system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory");
+ shared_memory_holder = SharedFrom(&system.Kernel().GetTimeSharedMem());
std::memset(shared_memory_holder->GetPointer(), 0, SHARED_MEMORY_SIZE);
}
@@ -31,8 +30,7 @@ void SharedMemory::SetupStandardSteadyClock(Core::System& system,
const Common::UUID& clock_source_id,
Clock::TimeSpanType current_time_point) {
const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks(
- Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
- Core::Hardware::CNTFREQ)};
+ system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)};
const Clock::SteadyClockContext context{
static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),
clock_source_id};
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index 78d4acd95..4177d0a41 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -5,6 +5,7 @@
#include <sstream>
#include "common/logging/log.h"
+#include "common/time_zone.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
@@ -14,6 +15,7 @@
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/time/time_manager.h"
#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/settings.h"
namespace Service::Time::TimeZone {
@@ -66,12 +68,23 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
return location_name_cache;
}
-TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system)
- : system{system}, location_name_cache{BuildLocationNameCache(system)} {
- if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile("GMT", vfs_file) == RESULT_SUCCESS) {
+TimeZoneContentManager::TimeZoneContentManager(Core::System& system)
+ : system{system}, location_name_cache{BuildLocationNameCache(system)} {}
+
+void TimeZoneContentManager::Initialize(TimeManager& time_manager) {
+ std::string location_name;
+ const auto timezone_setting = Settings::GetTimeZoneString();
+ if (timezone_setting == "auto" || timezone_setting == "default") {
+ location_name = Common::TimeZone::GetDefaultTimeZone();
+ } else {
+ location_name = timezone_setting;
+ }
+
+ if (FileSys::VirtualFile vfs_file;
+ GetTimeZoneInfoFile(location_name, vfs_file) == RESULT_SUCCESS) {
const auto time_point{
time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)};
- time_manager.SetupTimeZoneManager("GMT", time_point, location_name_cache.size(), {},
+ time_manager.SetupTimeZoneManager(location_name, time_point, location_name_cache.size(), {},
vfs_file);
} else {
time_zone_manager.MarkAsInitialized();
@@ -114,6 +127,12 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati
vfs_file = zoneinfo_dir->GetFile(location_name);
if (!vfs_file) {
+ LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.",
+ time_zone_binary_titleid, location_name);
+ vfs_file = zoneinfo_dir->GetFile(Common::TimeZone::GetDefaultTimeZone());
+ }
+
+ if (!vfs_file) {
LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"!", time_zone_binary_titleid,
location_name);
return ERROR_TIME_NOT_FOUND;
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
index 4f302c3b9..52dd1a020 100644
--- a/src/core/hle/service/time/time_zone_content_manager.h
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -21,7 +21,9 @@ namespace Service::Time::TimeZone {
class TimeZoneContentManager final {
public:
- TimeZoneContentManager(TimeManager& time_manager, Core::System& system);
+ explicit TimeZoneContentManager(Core::System& system);
+
+ void Initialize(TimeManager& time_manager);
TimeZoneManager& GetTimeZoneManager() {
return time_zone_manager;
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
index 07b553a43..bdf0439f2 100644
--- a/src/core/hle/service/time/time_zone_manager.cpp
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -309,7 +309,7 @@ static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
offset = GetTZName(name, offset);
std_len = offset;
}
- if (!std_len) {
+ if (std_len == 0) {
return {};
}
if (!GetOffset(name, offset, std_offset)) {
@@ -320,7 +320,7 @@ static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
int dest_len{};
int dest_offset{};
const char* dest_name{name + offset};
- if (rule.chars.size() < char_count) {
+ if (rule.chars.size() < std::size_t(char_count)) {
return {};
}
@@ -343,7 +343,7 @@ static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
return {};
}
char_count += dest_len + 1;
- if (rule.chars.size() < char_count) {
+ if (rule.chars.size() < std::size_t(char_count)) {
return {};
}
if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') {
@@ -414,7 +414,7 @@ static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
if (is_reversed ||
(start_time < end_time &&
(end_time - start_time < (year_seconds + (std_offset - dest_offset))))) {
- if (rule.ats.size() - 2 < time_count) {
+ if (rule.ats.size() - 2 < std::size_t(time_count)) {
break;
}
@@ -518,8 +518,8 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi
constexpr s32 time_zone_max_leaps{50};
constexpr s32 time_zone_max_chars{50};
if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps &&
- 0 < header.type_count && header.type_count < time_zone_rule.ttis.size() &&
- 0 <= header.time_count && header.time_count < time_zone_rule.ats.size() &&
+ 0 < header.type_count && header.type_count < s32(time_zone_rule.ttis.size()) &&
+ 0 <= header.time_count && header.time_count < s32(time_zone_rule.ats.size()) &&
0 <= header.char_count && header.char_count < time_zone_max_chars &&
(header.ttis_std_count == header.type_count || header.ttis_std_count == 0) &&
(header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) {
@@ -609,7 +609,7 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi
}
const u64 position{(read_offset - sizeof(TzifHeader))};
- const std::size_t bytes_read{vfs_file->GetSize() - sizeof(TzifHeader) - position};
+ const s64 bytes_read = s64(vfs_file->GetSize() - sizeof(TzifHeader) - position);
if (bytes_read < 0) {
return {};
}
@@ -621,11 +621,11 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi
std::array<char, time_zone_name_max + 1> temp_name{};
vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset);
if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' &&
- time_zone_rule.type_count + 2 <= time_zone_rule.ttis.size()) {
+ std::size_t(time_zone_rule.type_count) + 2 <= time_zone_rule.ttis.size()) {
temp_name[bytes_read - 1] = '\0';
std::array<char, time_zone_name_max> name{};
- std::memcpy(name.data(), temp_name.data() + 1, bytes_read - 1);
+ std::memcpy(name.data(), temp_name.data() + 1, std::size_t(bytes_read - 1));
TimeZoneRule temp_rule;
if (ParsePosixName(name.data(), temp_rule)) {
@@ -820,7 +820,10 @@ static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, Calend
const ResultCode result{
ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)};
calendar.time.year = static_cast<s16>(calendar_time.year);
- calendar.time.month = calendar_time.month + 1; // Internal impl. uses 0-indexed month
+
+ // Internal impl. uses 0-indexed month
+ calendar.time.month = static_cast<s8>(calendar_time.month + 1);
+
calendar.time.day = calendar_time.day;
calendar.time.hour = calendar_time.hour;
calendar.time.minute = calendar_time.minute;
@@ -872,13 +875,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
const CalendarTime& calendar_time, s64& posix_time) const {
posix_time = 0;
- CalendarTimeInternal internal_time{};
- internal_time.year = calendar_time.year;
- internal_time.month = calendar_time.month - 1; // Internal impl. uses 0-indexed month
- internal_time.day = calendar_time.day;
- internal_time.hour = calendar_time.hour;
- internal_time.minute = calendar_time.minute;
- internal_time.second = calendar_time.second;
+ CalendarTimeInternal internal_time{
+ .year = calendar_time.year,
+ // Internal impl. uses 0-indexed month
+ .month = static_cast<s8>(calendar_time.month - 1),
+ .day = calendar_time.day,
+ .hour = calendar_time.hour,
+ .minute = calendar_time.minute,
+ .second = calendar_time.second,
+ };
s32 hour{internal_time.hour};
s32 minute{internal_time.minute};
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index db57ae069..ff3a10b3e 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -142,7 +142,7 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u32>(1); // Number of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(s64));
+ ctx.WriteBuffer(posix_time);
}
void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
@@ -164,7 +164,7 @@ void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u32>(1); // Number of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(s64));
+ ctx.WriteBuffer(posix_time);
}
} // namespace Service::Time
diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp
index 58a9845fc..d033f8603 100644
--- a/src/core/hle/service/usb/usb.cpp
+++ b/src/core/hle/service/usb/usb.cpp
@@ -20,7 +20,7 @@ public:
static const FunctionInfo functions[] = {
{0, nullptr, "GetDsEndpoint"},
{1, nullptr, "GetSetupEvent"},
- {2, nullptr, "Unknown"},
+ {2, nullptr, "Unknown2"},
{3, nullptr, "EnableInterface"},
{4, nullptr, "DisableInterface"},
{5, nullptr, "CtrlInPostBufferAsync"},
@@ -55,6 +55,7 @@ public:
{9, nullptr, "SetBinaryObjectStore"},
{10, nullptr, "Enable"},
{11, nullptr, "Disable"},
+ {12, nullptr, "Unknown12"},
};
// clang-format on
@@ -69,13 +70,13 @@ public:
static const FunctionInfo functions[] = {
{0, nullptr, "Open"},
{1, nullptr, "Close"},
- {2, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
{3, nullptr, "Populate"},
{4, nullptr, "PostBufferAsync"},
{5, nullptr, "GetXferReport"},
{6, nullptr, "PostBufferMultiAsync"},
- {7, nullptr, "Unknown3"},
- {8, nullptr, "Unknown4"},
+ {7, nullptr, "Unknown7"},
+ {8, nullptr, "Unknown8"},
};
// clang-format on
@@ -88,13 +89,13 @@ public:
explicit IClientIfSession() : ServiceFramework{"IClientIfSession"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
+ {0, nullptr, "Unknown0"},
{1, nullptr, "SetInterface"},
{2, nullptr, "GetInterface"},
{3, nullptr, "GetAlternateInterface"},
{4, nullptr, "GetCurrentFrame"},
{5, nullptr, "CtrlXferAsync"},
- {6, nullptr, "Unknown2"},
+ {6, nullptr, "Unknown6"},
{7, nullptr, "GetCtrlXferReport"},
{8, nullptr, "ResetDevice"},
{9, nullptr, "OpenUsbEp"},
@@ -118,7 +119,7 @@ public:
{5, nullptr, "DestroyInterfaceAvailableEvent"},
{6, nullptr, "GetInterfaceStateChangeEvent"},
{7, nullptr, "AcquireUsbIf"},
- {8, nullptr, "Unknown1"},
+ {8, nullptr, "Unknown8"},
};
// clang-format on
@@ -179,8 +180,8 @@ public:
{4, nullptr, "GetFwRevision"},
{5, nullptr, "GetManufacturerId"},
{6, nullptr, "GetDeviceId"},
- {7, nullptr, "Unknown1"},
- {8, nullptr, "Unknown2"},
+ {7, nullptr, "Unknown7"},
+ {8, nullptr, "Unknown8"},
};
// clang-format on
@@ -215,12 +216,12 @@ public:
explicit USB_PM() : ServiceFramework{"usb:pm"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
- {4, nullptr, "Unknown5"},
- {5, nullptr, "Unknown6"},
+ {0, nullptr, "Unknown0"},
+ {1, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
+ {3, nullptr, "Unknown3"},
+ {4, nullptr, "Unknown4"},
+ {5, nullptr, "Unknown5"},
};
// clang-format on
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index fdc62d05b..55e00dd93 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -101,8 +101,8 @@ public:
}
std::u16string ReadInterfaceToken() {
- u32 unknown = Read<u32_le>();
- u32 length = Read<u32_le>();
+ [[maybe_unused]] const u32 unknown = Read<u32_le>();
+ const u32 length = Read<u32_le>();
std::u16string token{};
@@ -159,7 +159,7 @@ public:
header.data_size = static_cast<u32_le>(write_index - sizeof(Header));
header.data_offset = sizeof(Header);
header.objects_size = 4;
- header.objects_offset = sizeof(Header) + header.data_size;
+ header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size);
std::memcpy(buffer.data(), &header, sizeof(Header));
return buffer;
@@ -215,10 +215,9 @@ public:
explicit IGBPConnectRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPConnectRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -267,7 +266,7 @@ protected:
private:
struct Data {
- u32_le unk_0;
+ u32_le unk_0{};
};
Data data{};
@@ -279,10 +278,9 @@ public:
: Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPSetPreallocatedBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
buffer = Read<NVFlinger::IGBPBuffer>();
}
@@ -306,15 +304,40 @@ protected:
}
};
+class IGBPCancelBufferRequestParcel : public Parcel {
+public:
+ explicit IGBPCancelBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
+ Deserialize();
+ }
+
+ void DeserializeData() override {
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
+ data = Read<Data>();
+ }
+
+ struct Data {
+ u32_le slot;
+ Service::Nvidia::MultiFence multi_fence;
+ };
+
+ Data data;
+};
+
+class IGBPCancelBufferResponseParcel : public Parcel {
+protected:
+ void SerializeData() override {
+ Write<u32>(0); // Success
+ }
+};
+
class IGBPDequeueBufferRequestParcel : public Parcel {
public:
explicit IGBPDequeueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPDequeueBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -333,7 +356,6 @@ class IGBPDequeueBufferResponseParcel : public Parcel {
public:
explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence)
: slot(slot), multi_fence(multi_fence) {}
- ~IGBPDequeueBufferResponseParcel() override = default;
protected:
void SerializeData() override {
@@ -352,10 +374,9 @@ public:
explicit IGBPRequestBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPRequestBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
slot = Read<u32_le>();
}
@@ -384,10 +405,9 @@ public:
explicit IGBPQueueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPQueueBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -447,10 +467,9 @@ public:
explicit IGBPQueryRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPQueryRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
type = Read<u32_le>();
}
@@ -511,6 +530,7 @@ private:
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
static_cast<u32>(transaction), flags);
+ const auto guard = nv_flinger->Lock();
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
switch (transaction) {
@@ -518,9 +538,9 @@ private:
IGBPConnectRequestParcel request{ctx.ReadBuffer()};
IGBPConnectResponseParcel response{
static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) *
- Settings::values.resolution_factor),
+ Settings::values.resolution_factor.GetValue()),
static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) *
- Settings::values.resolution_factor)};
+ Settings::values.resolution_factor.GetValue())};
ctx.WriteBuffer(response.Serialize());
break;
}
@@ -547,9 +567,10 @@ private:
// Wait the current thread until a buffer becomes available
ctx.SleepClientThread(
"IHOSBinderDriver::DequeueBuffer", UINT64_MAX,
- [=](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
- Kernel::ThreadWakeupReason reason) {
+ [=, this](std::shared_ptr<Kernel::Thread> thread,
+ Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
+ const auto guard = nv_flinger->Lock();
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
auto result = buffer_queue.DequeueBuffer(width, height);
ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
@@ -594,7 +615,12 @@ private:
break;
}
case TransactionId::CancelBuffer: {
- LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
+ IGBPCancelBufferRequestParcel request{ctx.ReadBuffer()};
+
+ buffer_queue.CancelBuffer(request.data.slot, request.data.multi_fence);
+
+ IGBPCancelBufferResponseParcel response{};
+ ctx.WriteBuffer(response.Serialize());
break;
}
case TransactionId::Disconnect: {
@@ -614,6 +640,14 @@ private:
ctx.WriteBuffer(response.Serialize());
break;
}
+ case TransactionId::SetBufferCount: {
+ LOG_WARNING(Service_VI, "(STUBBED) called, transaction=SetBufferCount");
+ [[maybe_unused]] const auto buffer = ctx.ReadBuffer();
+
+ IGBPEmptyResponseParcel response{};
+ ctx.WriteBuffer(response.Serialize());
+ break;
+ }
default:
ASSERT_MSG(false, "Unimplemented");
}
@@ -690,6 +724,7 @@ public:
{3215, nullptr, "SetDisplayGamma"},
{3216, nullptr, "GetDisplayCmuLuma"},
{3217, nullptr, "SetDisplayCmuLuma"},
+ {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
{8225, nullptr, "GetSharedBufferMemoryHandleId"},
{8250, nullptr, "OpenSharedLayer"},
{8251, nullptr, "CloseSharedLayer"},
@@ -736,16 +771,16 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
} else {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
}
rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
@@ -775,6 +810,7 @@ public:
{2300, nullptr, "AcquireLayerTexturePresentingEvent"},
{2301, nullptr, "ReleaseLayerTexturePresentingEvent"},
{2302, nullptr, "GetDisplayHotplugEvent"},
+ {2303, nullptr, "GetDisplayModeChangedEvent"},
{2402, nullptr, "GetDisplayHotplugState"},
{2501, nullptr, "GetCompositorErrorInfo"},
{2601, nullptr, "GetDisplayErrorEvent"},
@@ -859,6 +895,7 @@ private:
const auto layer_id = nv_flinger->CreateLayer(display);
if (!layer_id) {
+ LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -975,6 +1012,7 @@ private:
const auto display_id = nv_flinger->OpenDisplay(name);
if (!display_id) {
+ LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1017,9 +1055,9 @@ private:
// between docked and undocked dimensions. We take the liberty of applying
// the resolution scaling factor here.
rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) *
- static_cast<u32>(Settings::values.resolution_factor));
+ static_cast<u32>(Settings::values.resolution_factor.GetValue()));
}
void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
@@ -1052,8 +1090,8 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called");
DisplayInfo display_info;
- display_info.width *= static_cast<u64>(Settings::values.resolution_factor);
- display_info.height *= static_cast<u64>(Settings::values.resolution_factor);
+ display_info.width *= static_cast<u64>(Settings::values.resolution_factor.GetValue());
+ display_info.height *= static_cast<u64>(Settings::values.resolution_factor.GetValue());
ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -1074,6 +1112,7 @@ private:
const auto display_id = nv_flinger->OpenDisplay(display_name);
if (!display_id) {
+ LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1081,6 +1120,7 @@ private:
const auto buffer_queue_id = nv_flinger->FindBufferQueueId(*display_id, layer_id);
if (!buffer_queue_id) {
+ LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1116,6 +1156,7 @@ private:
const auto layer_id = nv_flinger->CreateLayer(display_id);
if (!layer_id) {
+ LOG_ERROR(Service_VI, "Layer not found! layer_id={}", *layer_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1123,6 +1164,7 @@ private:
const auto buffer_queue_id = nv_flinger->FindBufferQueueId(display_id, *layer_id);
if (!buffer_queue_id) {
+ LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1153,6 +1195,7 @@ private:
const auto vsync_event = nv_flinger->FindVsyncEvent(display_id);
if (!vsync_event) {
+ LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_FOUND);
return;
@@ -1180,6 +1223,23 @@ private:
}
}
+ void GetIndirectLayerImageRequiredMemoryInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto width = rp.Pop<u64>();
+ const auto height = rp.Pop<u64>();
+ LOG_DEBUG(Service_VI, "called width={}, height={}", width, height);
+
+ constexpr std::size_t base_size = 0x20000;
+ constexpr std::size_t alignment = 0x1000;
+ const auto texture_size = width * height * 4;
+ const auto out_size = (texture_size + base_size - 1) / base_size * base_size;
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(out_size);
+ rb.Push(alignment);
+ }
+
static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
@@ -1193,6 +1253,7 @@ private:
case NintendoScaleMode::PreserveAspectRatio:
return MakeResult(ConvertedScaleMode::PreserveAspectRatio);
default:
+ LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
return ERR_OPERATION_FAILED;
}
}
@@ -1223,7 +1284,8 @@ IApplicationDisplayService::IApplicationDisplayService(
{2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
{2450, nullptr, "GetIndirectLayerImageMap"},
{2451, nullptr, "GetIndirectLayerImageCropMap"},
- {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},
+ {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo,
+ "GetIndirectLayerImageRequiredMemoryInfo"},
{5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"},
{5203, nullptr, "GetDisplayVsyncEventForDebug"},
};
@@ -1249,6 +1311,7 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
const auto policy = rp.PopEnum<Policy>();
if (!IsValidServiceAccess(permission, policy)) {
+ LOG_ERROR(Service_VI, "Permission denied for policy {}", static_cast<u32>(policy));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_PERMISSION_DENIED);
return;
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
index 9d5ceb608..6b7329345 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -12,6 +12,7 @@ VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
: ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
+ {1, nullptr, "GetDisplayServiceWithProxyNameExchange"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/wlan/wlan.cpp b/src/core/hle/service/wlan/wlan.cpp
index 2654594c1..0260d7dcf 100644
--- a/src/core/hle/service/wlan/wlan.cpp
+++ b/src/core/hle/service/wlan/wlan.cpp
@@ -15,34 +15,37 @@ public:
explicit WLANInfra() : ServiceFramework{"wlan:inf"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
+ {0, nullptr, "OpenMode"},
+ {1, nullptr, "CloseMode"},
{2, nullptr, "GetMacAddress"},
{3, nullptr, "StartScan"},
{4, nullptr, "StopScan"},
{5, nullptr, "Connect"},
{6, nullptr, "CancelConnect"},
{7, nullptr, "Disconnect"},
- {8, nullptr, "Unknown3"},
- {9, nullptr, "Unknown4"},
+ {8, nullptr, "GetConnectionEvent"},
+ {9, nullptr, "GetConnectionStatus"},
{10, nullptr, "GetState"},
{11, nullptr, "GetScanResult"},
{12, nullptr, "GetRssi"},
{13, nullptr, "ChangeRxAntenna"},
- {14, nullptr, "Unknown5"},
- {15, nullptr, "Unknown6"},
+ {14, nullptr, "GetFwVersion"},
+ {15, nullptr, "RequestSleep"},
{16, nullptr, "RequestWakeUp"},
{17, nullptr, "RequestIfUpDown"},
- {18, nullptr, "Unknown7"},
- {19, nullptr, "Unknown8"},
- {20, nullptr, "Unknown9"},
- {21, nullptr, "Unknown10"},
- {22, nullptr, "Unknown11"},
- {23, nullptr, "Unknown12"},
- {24, nullptr, "Unknown13"},
- {25, nullptr, "Unknown14"},
- {26, nullptr, "Unknown15"},
- {27, nullptr, "Unknown16"},
+ {18, nullptr, "Unknown18"},
+ {19, nullptr, "Unknown19"},
+ {20, nullptr, "Unknown20"},
+ {21, nullptr, "Unknown21"},
+ {22, nullptr, "Unknown22"},
+ {23, nullptr, "Unknown23"},
+ {24, nullptr, "Unknown24"},
+ {25, nullptr, "Unknown25"},
+ {26, nullptr, "Unknown26"},
+ {27, nullptr, "Unknown27"},
+ {28, nullptr, "Unknown28"},
+ {29, nullptr, "Unknown29"},
+ {30, nullptr, "Unknown30"},
};
// clang-format on
@@ -55,12 +58,12 @@ public:
explicit WLANLocal() : ServiceFramework{"wlan:lcl"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
- {4, nullptr, "Unknown5"},
- {5, nullptr, "Unknown6"},
+ {0, nullptr, "Unknown0"},
+ {1, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
+ {3, nullptr, "Unknown3"},
+ {4, nullptr, "Unknown4"},
+ {5, nullptr, "Unknown5"},
{6, nullptr, "GetMacAddress"},
{7, nullptr, "CreateBss"},
{8, nullptr, "DestroyBss"},
@@ -72,38 +75,42 @@ public:
{14, nullptr, "CancelJoin"},
{15, nullptr, "Disconnect"},
{16, nullptr, "SetBeaconLostCount"},
- {17, nullptr, "Unknown7"},
- {18, nullptr, "Unknown8"},
- {19, nullptr, "Unknown9"},
+ {17, nullptr, "Unknown17"},
+ {18, nullptr, "Unknown18"},
+ {19, nullptr, "Unknown19"},
{20, nullptr, "GetBssIndicationEvent"},
{21, nullptr, "GetBssIndicationInfo"},
{22, nullptr, "GetState"},
{23, nullptr, "GetAllowedChannels"},
{24, nullptr, "AddIe"},
{25, nullptr, "DeleteIe"},
- {26, nullptr, "Unknown10"},
- {27, nullptr, "Unknown11"},
+ {26, nullptr, "Unknown26"},
+ {27, nullptr, "Unknown27"},
{28, nullptr, "CreateRxEntry"},
{29, nullptr, "DeleteRxEntry"},
- {30, nullptr, "Unknown12"},
- {31, nullptr, "Unknown13"},
+ {30, nullptr, "Unknown30"},
+ {31, nullptr, "Unknown31"},
{32, nullptr, "AddMatchingDataToRxEntry"},
{33, nullptr, "RemoveMatchingDataFromRxEntry"},
{34, nullptr, "GetScanResult"},
- {35, nullptr, "Unknown14"},
+ {35, nullptr, "Unknown35"},
{36, nullptr, "SetActionFrameWithBeacon"},
{37, nullptr, "CancelActionFrameWithBeacon"},
{38, nullptr, "CreateRxEntryForActionFrame"},
{39, nullptr, "DeleteRxEntryForActionFrame"},
- {40, nullptr, "Unknown15"},
- {41, nullptr, "Unknown16"},
+ {40, nullptr, "Unknown40"},
+ {41, nullptr, "Unknown41"},
{42, nullptr, "CancelGetActionFrame"},
{43, nullptr, "GetRssi"},
- {44, nullptr, "Unknown17"},
- {45, nullptr, "Unknown18"},
- {46, nullptr, "Unknown19"},
- {47, nullptr, "Unknown20"},
- {48, nullptr, "Unknown21"},
+ {44, nullptr, "Unknown44"},
+ {45, nullptr, "Unknown45"},
+ {46, nullptr, "Unknown46"},
+ {47, nullptr, "Unknown47"},
+ {48, nullptr, "Unknown48"},
+ {49, nullptr, "Unknown49"},
+ {50, nullptr, "Unknown50"},
+ {51, nullptr, "Unknown51"},
+ {52, nullptr, "Unknown52"},
};
// clang-format on
@@ -142,18 +149,19 @@ public:
explicit WLANSocketManager() : ServiceFramework{"wlan:soc"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
- {4, nullptr, "Unknown5"},
- {5, nullptr, "Unknown6"},
+ {0, nullptr, "Unknown0"},
+ {1, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
+ {3, nullptr, "Unknown3"},
+ {4, nullptr, "Unknown4"},
+ {5, nullptr, "Unknown5"},
{6, nullptr, "GetMacAddress"},
{7, nullptr, "SwitchTsfTimerFunction"},
- {8, nullptr, "Unknown7"},
- {9, nullptr, "Unknown8"},
- {10, nullptr, "Unknown9"},
- {11, nullptr, "Unknown10"},
+ {8, nullptr, "Unknown8"},
+ {9, nullptr, "Unknown9"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
+ {12, nullptr, "Unknown12"},
};
// clang-format on
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 53559e8b1..2002dc4f2 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -14,6 +14,7 @@
#include "core/file_sys/romfs_factory.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
@@ -88,7 +89,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua
}
AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load(
- Kernel::Process& process) {
+ Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -113,7 +114,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
if (override_update) {
- const FileSys::PatchManager patch_manager(metadata.GetTitleID());
+ const FileSys::PatchManager patch_manager(
+ metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider());
dir = patch_manager.PatchExeFS(dir);
}
@@ -129,27 +131,48 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
metadata.Print();
- if (process.LoadFromMetadata(metadata).IsError()) {
- return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
+ const auto static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
+ "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"};
+
+ // Use the NSO module loader to figure out the code layout
+ std::size_t code_size{};
+ for (const auto& module : static_modules) {
+ const FileSys::VirtualFile module_file{dir->GetFile(module)};
+ if (!module_file) {
+ continue;
+ }
+
+ const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
+ const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+ process, system, *module_file, code_size, should_pass_arguments, false);
+ if (!tentative_next_load_addr) {
+ return {ResultStatus::ErrorLoadingNSO, {}};
+ }
+
+ code_size = *tentative_next_load_addr;
}
- const FileSys::PatchManager pm(metadata.GetTitleID());
+ // Setup the process code layout
+ if (process.LoadFromMetadata(metadata, code_size).IsError()) {
+ return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
+ }
// Load NSO modules
modules.clear();
- const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- VAddr next_load_addr = base_address;
- for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
- "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
- const FileSys::VirtualFile module_file = dir->GetFile(module);
- if (module_file == nullptr) {
+ const VAddr base_address{process.PageTable().GetCodeRegionStart()};
+ VAddr next_load_addr{base_address};
+ const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
+ system.GetContentProvider()};
+ for (const auto& module : static_modules) {
+ const FileSys::VirtualFile module_file{dir->GetFile(module)};
+ if (!module_file) {
continue;
}
- const VAddr load_addr = next_load_addr;
+ const VAddr load_addr{next_load_addr};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
- const auto tentative_next_load_addr =
- AppLoader_NSO::LoadModule(process, *module_file, load_addr, should_pass_arguments, pm);
+ const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+ process, system, *module_file, load_addr, should_pass_arguments, true, pm);
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -171,8 +194,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
// Register the RomFS if a ".romfs" file was found
if (romfs_iter != files.end() && *romfs_iter != nullptr) {
romfs = *romfs_iter;
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
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 @@
#include "core/file_sys/program_metadata.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Loader {
/**
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 8908e5328..dca1fcb18 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -10,8 +10,8 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/loader/elf.h"
#include "core/memory.h"
@@ -383,7 +383,8 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) {
+AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -393,15 +394,20 @@ AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) {
return {ResultStatus::ErrorIncorrectELFFileSize, {}};
}
- const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
+ const VAddr base_address = process.PageTable().GetCodeRegionStart();
ElfReader elf_reader(&buffer[0]);
Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
const VAddr entry_point = codeset.entrypoint;
+ // Setup the process code layout
+ if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), buffer.size()).IsError()) {
+ return {ResultStatus::ErrorNotInitialized, {}};
+ }
+
process.LoadModule(std::move(codeset), entry_point);
is_loaded = true;
- return {ResultStatus::Success, LoadParameters{48, Memory::DEFAULT_STACK_SIZE}};
+ return {ResultStatus::Success, LoadParameters{48, Core::Memory::DEFAULT_STACK_SIZE}};
}
} // namespace Loader
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 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Loader {
/// Loads an ELF/AXF file
@@ -26,7 +30,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
};
} // namespace Loader
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 092103abe..2a905d3e4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -7,14 +7,16 @@
#include "core/file_sys/program_metadata.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/loader/kip.h"
+#include "core/memory.h"
namespace Loader {
namespace {
constexpr u32 PageAlignSize(u32 size) {
- return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
} // Anonymous namespace
@@ -41,7 +43,8 @@ FileType AppLoader_KIP::GetFileType() const {
: FileType::Error;
}
-AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
+AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -68,7 +71,7 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
kip->GetMainThreadCpuCore(), kip->GetMainThreadStackSize(),
kip->GetTitleID(), 0xFFFFFFFFFFFFFFFF, kip->GetKernelCapabilities());
- const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
+ const VAddr base_address = process.PageTable().GetCodeRegionStart();
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
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 @@
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class KIP;
}
@@ -26,7 +30,7 @@ public:
FileType GetFileType() const override;
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
private:
std::unique_ptr<FileSys::KIP> kip;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 59ca7091a..deffe7379 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -3,11 +3,14 @@
// Refer to the license.txt file included.
#include <memory>
+#include <optional>
#include <ostream>
#include <string>
+#include "common/concepts.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h"
@@ -21,27 +24,41 @@
namespace Loader {
-FileType IdentifyFile(FileSys::VirtualFile file) {
- FileType type;
-
-#define CHECK_TYPE(loader) \
- type = AppLoader_##loader::IdentifyType(file); \
- if (FileType::Error != type) \
- return type;
+namespace {
- CHECK_TYPE(DeconstructedRomDirectory)
- CHECK_TYPE(ELF)
- CHECK_TYPE(NSO)
- CHECK_TYPE(NRO)
- CHECK_TYPE(NCA)
- CHECK_TYPE(XCI)
- CHECK_TYPE(NAX)
- CHECK_TYPE(NSP)
- CHECK_TYPE(KIP)
+template <Common::DerivedFrom<AppLoader> T>
+std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
+ const auto file_type = T::IdentifyType(file);
+ if (file_type != FileType::Error) {
+ return file_type;
+ }
+ return std::nullopt;
+}
-#undef CHECK_TYPE
+} // namespace
- return FileType::Unknown;
+FileType IdentifyFile(FileSys::VirtualFile file) {
+ if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) {
+ return *romdir_type;
+ } else if (const auto elf_type = IdentifyFileLoader<AppLoader_ELF>(file)) {
+ return *elf_type;
+ } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) {
+ return *nso_type;
+ } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) {
+ return *nro_type;
+ } else if (const auto nca_type = IdentifyFileLoader<AppLoader_NCA>(file)) {
+ return *nca_type;
+ } else if (const auto xci_type = IdentifyFileLoader<AppLoader_XCI>(file)) {
+ return *xci_type;
+ } else if (const auto nax_type = IdentifyFileLoader<AppLoader_NAX>(file)) {
+ return *nax_type;
+ } else if (const auto nsp_type = IdentifyFileLoader<AppLoader_NSP>(file)) {
+ return *nsp_type;
+ } else if (const auto kip_type = IdentifyFileLoader<AppLoader_KIP>(file)) {
+ return *kip_type;
+ } else {
+ return FileType::Unknown;
+ }
}
FileType GuessFromFilename(const std::string& name) {
@@ -51,7 +68,7 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA;
const std::string extension =
- Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
+ Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
if (extension == "elf")
return FileType::ELF;
@@ -178,15 +195,14 @@ AppLoader::~AppLoader() = default;
/**
* Get a loader for a file with a specific type
- * @param file The file to load
- * @param type The type of the file
- * @param file the file to retrieve the loader for
- * @param type the file type
+ * @param system The system context to use.
+ * @param file The file to retrieve the loader for
+ * @param type The file type
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/
-static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) {
+static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::VirtualFile file,
+ FileType type) {
switch (type) {
-
// Standard ELF file format.
case FileType::ELF:
return std::make_unique<AppLoader_ELF>(std::move(file));
@@ -205,7 +221,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
// NX XCI (nX Card Image) file format.
case FileType::XCI:
- return std::make_unique<AppLoader_XCI>(std::move(file));
+ return std::make_unique<AppLoader_XCI>(std::move(file), system.GetFileSystemController(),
+ system.GetContentProvider());
// NX NAX (NintendoAesXts) file format.
case FileType::NAX:
@@ -213,7 +230,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
// NX NSP (Nintendo Submission Package) file format
case FileType::NSP:
- return std::make_unique<AppLoader_NSP>(std::move(file));
+ return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
+ system.GetContentProvider());
// NX KIP (Kernel Internal Process) file format
case FileType::KIP:
@@ -228,20 +246,21 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
}
}
-std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
+std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file) {
FileType type = IdentifyFile(file);
- FileType filename_type = GuessFromFilename(file->GetName());
+ const FileType filename_type = GuessFromFilename(file->GetName());
// Special case: 00 is either a NCA or NAX.
if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) {
LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
- if (FileType::Unknown == type)
+ if (FileType::Unknown == type) {
type = filename_type;
+ }
}
LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type));
- return GetFileLoader(std::move(file), type);
+ return GetFileLoader(system, std::move(file), type);
}
} // namespace Loader
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 227ecc704..8dc2d7615 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -15,6 +15,10 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NACP;
} // namespace FileSys
@@ -154,9 +158,10 @@ public:
/**
* Load the application and return the created Process instance
* @param process The newly created process.
+ * @param system The system that this process is being loaded under.
* @return The status result of the operation.
*/
- virtual LoadResult Load(Kernel::Process& process) = 0;
+ virtual LoadResult Load(Kernel::Process& process, Core::System& system) = 0;
/**
* Get the code (typically .code section) of the application
@@ -285,9 +290,12 @@ protected:
/**
* Identifies a bootable file and return a suitable loader
- * @param file The bootable file
- * @return the best loader for this file
+ *
+ * @param system The system context.
+ * @param file The bootable file.
+ *
+ * @return the best loader for this file.
*/
-std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file);
+std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file);
} // namespace Loader
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 {
return IdentifyTypeImpl(*nax);
}
-AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
+AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -65,7 +66,7 @@ AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
return {nca_status, {}};
}
- const auto result = nca_loader->Load(process);
+ const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
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 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
-namespace FileSys {
+namespace Core {
+class System;
+}
+namespace FileSys {
class NAX;
-
} // namespace FileSys
namespace Loader {
@@ -33,7 +35,7 @@ public:
FileType GetFileType() const override;
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
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) {
return FileType::Error;
}
-AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
+AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -52,14 +52,14 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
- const auto load_result = directory_loader->Load(process);
+ const auto load_result = directory_loader->Load(process, system);
if (load_result.first != ResultStatus::Success) {
return load_result;
}
if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
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 @@
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NCA;
}
@@ -33,7 +37,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 175898b91..5f4b3104b 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -16,8 +16,8 @@
#include "core/file_sys/vfs_offset.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/nro.h"
#include "core/loader/nso.h"
@@ -127,11 +127,11 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
}
static constexpr u32 PageAlignSize(u32 size) {
- return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
- const std::string& name, VAddr load_base) {
+ const std::string& name) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -187,41 +187,44 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
+ // Setup the process code layout
+ if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
+ .IsError()) {
+ return false;
+ }
+
// Load codeset for current process
codeset.memory = std::move(program_image);
- process.LoadModule(std::move(codeset), load_base);
+ process.LoadModule(std::move(codeset), process.PageTable().GetCodeRegionStart());
// Register module with GDBStub
- GDBStub::RegisterModule(name, load_base, load_base);
+ GDBStub::RegisterModule(name, process.PageTable().GetCodeRegionStart(),
+ process.PageTable().GetCodeRegionEnd());
return true;
}
-bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file,
- VAddr load_base) {
- return LoadNroImpl(process, file.ReadAllBytes(), file.GetName(), load_base);
+bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file) {
+ return LoadNroImpl(process, file.ReadAllBytes(), file.GetName());
}
-AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
+AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- // Load NRO
- const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
-
- if (!LoadNro(process, *file, base_address)) {
+ if (!LoadNro(process, *file)) {
return {ResultStatus::ErrorLoadingNRO, {}};
}
if (romfs != nullptr) {
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
is_loaded = true;
return {ResultStatus::Success,
- LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}};
+ LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}};
}
ResultStatus AppLoader_NRO::ReadIcon(std::vector<u8>& buffer) {
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 71811bc29..a2aab2ecc 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NACP;
}
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
@@ -47,7 +51,7 @@ public:
bool IsRomFSUpdatable() const override;
private:
- bool LoadNro(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base);
+ bool LoadNro(Kernel::Process& process, const FileSys::VfsFile& file);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 044067a5b..aa85c1a29 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -16,8 +16,8 @@
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
#include "core/memory.h"
#include "core/settings.h"
@@ -37,7 +37,7 @@ static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
const NSOSegmentHeader& header) {
- const std::vector<u8> uncompressed_data =
+ std::vector<u8> uncompressed_data =
Common::Compression::DecompressDataLZ4(compressed_data, header.size);
ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
@@ -47,7 +47,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
}
constexpr u32 PageAlignSize(u32 size) {
- return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
} // Anonymous namespace
@@ -71,21 +71,21 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::NSO;
}
-std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
+std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::System& system,
const FileSys::VfsFile& file, VAddr load_base,
- bool should_pass_arguments,
+ bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm) {
if (file.GetSize() < sizeof(NSOHeader)) {
- return {};
+ return std::nullopt;
}
NSOHeader nso_header{};
if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
- return {};
+ return std::nullopt;
}
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
- return {};
+ return std::nullopt;
}
// Build program image
@@ -97,21 +97,17 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
- program_image.resize(nso_header.segments[i].location +
- PageAlignSize(static_cast<u32>(data.size())));
+ program_image.resize(nso_header.segments[i].location + static_cast<u32>(data.size()));
std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
data.size());
codeset.segments[i].addr = nso_header.segments[i].location;
codeset.segments[i].offset = nso_header.segments[i].location;
- codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
+ codeset.segments[i].size = nso_header.segments[i].size;
}
- if (should_pass_arguments) {
- std::vector<u8> arg_data{Settings::values.program_args.begin(),
- Settings::values.program_args.end()};
- if (arg_data.empty()) {
- arg_data.resize(NSO_ARGUMENT_DEFAULT_SIZE);
- }
+ if (should_pass_arguments && !Settings::values.program_args.empty()) {
+ const auto arg_data{Settings::values.program_args};
+
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
@@ -123,24 +119,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
arg_data.size());
}
- // MOD header pointer is at .text offset + 4
- u32 module_offset;
- std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
-
- // Read MOD header
- MODHeader mod_header{};
- // Default .bss to size in segment header if MOD0 section doesn't exist
- u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
- std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader));
- const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
- if (has_mod_header) {
- // Resize program image to include .bss section and page align each section
- bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
- }
- codeset.DataSegment().size += bss_size;
- const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
+ codeset.DataSegment().size += nso_header.segments[2].bss_size;
+ const u32 image_size{
+ PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
program_image.resize(image_size);
+ for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
+ codeset.segments[i].size = PageAlignSize(codeset.segments[i].size);
+ }
+
// Apply patches if necessary
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
std::vector<u8> pi_header;
@@ -154,11 +141,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
}
+ // If we aren't actually loading (i.e. just computing the process code layout), we are done
+ if (!load_into_process) {
+ return load_base + image_size;
+ }
+
// Apply cheats if they exist and the program has a valid title ID
if (pm) {
- auto& system = Core::System::GetInstance();
system.SetCurrentProcessBuildID(nso_header.build_id);
- const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
+ const auto cheats = pm->CreateCheatList(nso_header.build_id);
if (!cheats.empty()) {
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
}
@@ -174,7 +165,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
return load_base + image_size;
}
-AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
+AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -182,8 +173,8 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
modules.clear();
// Load module
- const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- if (!LoadModule(process, *file, base_address, true)) {
+ const VAddr base_address = process.PageTable().GetCodeRegionStart();
+ if (!LoadModule(process, system, *file, base_address, true, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -192,7 +183,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
is_loaded = true;
return {ResultStatus::Success,
- LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}};
+ LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}};
}
ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) {
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index d2d600cd9..d331096ae 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -12,6 +12,10 @@
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class Process;
}
@@ -55,9 +59,7 @@ struct NSOHeader {
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
-constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
-// NOTE: Official software default argument state is unverified.
-constexpr u64 NSO_ARGUMENT_DEFAULT_SIZE = 1;
+constexpr u32 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
struct NSOArgumentHeader {
u32_le allocated_size;
@@ -82,11 +84,12 @@ public:
return IdentifyType(file);
}
- static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file,
- VAddr load_base, bool should_pass_arguments,
+ static std::optional<VAddr> LoadModule(Kernel::Process& process, Core::System& system,
+ const FileSys::VfsFile& file, VAddr load_base,
+ bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm = {});
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadNSOModules(Modules& modules) override;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 13950fc08..e821937fd 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -21,26 +21,33 @@
namespace Loader {
-AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
+AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider)
: AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
title_id(nsp->GetProgramTitleID()) {
- if (nsp->GetStatus() != ResultStatus::Success)
+ if (nsp->GetStatus() != ResultStatus::Success) {
return;
+ }
if (nsp->IsExtractedType()) {
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
} else {
const auto control_nca =
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
- if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+ if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) {
return;
+ }
- std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
+ std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] {
+ const FileSys::PatchManager pm{nsp->GetProgramTitleID(), fsc, content_provider};
+ return pm.ParseControlNCA(*control_nca);
+ }();
- if (title_id == 0)
+ if (title_id == 0) {
return;
+ }
secondary_loader = std::make_unique<AppLoader_NCA>(
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
@@ -71,7 +78,7 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
+AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -99,15 +106,14 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
}
- const auto result = secondary_loader->Load(process);
+ const auto result = secondary_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
- std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 868b028d3..36e8e3533 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -10,10 +10,15 @@
#include "core/loader/loader.h"
namespace FileSys {
+class ContentProvider;
class NACP;
class NSP;
} // namespace FileSys
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Loader {
class AppLoader_NCA;
@@ -21,7 +26,9 @@ class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_NSP final : public AppLoader {
public:
- explicit AppLoader_NSP(FileSys::VirtualFile file);
+ explicit AppLoader_NSP(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider);
~AppLoader_NSP() override;
/**
@@ -35,7 +42,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 7186ad1ff..536e721fc 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -20,18 +20,24 @@
namespace Loader {
-AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
+AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider)
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
- if (xci->GetStatus() != ResultStatus::Success)
+ if (xci->GetStatus() != ResultStatus::Success) {
return;
+ }
const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
- if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+ if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) {
return;
+ }
- std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca);
+ std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] {
+ const FileSys::PatchManager pm{xci->GetProgramTitleID(), fsc, content_provider};
+ return pm.ParseControlNCA(*control_nca);
+ }();
}
AppLoader_XCI::~AppLoader_XCI() = default;
@@ -49,7 +55,7 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
+AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -66,15 +72,14 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
return {ResultStatus::ErrorMissingProductionKeyFile, {}};
}
- const auto result = nca_loader->Load(process);
+ const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
- std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 618ae2f47..6dc1f9243 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -10,10 +10,15 @@
#include "core/loader/loader.h"
namespace FileSys {
+class ContentProvider;
class NACP;
class XCI;
} // namespace FileSys
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Loader {
class AppLoader_NCA;
@@ -21,7 +26,9 @@ class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_XCI final : public AppLoader {
public:
- explicit AppLoader_XCI(FileSys::VirtualFile file);
+ explicit AppLoader_XCI(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider);
~AppLoader_XCI() override;
/**
@@ -35,7 +42,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 6061d37ae..b88aa5c40 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -8,19 +8,21 @@
#include <utility>
#include "common/assert.h"
+#include "common/atomic_ops.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/page_table.h"
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
+#include "core/device_memory.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/physical_memory.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
#include "video_core/gpu.h"
-namespace Memory {
+namespace Core::Memory {
// Implementation class used to keep the specifics of the memory subsystem hidden
// from outside classes. This also allows modification to the internals of the memory
@@ -28,23 +30,15 @@ namespace Memory {
struct Memory::Impl {
explicit Impl(Core::System& system_) : system{system_} {}
- void SetCurrentPageTable(Kernel::Process& process) {
- current_page_table = &process.VMManager().page_table;
+ void SetCurrentPageTable(Kernel::Process& process, u32 core_id) {
+ current_page_table = &process.PageTable().PageTableImpl();
- const std::size_t address_space_width = process.VMManager().GetAddressSpaceWidth();
+ const std::size_t address_space_width = process.PageTable().GetAddressSpaceWidth();
- system.ArmInterface(0).PageTableChanged(*current_page_table, address_space_width);
- system.ArmInterface(1).PageTableChanged(*current_page_table, address_space_width);
- system.ArmInterface(2).PageTableChanged(*current_page_table, address_space_width);
- system.ArmInterface(3).PageTableChanged(*current_page_table, address_space_width);
+ system.ArmInterface(core_id).PageTableChanged(*current_page_table, address_space_width);
}
- void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Kernel::PhysicalMemory& memory, VAddr offset) {
- MapMemoryRegion(page_table, base, size, memory.data() + offset);
- }
-
- void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
+ void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
@@ -52,46 +46,27 @@ struct Memory::Impl {
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
Common::MemoryHookPointer mmio_handler) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr,
- Common::PageType::Special);
-
- const auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- const Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice,
- std::move(mmio_handler)};
- page_table.special_regions.add(
- std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
+ UNIMPLEMENTED();
}
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr,
- Common::PageType::Unmapped);
-
- const auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- page_table.special_regions.erase(interval);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped);
}
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
Common::MemoryHookPointer hook) {
- const auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- const Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.add(
- std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
+ UNIMPLEMENTED();
}
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
Common::MemoryHookPointer hook) {
- const auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- const Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.subtract(
- std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
+ UNIMPLEMENTED();
}
bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) const {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
const u8* const page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
if (page_pointer != nullptr) {
@@ -113,55 +88,28 @@ struct Memory::Impl {
return IsValidVirtualAddress(*system.CurrentProcess(), vaddr);
}
- /**
- * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
- * using a VMA from the current process
- */
- u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
- const auto& vm_manager = process.VMManager();
-
- const auto it = vm_manager.FindVMA(vaddr);
- DEBUG_ASSERT(vm_manager.IsValidHandle(it));
+ u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const {
+ const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]};
- u8* direct_pointer = nullptr;
- const auto& vma = it->second;
- switch (vma.type) {
- case Kernel::VMAType::AllocatedMemoryBlock:
- direct_pointer = vma.backing_block->data() + vma.offset;
- break;
- case Kernel::VMAType::BackingMemory:
- direct_pointer = vma.backing_memory;
- break;
- case Kernel::VMAType::Free:
- return nullptr;
- default:
- UNREACHABLE();
+ if (!paddr) {
+ return {};
}
- return direct_pointer + (vaddr - vma.base);
+ return system.DeviceMemory().GetPointer(paddr) + vaddr;
}
- /**
- * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
- * using a VMA from the current process.
- */
- u8* GetPointerFromVMA(VAddr vaddr) {
- return GetPointerFromVMA(*system.CurrentProcess(), vaddr);
- }
-
- u8* GetPointer(const VAddr vaddr) {
- u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
+ u8* GetPointer(const VAddr vaddr) const {
+ u8* const page_pointer{current_page_table->pointers[vaddr >> PAGE_BITS]};
+ if (page_pointer) {
return page_pointer + vaddr;
}
if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
Common::PageType::RasterizerCachedMemory) {
- return GetPointerFromVMA(vaddr);
+ return GetPointerFromRasterizerCachedMemory(vaddr);
}
- LOG_ERROR(HW_Memory, "Unknown GetPointer @ 0x{:016X}", vaddr);
- return nullptr;
+ return {};
}
u8 Read8(const VAddr addr) {
@@ -169,15 +117,33 @@ struct Memory::Impl {
}
u16 Read16(const VAddr addr) {
- return Read<u16_le>(addr);
+ if ((addr & 1) == 0) {
+ return Read<u16_le>(addr);
+ } else {
+ const u32 a{Read<u8>(addr)};
+ const u32 b{Read<u8>(addr + sizeof(u8))};
+ return static_cast<u16>((b << 8) | a);
+ }
}
u32 Read32(const VAddr addr) {
- return Read<u32_le>(addr);
+ if ((addr & 3) == 0) {
+ return Read<u32_le>(addr);
+ } else {
+ const u32 a{Read16(addr)};
+ const u32 b{Read16(addr + sizeof(u16))};
+ return (b << 16) | a;
+ }
}
u64 Read64(const VAddr addr) {
- return Read<u64_le>(addr);
+ if ((addr & 7) == 0) {
+ return Read<u64_le>(addr);
+ } else {
+ const u32 a{Read32(addr)};
+ const u32 b{Read32(addr + sizeof(u32))};
+ return (static_cast<u64>(b) << 32) | a;
+ }
}
void Write8(const VAddr addr, const u8 data) {
@@ -185,15 +151,46 @@ struct Memory::Impl {
}
void Write16(const VAddr addr, const u16 data) {
- Write<u16_le>(addr, data);
+ if ((addr & 1) == 0) {
+ Write<u16_le>(addr, data);
+ } else {
+ Write<u8>(addr, static_cast<u8>(data));
+ Write<u8>(addr + sizeof(u8), static_cast<u8>(data >> 8));
+ }
}
void Write32(const VAddr addr, const u32 data) {
- Write<u32_le>(addr, data);
+ if ((addr & 3) == 0) {
+ Write<u32_le>(addr, data);
+ } else {
+ Write16(addr, static_cast<u16>(data));
+ Write16(addr + sizeof(u16), static_cast<u16>(data >> 16));
+ }
}
void Write64(const VAddr addr, const u64 data) {
- Write<u64_le>(addr, data);
+ if ((addr & 7) == 0) {
+ Write<u64_le>(addr, data);
+ } else {
+ Write32(addr, static_cast<u32>(data));
+ Write32(addr + sizeof(u32), static_cast<u32>(data >> 32));
+ }
+ }
+
+ bool WriteExclusive8(const VAddr addr, const u8 data, const u8 expected) {
+ return WriteExclusive<u8>(addr, data, expected);
+ }
+
+ bool WriteExclusive16(const VAddr addr, const u16 data, const u16 expected) {
+ return WriteExclusive<u16_le>(addr, data, expected);
+ }
+
+ bool WriteExclusive32(const VAddr addr, const u32 data, const u32 expected) {
+ return WriteExclusive<u32_le>(addr, data, expected);
+ }
+
+ bool WriteExclusive64(const VAddr addr, const u64 data, const u64 expected) {
+ return WriteExclusive<u64_le>(addr, data, expected);
}
std::string ReadCString(VAddr vaddr, std::size_t max_length) {
@@ -213,7 +210,7 @@ struct Memory::Impl {
void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = src_addr >> PAGE_BITS;
@@ -241,7 +238,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- const u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
system.GPU().FlushRegion(current_vaddr, copy_amount);
std::memcpy(dest_buffer, host_ptr, copy_amount);
break;
@@ -259,7 +256,7 @@ struct Memory::Impl {
void ReadBlockUnsafe(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = src_addr >> PAGE_BITS;
@@ -287,7 +284,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- const u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
std::memcpy(dest_buffer, host_ptr, copy_amount);
break;
}
@@ -312,7 +309,7 @@ struct Memory::Impl {
void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer,
const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = dest_addr >> PAGE_BITS;
std::size_t page_offset = dest_addr & PAGE_MASK;
@@ -338,7 +335,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
system.GPU().InvalidateRegion(current_vaddr, copy_amount);
std::memcpy(host_ptr, src_buffer, copy_amount);
break;
@@ -356,7 +353,7 @@ struct Memory::Impl {
void WriteBlockUnsafe(const Kernel::Process& process, const VAddr dest_addr,
const void* src_buffer, const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = dest_addr >> PAGE_BITS;
std::size_t page_offset = dest_addr & PAGE_MASK;
@@ -382,7 +379,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
std::memcpy(host_ptr, src_buffer, copy_amount);
break;
}
@@ -406,7 +403,7 @@ struct Memory::Impl {
}
void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = dest_addr >> PAGE_BITS;
std::size_t page_offset = dest_addr & PAGE_MASK;
@@ -432,7 +429,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
system.GPU().InvalidateRegion(current_vaddr, copy_amount);
std::memset(host_ptr, 0, copy_amount);
break;
@@ -453,7 +450,7 @@ struct Memory::Impl {
void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
const std::size_t size) {
- const auto& page_table = process.VMManager().page_table;
+ const auto& page_table = process.PageTable().PageTableImpl();
std::size_t remaining_size = size;
std::size_t page_index = src_addr >> PAGE_BITS;
std::size_t page_offset = src_addr & PAGE_MASK;
@@ -479,7 +476,7 @@ struct Memory::Impl {
break;
}
case Common::PageType::RasterizerCachedMemory: {
- const u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
+ const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
system.GPU().FlushRegion(current_vaddr, copy_amount);
WriteBlock(process, dest_addr, host_ptr, copy_amount);
break;
@@ -512,7 +509,7 @@ struct Memory::Impl {
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
- Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType& page_type{current_page_table->attributes[vaddr >> PAGE_BITS]};
if (cached) {
// Switch page type to cached if now cached
@@ -544,16 +541,16 @@ struct Memory::Impl {
// that this area is already unmarked as cached.
break;
case Common::PageType::RasterizerCachedMemory: {
- u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ u8* pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)};
if (pointer == nullptr) {
// It's possible that this function has been called while updating the
// pagetable after unmapping a VMA. In that case the underlying VMA will no
// longer exist, and we should just leave the pagetable entry blank.
page_type = Common::PageType::Unmapped;
} else {
- page_type = Common::PageType::Memory;
current_page_table->pointers[vaddr >> PAGE_BITS] =
pointer - (vaddr & ~PAGE_MASK);
+ page_type = Common::PageType::Memory;
}
break;
}
@@ -570,12 +567,12 @@ struct Memory::Impl {
* @param page_table The page table to use to perform the mapping.
* @param base The base address to begin mapping at.
* @param size The total size of the range in bytes.
- * @param memory The memory to map.
+ * @param target The target address to begin mapping from.
* @param type The page type to map the memory as.
*/
- void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
+ void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target,
Common::PageType type) {
- LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
+ LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
// During boot, current_page_table might not be set yet, in which case we need not flush
@@ -593,19 +590,29 @@ struct Memory::Impl {
ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
base + page_table.pointers.size());
- std::fill(page_table.attributes.begin() + base, page_table.attributes.begin() + end, type);
+ if (!target) {
+ ASSERT_MSG(type != Common::PageType::Memory,
+ "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
+
+ while (base != end) {
+ page_table.attributes[base] = type;
+ page_table.pointers[base] = nullptr;
+ page_table.backing_addr[base] = 0;
- if (memory == nullptr) {
- std::fill(page_table.pointers.begin() + base, page_table.pointers.begin() + end,
- memory);
+ base += 1;
+ }
} else {
while (base != end) {
- page_table.pointers[base] = memory - (base << PAGE_BITS);
+ page_table.pointers[base] =
+ system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS);
+ page_table.attributes[base] = type;
+ page_table.backing_addr[base] = target - (base << PAGE_BITS);
+
ASSERT_MSG(page_table.pointers[base],
"memory mapping base yield a nullptr within the table");
base += 1;
- memory += PAGE_SIZE;
+ target += PAGE_SIZE;
}
}
}
@@ -640,7 +647,7 @@ struct Memory::Impl {
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
case Common::PageType::RasterizerCachedMemory: {
- const u8* const host_ptr = GetPointerFromVMA(vaddr);
+ const u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
system.GPU().FlushRegion(vaddr, sizeof(T));
T value;
std::memcpy(&value, host_ptr, sizeof(T));
@@ -682,7 +689,7 @@ struct Memory::Impl {
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
case Common::PageType::RasterizerCachedMemory: {
- u8* const host_ptr{GetPointerFromVMA(vaddr)};
+ u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
system.GPU().InvalidateRegion(vaddr, sizeof(T));
std::memcpy(host_ptr, &data, sizeof(T));
break;
@@ -692,6 +699,65 @@ struct Memory::Impl {
}
}
+ template <typename T>
+ bool WriteExclusive(const VAddr vaddr, const T data, const T expected) {
+ u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
+ if (page_pointer != nullptr) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ auto* pointer = reinterpret_cast<volatile T*>(&page_pointer[vaddr]);
+ return Common::AtomicCompareAndSwap(pointer, data, expected);
+ }
+
+ const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ switch (type) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
+ static_cast<u32>(data), vaddr);
+ return true;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
+ break;
+ case Common::PageType::RasterizerCachedMemory: {
+ u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
+ system.GPU().InvalidateRegion(vaddr, sizeof(T));
+ auto* pointer = reinterpret_cast<volatile T*>(&host_ptr);
+ return Common::AtomicCompareAndSwap(pointer, data, expected);
+ }
+ default:
+ UNREACHABLE();
+ }
+ return true;
+ }
+
+ bool WriteExclusive128(const VAddr vaddr, const u128 data, const u128 expected) {
+ u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
+ if (page_pointer != nullptr) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ auto* pointer = reinterpret_cast<volatile u64*>(&page_pointer[vaddr]);
+ return Common::AtomicCompareAndSwap(pointer, data, expected);
+ }
+
+ const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ switch (type) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}{:016X}", sizeof(data) * 8,
+ static_cast<u64>(data[1]), static_cast<u64>(data[0]), vaddr);
+ return true;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
+ break;
+ case Common::PageType::RasterizerCachedMemory: {
+ u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
+ system.GPU().InvalidateRegion(vaddr, sizeof(u128));
+ auto* pointer = reinterpret_cast<volatile u64*>(&host_ptr);
+ return Common::AtomicCompareAndSwap(pointer, data, expected);
+ }
+ default:
+ UNREACHABLE();
+ }
+ return true;
+ }
+
Common::PageTable* current_page_table = nullptr;
Core::System& system;
};
@@ -699,16 +765,11 @@ struct Memory::Impl {
Memory::Memory(Core::System& system) : impl{std::make_unique<Impl>(system)} {}
Memory::~Memory() = default;
-void Memory::SetCurrentPageTable(Kernel::Process& process) {
- impl->SetCurrentPageTable(process);
+void Memory::SetCurrentPageTable(Kernel::Process& process, u32 core_id) {
+ impl->SetCurrentPageTable(process, core_id);
}
-void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Kernel::PhysicalMemory& memory, VAddr offset) {
- impl->MapMemoryRegion(page_table, base, size, memory, offset);
-}
-
-void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
+void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
impl->MapMemoryRegion(page_table, base, size, target);
}
@@ -779,6 +840,26 @@ void Memory::Write64(VAddr addr, u64 data) {
impl->Write64(addr, data);
}
+bool Memory::WriteExclusive8(VAddr addr, u8 data, u8 expected) {
+ return impl->WriteExclusive8(addr, data, expected);
+}
+
+bool Memory::WriteExclusive16(VAddr addr, u16 data, u16 expected) {
+ return impl->WriteExclusive16(addr, data, expected);
+}
+
+bool Memory::WriteExclusive32(VAddr addr, u32 data, u32 expected) {
+ return impl->WriteExclusive32(addr, data, expected);
+}
+
+bool Memory::WriteExclusive64(VAddr addr, u64 data, u64 expected) {
+ return impl->WriteExclusive64(addr, data, expected);
+}
+
+bool Memory::WriteExclusive128(VAddr addr, u128 data, u128 expected) {
+ return impl->WriteExclusive128(addr, data, expected);
+}
+
std::string Memory::ReadCString(VAddr vaddr, std::size_t max_length) {
return impl->ReadCString(vaddr, max_length);
}
@@ -845,4 +926,4 @@ bool IsKernelVirtualAddress(const VAddr vaddr) {
return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END;
}
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory.h b/src/core/memory.h
index b92d678a4..4a1cc63f4 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -23,7 +23,7 @@ class PhysicalMemory;
class Process;
} // namespace Kernel
-namespace Memory {
+namespace Core::Memory {
/**
* Page size used by the ARM architecture. This is the smallest granularity with which memory can
@@ -64,20 +64,7 @@ public:
*
* @param process The process to use the page table of.
*/
- void SetCurrentPageTable(Kernel::Process& process);
-
- /**
- * Maps an physical buffer onto a region of the emulated process address space.
- *
- * @param page_table The page table of the emulated process.
- * @param base The address to start mapping at. Must be page-aligned.
- * @param size The amount of bytes to map. Must be page-aligned.
- * @param memory Physical buffer with the memory backing the mapping. Must be of length
- * at least `size + offset`.
- * @param offset The offset within the physical memory. Must be page-aligned.
- */
- void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Kernel::PhysicalMemory& memory, VAddr offset);
+ void SetCurrentPageTable(Kernel::Process& process, u32 core_id);
/**
* Maps an allocated buffer onto a region of the emulated process address space.
@@ -88,7 +75,7 @@ public:
* @param target Buffer with the memory backing the mapping. Must be of length at least
* `size`.
*/
- void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
+ void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target);
/**
* Maps a region of the emulated process address space as a IO region.
@@ -258,6 +245,71 @@ public:
void Write64(VAddr addr, u64 data);
/**
+ * Writes a 8-bit unsigned integer to the given virtual address in
+ * the current process' address space if and only if the address contains
+ * the expected value. This operation is atomic.
+ *
+ * @param addr The virtual address to write the 8-bit unsigned integer to.
+ * @param data The 8-bit unsigned integer to write to the given virtual address.
+ * @param expected The 8-bit unsigned integer to check against the given virtual address.
+ *
+ * @post The memory range [addr, sizeof(data)) contains the given data value.
+ */
+ bool WriteExclusive8(VAddr addr, u8 data, u8 expected);
+
+ /**
+ * Writes a 16-bit unsigned integer to the given virtual address in
+ * the current process' address space if and only if the address contains
+ * the expected value. This operation is atomic.
+ *
+ * @param addr The virtual address to write the 16-bit unsigned integer to.
+ * @param data The 16-bit unsigned integer to write to the given virtual address.
+ * @param expected The 16-bit unsigned integer to check against the given virtual address.
+ *
+ * @post The memory range [addr, sizeof(data)) contains the given data value.
+ */
+ bool WriteExclusive16(VAddr addr, u16 data, u16 expected);
+
+ /**
+ * Writes a 32-bit unsigned integer to the given virtual address in
+ * the current process' address space if and only if the address contains
+ * the expected value. This operation is atomic.
+ *
+ * @param addr The virtual address to write the 32-bit unsigned integer to.
+ * @param data The 32-bit unsigned integer to write to the given virtual address.
+ * @param expected The 32-bit unsigned integer to check against the given virtual address.
+ *
+ * @post The memory range [addr, sizeof(data)) contains the given data value.
+ */
+ bool WriteExclusive32(VAddr addr, u32 data, u32 expected);
+
+ /**
+ * Writes a 64-bit unsigned integer to the given virtual address in
+ * the current process' address space if and only if the address contains
+ * the expected value. This operation is atomic.
+ *
+ * @param addr The virtual address to write the 64-bit unsigned integer to.
+ * @param data The 64-bit unsigned integer to write to the given virtual address.
+ * @param expected The 64-bit unsigned integer to check against the given virtual address.
+ *
+ * @post The memory range [addr, sizeof(data)) contains the given data value.
+ */
+ bool WriteExclusive64(VAddr addr, u64 data, u64 expected);
+
+ /**
+ * Writes a 128-bit unsigned integer to the given virtual address in
+ * the current process' address space if and only if the address contains
+ * the expected value. This operation is atomic.
+ *
+ * @param addr The virtual address to write the 128-bit unsigned integer to.
+ * @param data The 128-bit unsigned integer to write to the given virtual address.
+ * @param expected The 128-bit unsigned integer to check against the given virtual address.
+ *
+ * @post The memory range [addr, sizeof(data)) contains the given data value.
+ */
+ bool WriteExclusive128(VAddr addr, u128 data, u128 expected);
+
+ /**
* Reads a null-terminated string from the given virtual address.
* This function will continually read characters until either:
*
@@ -503,4 +555,4 @@ private:
/// Determines if the given VAddr is a kernel address
bool IsKernelVirtualAddress(VAddr vaddr);
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 4472500d2..2dd0eb0f8 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -10,17 +10,33 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hardware_properties.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
+#include "core/memory.h"
#include "core/memory/cheat_engine.h"
-namespace Memory {
-
-constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 12);
+namespace Core::Memory {
+namespace {
+constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
+std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) {
+ auto end_index = start_index;
+ while (data[end_index] != match) {
+ ++end_index;
+ if (end_index > data.size() ||
+ (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
+ return {};
+ }
+ }
+
+ return data.substr(start_index, end_index - start_index);
+}
+} // Anonymous namespace
+
StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
: metadata(metadata), system(system) {}
@@ -40,7 +56,7 @@ u64 StandardVmCallbacks::HidKeysDown() {
if (applet_resource == nullptr) {
LOG_WARNING(CheatEngine,
"Attempted to read input state, but applet resource is not initialized!");
- return false;
+ return 0;
}
const auto press_state =
@@ -80,26 +96,9 @@ CheatParser::~CheatParser() = default;
TextCheatParser::~TextCheatParser() = default;
-namespace {
-template <char match>
-std::string_view ExtractName(std::string_view data, std::size_t start_index) {
- auto end_index = start_index;
- while (data[end_index] != match) {
- ++end_index;
- if (end_index > data.size() ||
- (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
- return {};
- }
- }
-
- return data.substr(start_index, end_index - start_index);
-}
-} // Anonymous namespace
-
-std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
- std::string_view data) const {
+std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
std::vector<CheatEntry> out(1);
- std::optional<u64> current_entry = std::nullopt;
+ std::optional<u64> current_entry;
for (std::size_t i = 0; i < data.size(); ++i) {
if (::isspace(data[i])) {
@@ -113,7 +112,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
return {};
}
- const auto name = ExtractName<'}'>(data, i + 1);
+ const auto name = ExtractName(data, i + 1, '}');
if (name.empty()) {
return {};
}
@@ -130,7 +129,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
current_entry = out.size();
out.emplace_back();
- const auto name = ExtractName<']'>(data, i + 1);
+ const auto name = ExtractName(data, i + 1, ']');
if (name.empty()) {
return {};
}
@@ -154,8 +153,9 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
return {};
}
+ const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
- std::stoul(hex, nullptr, 0x10);
+ value;
i += 8;
} else {
@@ -188,23 +188,38 @@ CheatEngine::~CheatEngine() {
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
- [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
- core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ FrameCallback(user_data, ns_late);
+ });
+ core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
metadata.process_id = system.CurrentProcess()->GetProcessID();
metadata.title_id = system.CurrentProcess()->GetTitleID();
- const auto& vm_manager = system.CurrentProcess()->VMManager();
- metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
- metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
- vm_manager.GetAddressSpaceSize()};
- metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
+ const auto& page_table = system.CurrentProcess()->PageTable();
+ metadata.heap_extents = {
+ .base = page_table.GetHeapRegionStart(),
+ .size = page_table.GetHeapRegionSize(),
+ };
+
+ metadata.address_space_extents = {
+ .base = page_table.GetAddressSpaceStart(),
+ .size = page_table.GetAddressSpaceSize(),
+ };
+
+ metadata.alias_extents = {
+ .base = page_table.GetAliasCodeRegionStart(),
+ .size = page_table.GetAliasCodeRegionSize(),
+ };
is_pending_reload.exchange(true);
}
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
- metadata.main_nso_extents = {main_region_begin, main_region_size};
+ metadata.main_nso_extents = {
+ .base = main_region_begin,
+ .size = main_region_size,
+ };
}
void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
@@ -214,7 +229,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
-void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
+void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats);
}
@@ -227,7 +242,7 @@ void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
vm.Execute(metadata);
- core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
+ core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
}
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index 3d6b2298a..a31002346 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -5,6 +5,7 @@
#pragma once
#include <atomic>
+#include <chrono>
#include <memory>
#include <vector>
#include "common/common_types.h"
@@ -20,7 +21,7 @@ class CoreTiming;
struct EventType;
} // namespace Core::Timing
-namespace Memory {
+namespace Core::Memory {
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
public:
@@ -46,8 +47,7 @@ class CheatParser {
public:
virtual ~CheatParser();
- virtual std::vector<CheatEntry> Parse(const Core::System& system,
- std::string_view data) const = 0;
+ [[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
};
// CheatParser implementation that parses text files
@@ -55,7 +55,7 @@ class TextCheatParser final : public CheatParser {
public:
~TextCheatParser() override;
- std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
+ [[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
};
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
@@ -71,7 +71,7 @@ public:
void Reload(std::vector<CheatEntry> cheats);
private:
- void FrameCallback(u64 userdata, s64 cycles_late);
+ void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
DmntCheatVm vm;
CheatProcessMetadata metadata;
@@ -84,4 +84,4 @@ private:
Core::System& system;
};
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h
index bf68fa0fe..5e60733dc 100644
--- a/src/core/memory/dmnt_cheat_types.h
+++ b/src/core/memory/dmnt_cheat_types.h
@@ -26,7 +26,7 @@
#include "common/common_types.h"
-namespace Memory {
+namespace Core::Memory {
struct MemoryRegionExtents {
u64 base{};
@@ -55,4 +55,4 @@ struct CheatEntry {
CheatDefinition definition{};
};
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
index 4f4fa5099..48be80c12 100644
--- a/src/core/memory/dmnt_cheat_vm.cpp
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -27,7 +27,7 @@
#include "core/memory/dmnt_cheat_types.h"
#include "core/memory/dmnt_cheat_vm.h"
-namespace Memory {
+namespace Core::Memory {
DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
@@ -55,7 +55,7 @@ void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
- } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) {
+ } else if (std::holds_alternative<EndConditionalOpcode>(opcode.opcode)) {
callbacks->CommandLog("Opcode: End Conditional");
} else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
if (ctrl_loop->start_loop) {
@@ -190,6 +190,15 @@ void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
callbacks->CommandLog(
fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
}
+ } else if (auto rw_static_reg = std::get_if<ReadWriteStaticRegisterOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Read/Write Static Register");
+ if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
+ callbacks->CommandLog("Op Type: ReadStaticRegister");
+ } else {
+ callbacks->CommandLog("Op Type: WriteStaticRegister");
+ }
+ callbacks->CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx));
+ callbacks->CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx));
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Debug Log");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
@@ -304,30 +313,32 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
switch (opcode_type) {
case CheatVmOpcodeType::StoreStatic: {
- StoreStaticOpcode store_static{};
// 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
// Read additional words.
const u32 second_dword = GetNextDword();
- store_static.bit_width = (first_dword >> 24) & 0xF;
- store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- store_static.offset_register = ((first_dword >> 16) & 0xF);
- store_static.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- store_static.value = GetNextVmInt(store_static.bit_width);
- opcode.opcode = store_static;
+ const u32 bit_width = (first_dword >> 24) & 0xF;
+
+ opcode.opcode = StoreStaticOpcode{
+ .bit_width = bit_width,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .offset_register = (first_dword >> 16) & 0xF,
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ .value = GetNextVmInt(bit_width),
+ };
} break;
case CheatVmOpcodeType::BeginConditionalBlock: {
- BeginConditionalOpcode begin_cond{};
// 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
// Read additional words.
const u32 second_dword = GetNextDword();
- begin_cond.bit_width = (first_dword >> 24) & 0xF;
- begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
- begin_cond.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- begin_cond.value = GetNextVmInt(begin_cond.bit_width);
- opcode.opcode = begin_cond;
+ const u32 bit_width = (first_dword >> 24) & 0xF;
+
+ opcode.opcode = BeginConditionalOpcode{
+ .bit_width = bit_width,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ .value = GetNextVmInt(bit_width),
+ };
} break;
case CheatVmOpcodeType::EndConditionalBlock: {
// 20000000
@@ -335,12 +346,14 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = EndConditionalOpcode{};
} break;
case CheatVmOpcodeType::ControlLoop: {
- ControlLoopOpcode ctrl_loop{};
// 300R0000 VVVVVVVV
// 310R0000
// Parse register, whether loop start or loop end.
- ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
- ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
+ ControlLoopOpcode ctrl_loop{
+ .start_loop = ((first_dword >> 24) & 0xF) == 0,
+ .reg_index = (first_dword >> 20) & 0xF,
+ .num_iters = 0,
+ };
// Read number of iters if loop start.
if (ctrl_loop.start_loop) {
@@ -349,65 +362,65 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = ctrl_loop;
} break;
case CheatVmOpcodeType::LoadRegisterStatic: {
- LoadRegisterStaticOpcode ldr_static{};
// 400R0000 VVVVVVVV VVVVVVVV
// Read additional words.
- ldr_static.reg_index = ((first_dword >> 16) & 0xF);
- ldr_static.value =
- (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
- opcode.opcode = ldr_static;
+ opcode.opcode = LoadRegisterStaticOpcode{
+ .reg_index = (first_dword >> 16) & 0xF,
+ .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::LoadRegisterMemory: {
- LoadRegisterMemoryOpcode ldr_memory{};
// 5TMRI0AA AAAAAAAA
// Read additional words.
const u32 second_dword = GetNextDword();
- ldr_memory.bit_width = (first_dword >> 24) & 0xF;
- ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
- ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
- ldr_memory.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- opcode.opcode = ldr_memory;
+ opcode.opcode = LoadRegisterMemoryOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .reg_index = ((first_dword >> 16) & 0xF),
+ .load_from_reg = ((first_dword >> 12) & 0xF) != 0,
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ };
} break;
case CheatVmOpcodeType::StoreStaticToAddress: {
- StoreStaticToAddressOpcode str_static{};
// 6T0RIor0 VVVVVVVV VVVVVVVV
// Read additional words.
- str_static.bit_width = (first_dword >> 24) & 0xF;
- str_static.reg_index = ((first_dword >> 16) & 0xF);
- str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
- str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
- str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
- str_static.value =
- (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
- opcode.opcode = str_static;
+ opcode.opcode = StoreStaticToAddressOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .reg_index = (first_dword >> 16) & 0xF,
+ .increment_reg = ((first_dword >> 12) & 0xF) != 0,
+ .add_offset_reg = ((first_dword >> 8) & 0xF) != 0,
+ .offset_reg_index = (first_dword >> 4) & 0xF,
+ .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::PerformArithmeticStatic: {
- PerformArithmeticStaticOpcode perform_math_static{};
// 7T0RC000 VVVVVVVV
// Read additional words.
- perform_math_static.bit_width = (first_dword >> 24) & 0xF;
- perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
- perform_math_static.math_type =
- static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF);
- perform_math_static.value = GetNextDword();
- opcode.opcode = perform_math_static;
+ opcode.opcode = PerformArithmeticStaticOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .reg_index = ((first_dword >> 16) & 0xF),
+ .math_type = static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF),
+ .value = GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
- BeginKeypressConditionalOpcode begin_keypress_cond{};
// 8kkkkkkk
// Just parse the mask.
- begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
+ opcode.opcode = BeginKeypressConditionalOpcode{
+ .key_mask = first_dword & 0x0FFFFFFF,
+ };
} break;
case CheatVmOpcodeType::PerformArithmeticRegister: {
- PerformArithmeticRegisterOpcode perform_math_reg{};
// 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
- perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
- perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF);
- perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
- perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
- perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
+ PerformArithmeticRegisterOpcode perform_math_reg{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF),
+ .dst_reg_index = (first_dword >> 16) & 0xF,
+ .src_reg_1_index = (first_dword >> 12) & 0xF,
+ .src_reg_2_index = 0,
+ .has_immediate = ((first_dword >> 8) & 0xF) != 0,
+ .value = {},
+ };
if (perform_math_reg.has_immediate) {
perform_math_reg.src_reg_2_index = 0;
perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width);
@@ -417,7 +430,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = perform_math_reg;
} break;
case CheatVmOpcodeType::StoreRegisterToAddress: {
- StoreRegisterToAddressOpcode str_register{};
// ATSRIOxa (aaaaaaaa)
// A = opcode 10
// T = bit width
@@ -429,20 +441,23 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// Relative Address
// x = offset register (for offset type 1), memory type (for offset type 3)
// a = relative address (for offset type 2+3)
- str_register.bit_width = (first_dword >> 24) & 0xF;
- str_register.str_reg_index = ((first_dword >> 20) & 0xF);
- str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
- str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
- str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF));
- str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
+ StoreRegisterToAddressOpcode str_register{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .str_reg_index = (first_dword >> 20) & 0xF,
+ .addr_reg_index = (first_dword >> 16) & 0xF,
+ .increment_reg = ((first_dword >> 12) & 0xF) != 0,
+ .ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)),
+ .mem_type = MemoryAccessType::MainNso,
+ .ofs_reg_index = (first_dword >> 4) & 0xF,
+ .rel_address = 0,
+ };
switch (str_register.ofs_type) {
case StoreRegisterOffsetType::None:
case StoreRegisterOffsetType::Reg:
// Nothing more to do
break;
case StoreRegisterOffsetType::Imm:
- str_register.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case StoreRegisterOffsetType::MemReg:
str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
@@ -450,8 +465,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
case StoreRegisterOffsetType::MemImm:
case StoreRegisterOffsetType::MemImmReg:
str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- str_register.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
default:
str_register.ofs_type = StoreRegisterOffsetType::None;
@@ -460,7 +474,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = str_register;
} break;
case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
- BeginRegisterConditionalOpcode begin_reg_cond{};
// C0TcSX##
// C0TcS0Ma aaaaaaaa
// C0TcS1Mr
@@ -482,11 +495,19 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// r = offset register.
// X = other register.
// V = value.
- begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
- begin_reg_cond.cond_type =
- static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
- begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
- begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
+
+ BeginRegisterConditionalOpcode begin_reg_cond{
+ .bit_width = (first_dword >> 20) & 0xF,
+ .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
+ .val_reg_index = (first_dword >> 12) & 0xF,
+ .comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF),
+ .mem_type = MemoryAccessType::MainNso,
+ .addr_reg_index = 0,
+ .other_reg_index = 0,
+ .ofs_reg_index = 0,
+ .rel_address = 0,
+ .value = {},
+ };
switch (begin_reg_cond.comp_type) {
case CompareRegisterValueType::StaticValue:
@@ -498,26 +519,25 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
case CompareRegisterValueType::MemoryRelAddr:
begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
begin_reg_cond.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case CompareRegisterValueType::MemoryOfsReg:
begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
break;
case CompareRegisterValueType::RegisterRelAddr:
- begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
+ begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF;
begin_reg_cond.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case CompareRegisterValueType::RegisterOfsReg:
- begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
- begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
+ begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF;
+ begin_reg_cond.ofs_reg_index = first_dword & 0xF;
break;
}
opcode.opcode = begin_reg_cond;
} break;
case CheatVmOpcodeType::SaveRestoreRegister: {
- SaveRestoreRegisterOpcode save_restore_reg{};
// C10D0Sx0
// C1 = opcode 0xC1
// D = destination index.
@@ -525,26 +545,37 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
// a register.
// NOTE: If we add more save slots later, current encoding is backwards compatible.
- save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
- save_restore_reg.src_index = (first_dword >> 8) & 0xF;
- save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
- opcode.opcode = save_restore_reg;
+ opcode.opcode = SaveRestoreRegisterOpcode{
+ .dst_index = (first_dword >> 16) & 0xF,
+ .src_index = (first_dword >> 8) & 0xF,
+ .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF),
+ };
} break;
case CheatVmOpcodeType::SaveRestoreRegisterMask: {
- SaveRestoreRegisterMaskOpcode save_restore_regmask{};
// C2x0XXXX
// C2 = opcode 0xC2
// x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring.
// X = 16-bit bitmask, bit i --> save or restore register i.
- save_restore_regmask.op_type =
- static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF);
+ SaveRestoreRegisterMaskOpcode save_restore_regmask{
+ .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF),
+ .should_operate = {},
+ };
for (std::size_t i = 0; i < NumRegisters; i++) {
- save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
+ save_restore_regmask.should_operate[i] = (first_dword & (1U << i)) != 0;
}
opcode.opcode = save_restore_regmask;
} break;
+ case CheatVmOpcodeType::ReadWriteStaticRegister: {
+ // C3000XXx
+ // C3 = opcode 0xC3.
+ // XX = static register index.
+ // x = register index.
+ opcode.opcode = ReadWriteStaticRegisterOpcode{
+ .static_idx = (first_dword >> 4) & 0xFF,
+ .idx = first_dword & 0xF,
+ };
+ } break;
case CheatVmOpcodeType::DebugLog: {
- DebugLogOpcode debug_log{};
// FFFTIX##
// FFFTI0Ma aaaaaaaa
// FFFTI1Mr
@@ -563,31 +594,36 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// a = relative address.
// r = offset register.
// X = value register.
- debug_log.bit_width = (first_dword >> 16) & 0xF;
- debug_log.log_id = ((first_dword >> 12) & 0xF);
- debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF);
+ DebugLogOpcode debug_log{
+ .bit_width = (first_dword >> 16) & 0xF,
+ .log_id = (first_dword >> 12) & 0xF,
+ .val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF),
+ .mem_type = MemoryAccessType::MainNso,
+ .addr_reg_index = 0,
+ .val_reg_index = 0,
+ .ofs_reg_index = 0,
+ .rel_address = 0,
+ };
switch (debug_log.val_type) {
case DebugLogValueType::RegisterValue:
- debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
+ debug_log.val_reg_index = (first_dword >> 4) & 0xF;
break;
case DebugLogValueType::MemoryRelAddr:
debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- debug_log.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case DebugLogValueType::MemoryOfsReg:
debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- debug_log.ofs_reg_index = (first_dword & 0xF);
+ debug_log.ofs_reg_index = first_dword & 0xF;
break;
case DebugLogValueType::RegisterRelAddr:
- debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
- debug_log.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ debug_log.addr_reg_index = (first_dword >> 4) & 0xF;
+ debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case DebugLogValueType::RegisterOfsReg:
- debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
- debug_log.ofs_reg_index = (first_dword & 0xF);
+ debug_log.addr_reg_index = (first_dword >> 4) & 0xF;
+ debug_log.ofs_reg_index = first_dword & 0xF;
break;
}
opcode.opcode = debug_log;
@@ -666,6 +702,7 @@ void DmntCheatVm::ResetState() {
registers.fill(0);
saved_values.fill(0);
loop_tops.fill(0);
+ static_registers.fill(0);
instruction_ptr = 0;
condition_depth = 0;
decode_success = true;
@@ -779,7 +816,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
if (!cond_met) {
SkipConditionalBlock();
}
- } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
+ } else if (std::holds_alternative<EndConditionalOpcode>(cur_opcode.opcode)) {
// Decrement the condition depth.
// We will assume, graciously, that mismatched conditional block ends are a nop.
if (condition_depth > 0) {
@@ -1152,6 +1189,15 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
}
}
+ } else if (auto rw_static_reg =
+ std::get_if<ReadWriteStaticRegisterOpcode>(&cur_opcode.opcode)) {
+ if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
+ // Load a register with a static register.
+ registers[rw_static_reg->idx] = static_registers[rw_static_reg->static_idx];
+ } else {
+ // Store a register to a static register.
+ static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
+ }
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
// Read value from memory.
u64 log_value = 0;
@@ -1209,4 +1255,4 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
}
-} // namespace Memory
+} // namespace Core::Memory
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h
index c36212cf1..21b86b72c 100644
--- a/src/core/memory/dmnt_cheat_vm.h
+++ b/src/core/memory/dmnt_cheat_vm.h
@@ -30,7 +30,7 @@
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"
-namespace Memory {
+namespace Core::Memory {
enum class CheatVmOpcodeType : u32 {
StoreStatic = 0,
@@ -56,6 +56,7 @@ enum class CheatVmOpcodeType : u32 {
BeginRegisterConditionalBlock = 0xC0,
SaveRestoreRegister = 0xC1,
SaveRestoreRegisterMask = 0xC2,
+ ReadWriteStaticRegister = 0xC3,
// This is a meta entry, and not a real opcode.
// This is to facilitate multi-nybble instruction decoding.
@@ -237,6 +238,11 @@ struct SaveRestoreRegisterMaskOpcode {
std::array<bool, 0x10> should_operate{};
};
+struct ReadWriteStaticRegisterOpcode {
+ u32 static_idx{};
+ u32 idx{};
+};
+
struct DebugLogOpcode {
u32 bit_width{};
u32 log_id{};
@@ -259,7 +265,8 @@ struct CheatVmOpcode {
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
- SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction>
+ SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, DebugLogOpcode,
+ UnrecognizedInstruction>
opcode{};
};
@@ -281,6 +288,10 @@ public:
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
static constexpr std::size_t NumRegisters = 0x10;
+ static constexpr std::size_t NumReadableStaticRegisters = 0x80;
+ static constexpr std::size_t NumWritableStaticRegisters = 0x80;
+ static constexpr std::size_t NumStaticRegisters =
+ NumReadableStaticRegisters + NumWritableStaticRegisters;
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks);
~DmntCheatVm();
@@ -302,6 +313,7 @@ private:
std::array<u32, MaximumProgramOpcodeCount> program{};
std::array<u64, NumRegisters> registers{};
std::array<u64, NumRegisters> saved_values{};
+ std::array<u64, NumStaticRegisters> static_registers{};
std::array<std::size_t, NumRegisters> loop_tops{};
bool DecodeNextOpcode(CheatVmOpcode& out);
@@ -318,4 +330,4 @@ private:
MemoryAccessType mem_type, u64 rel_address);
};
-}; // namespace Memory
+}; // namespace Core::Memory
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
new file mode 100644
index 000000000..5ef2e8511
--- /dev/null
+++ b/src/core/network/network.cpp
@@ -0,0 +1,654 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#ifdef _WIN32
+#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
+#include <winsock2.h>
+#elif __unix__
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#else
+#error "Unimplemented platform"
+#endif
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/network/network.h"
+#include "core/network/sockets.h"
+
+namespace Network {
+
+namespace {
+
+#ifdef _WIN32
+
+using socklen_t = int;
+
+void Initialize() {
+ WSADATA wsa_data;
+ (void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
+}
+
+void Finalize() {
+ WSACleanup();
+}
+
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+ auto& bytes = addr.S_un.S_un_b;
+ return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
+}
+
+sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
+ sockaddr_in result;
+
+#ifdef __unix__
+ result.sin_len = sizeof(result);
+#endif
+
+ switch (static_cast<Domain>(input.family)) {
+ case Domain::INET:
+ result.sin_family = AF_INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family));
+ result.sin_family = AF_INET;
+ break;
+ }
+
+ result.sin_port = htons(input.portno);
+
+ auto& ip = result.sin_addr.S_un.S_un_b;
+ ip.s_b1 = input.ip[0];
+ ip.s_b2 = input.ip[1];
+ ip.s_b3 = input.ip[2];
+ ip.s_b4 = input.ip[3];
+
+ sockaddr addr;
+ std::memcpy(&addr, &result, sizeof(addr));
+ return addr;
+}
+
+LINGER MakeLinger(bool enable, u32 linger_value) {
+ ASSERT(linger_value <= std::numeric_limits<u_short>::max());
+
+ LINGER value;
+ value.l_onoff = enable ? 1 : 0;
+ value.l_linger = static_cast<u_short>(linger_value);
+ return value;
+}
+
+int LastError() {
+ return WSAGetLastError();
+}
+
+bool EnableNonBlock(SOCKET fd, bool enable) {
+ u_long value = enable ? 1 : 0;
+ return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
+}
+
+#elif __unix__ // ^ _WIN32 v __unix__
+
+using SOCKET = int;
+using WSAPOLLFD = pollfd;
+using ULONG = u64;
+
+constexpr SOCKET INVALID_SOCKET = -1;
+constexpr SOCKET SOCKET_ERROR = -1;
+
+constexpr int WSAEWOULDBLOCK = EAGAIN;
+constexpr int WSAENOTCONN = ENOTCONN;
+
+constexpr int SD_RECEIVE = SHUT_RD;
+constexpr int SD_SEND = SHUT_WR;
+constexpr int SD_BOTH = SHUT_RDWR;
+
+void Initialize() {}
+
+void Finalize() {}
+
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+ const u32 bytes = addr.s_addr;
+ return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
+ static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
+}
+
+sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
+ sockaddr_in result;
+
+ switch (static_cast<Domain>(input.family)) {
+ case Domain::INET:
+ result.sin_family = AF_INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family));
+ result.sin_family = AF_INET;
+ break;
+ }
+
+ result.sin_port = htons(input.portno);
+
+ result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24;
+
+ sockaddr addr;
+ std::memcpy(&addr, &result, sizeof(addr));
+ return addr;
+}
+
+int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
+ return poll(fds, static_cast<nfds_t>(nfds), timeout);
+}
+
+int closesocket(SOCKET fd) {
+ return close(fd);
+}
+
+linger MakeLinger(bool enable, u32 linger_value) {
+ linger value;
+ value.l_onoff = enable ? 1 : 0;
+ value.l_linger = linger_value;
+ return value;
+}
+
+int LastError() {
+ return errno;
+}
+
+bool EnableNonBlock(int fd, bool enable) {
+ int flags = fcntl(fd, F_GETFD);
+ if (flags == -1) {
+ return false;
+ }
+ if (enable) {
+ flags |= O_NONBLOCK;
+ } else {
+ flags &= ~O_NONBLOCK;
+ }
+ return fcntl(fd, F_SETFD, flags) == 0;
+}
+
+#endif
+
+int TranslateDomain(Domain domain) {
+ switch (domain) {
+ case Domain::INET:
+ return AF_INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ return 0;
+ }
+}
+
+int TranslateType(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return SOCK_STREAM;
+ case Type::DGRAM:
+ return SOCK_DGRAM;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ return 0;
+ }
+}
+
+int TranslateProtocol(Protocol protocol) {
+ switch (protocol) {
+ case Protocol::TCP:
+ return IPPROTO_TCP;
+ case Protocol::UDP:
+ return IPPROTO_UDP;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol));
+ return 0;
+ }
+}
+
+SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
+ sockaddr_in input;
+ std::memcpy(&input, &input_, sizeof(input));
+
+ SockAddrIn result;
+
+ switch (input.sin_family) {
+ case AF_INET:
+ result.family = Domain::INET;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family);
+ result.family = Domain::INET;
+ break;
+ }
+
+ result.portno = ntohs(input.sin_port);
+
+ result.ip = TranslateIPv4(input.sin_addr);
+
+ return result;
+}
+
+u16 TranslatePollEvents(u32 events) {
+ u32 result = 0;
+
+ if ((events & POLL_IN) != 0) {
+ events &= ~POLL_IN;
+ result |= POLLIN;
+ }
+ if ((events & POLL_PRI) != 0) {
+ events &= ~POLL_PRI;
+#ifdef _WIN32
+ LOG_WARNING(Service, "Winsock doesn't support POLLPRI");
+#else
+ result |= POLL_PRI;
+#endif
+ }
+ if ((events & POLL_OUT) != 0) {
+ events &= ~POLL_OUT;
+ result |= POLLOUT;
+ }
+
+ UNIMPLEMENTED_IF_MSG(events != 0, "Unhandled guest events=0x{:x}", events);
+
+ return static_cast<u16>(result);
+}
+
+u16 TranslatePollRevents(u32 revents) {
+ u32 result = 0;
+ const auto translate = [&result, &revents](u32 host, u32 guest) {
+ if ((revents & host) != 0) {
+ revents &= ~host;
+ result |= guest;
+ }
+ };
+
+ translate(POLLIN, POLL_IN);
+ translate(POLLPRI, POLL_PRI);
+ translate(POLLOUT, POLL_OUT);
+ translate(POLLERR, POLL_ERR);
+ translate(POLLHUP, POLL_HUP);
+
+ UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
+
+ return static_cast<u16>(result);
+}
+
+template <typename T>
+Errno SetSockOpt(SOCKET fd, int option, T value) {
+ const int result =
+ setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
+ if (result != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+}
+
+} // Anonymous namespace
+
+NetworkInstance::NetworkInstance() {
+ Initialize();
+}
+
+NetworkInstance::~NetworkInstance() {
+ Finalize();
+}
+
+std::pair<IPv4Address, Errno> GetHostIPv4Address() {
+ std::array<char, 256> name{};
+ if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) {
+ UNIMPLEMENTED_MSG("Unhandled gethostname error");
+ return {IPv4Address{}, Errno::SUCCESS};
+ }
+
+ hostent* const ent = gethostbyname(name.data());
+ if (!ent) {
+ UNIMPLEMENTED_MSG("Unhandled gethostbyname error");
+ return {IPv4Address{}, Errno::SUCCESS};
+ }
+ if (ent->h_addr_list == nullptr) {
+ UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list");
+ return {IPv4Address{}, Errno::SUCCESS};
+ }
+ if (ent->h_length != sizeof(in_addr)) {
+ UNIMPLEMENTED_MSG("Unexpected size={} in hostent->h_length", ent->h_length);
+ }
+
+ in_addr addr;
+ std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr));
+ return {TranslateIPv4(addr), Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
+ const size_t num = pollfds.size();
+
+ std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
+ std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
+ WSAPOLLFD result;
+ result.fd = fd.socket->fd;
+ result.events = TranslatePollEvents(fd.events);
+ result.revents = 0;
+ return result;
+ });
+
+ const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
+ if (result == 0) {
+ ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
+ [](WSAPOLLFD fd) { return fd.revents == 0; }));
+ return {0, Errno::SUCCESS};
+ }
+
+ for (size_t i = 0; i < num; ++i) {
+ pollfds[i].revents = TranslatePollRevents(static_cast<u32>(host_pollfds[i].revents));
+ }
+
+ if (result > 0) {
+ return {result, Errno::SUCCESS};
+ }
+
+ ASSERT(result == SOCKET_ERROR);
+
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {-1, Errno::SUCCESS};
+}
+
+Socket::~Socket() {
+ if (fd == INVALID_SOCKET) {
+ return;
+ }
+ (void)closesocket(fd);
+ fd = INVALID_SOCKET;
+}
+
+Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {}
+
+Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
+ fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
+ if (fd != INVALID_SOCKET) {
+ return Errno::SUCCESS;
+ }
+
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+}
+
+std::pair<Socket::AcceptResult, Errno> Socket::Accept() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ const SOCKET new_socket = accept(fd, &addr, &addrlen);
+
+ if (new_socket == INVALID_SOCKET) {
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {AcceptResult{}, Errno::SUCCESS};
+ }
+
+ AcceptResult result;
+ result.socket = std::make_unique<Socket>();
+ result.socket->fd = new_socket;
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+ result.sockaddr_in = TranslateToSockAddrIn(addr);
+
+ return {std::move(result), Errno::SUCCESS};
+}
+
+Errno Socket::Connect(SockAddrIn addr_in) {
+ const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in);
+ if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ switch (const int ec = LastError()) {
+ case WSAEWOULDBLOCK:
+ LOG_DEBUG(Service, "EAGAIN generated");
+ return Errno::AGAIN;
+ default:
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+ }
+}
+
+std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) {
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {SockAddrIn{}, Errno::SUCCESS};
+ }
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+ return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
+}
+
+std::pair<SockAddrIn, Errno> Socket::GetSockName() {
+ sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) {
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {SockAddrIn{}, Errno::SUCCESS};
+ }
+
+ ASSERT(addrlen == sizeof(sockaddr_in));
+ return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
+}
+
+Errno Socket::Bind(SockAddrIn addr) {
+ const sockaddr addr_in = TranslateFromSockAddrIn(addr);
+ if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+}
+
+Errno Socket::Listen(s32 backlog) {
+ if (listen(fd, backlog) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+}
+
+Errno Socket::Shutdown(ShutdownHow how) {
+ int host_how = 0;
+ switch (how) {
+ case ShutdownHow::RD:
+ host_how = SD_RECEIVE;
+ break;
+ case ShutdownHow::WR:
+ host_how = SD_SEND;
+ break;
+ case ShutdownHow::RDWR:
+ host_how = SD_BOTH;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented flag how={}", static_cast<int>(how));
+ return Errno::SUCCESS;
+ }
+ if (shutdown(fd, host_how) != SOCKET_ERROR) {
+ return Errno::SUCCESS;
+ }
+
+ switch (const int ec = LastError()) {
+ case WSAENOTCONN:
+ LOG_ERROR(Service, "ENOTCONN generated");
+ return Errno::NOTCONN;
+ default:
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+ }
+}
+
+std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ const auto result =
+ recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ switch (const int ec = LastError()) {
+ case WSAEWOULDBLOCK:
+ LOG_DEBUG(Service, "EAGAIN generated");
+ return {-1, Errno::AGAIN};
+ case WSAENOTCONN:
+ LOG_ERROR(Service, "ENOTCONN generated");
+ return {-1, Errno::NOTCONN};
+ default:
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {0, Errno::SUCCESS};
+ }
+}
+
+std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
+ ASSERT(flags == 0);
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ sockaddr addr_in{};
+ socklen_t addrlen = sizeof(addr_in);
+ socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
+ sockaddr* const p_addr_in = addr ? &addr_in : nullptr;
+
+ const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
+ static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
+ if (result != SOCKET_ERROR) {
+ if (addr) {
+ ASSERT(addrlen == sizeof(addr_in));
+ *addr = TranslateToSockAddrIn(addr_in);
+ }
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ switch (const int ec = LastError()) {
+ case WSAEWOULDBLOCK:
+ LOG_DEBUG(Service, "EAGAIN generated");
+ return {-1, Errno::AGAIN};
+ case WSAENOTCONN:
+ LOG_ERROR(Service, "ENOTCONN generated");
+ return {-1, Errno::NOTCONN};
+ default:
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {-1, Errno::SUCCESS};
+ }
+}
+
+std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) {
+ ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+ ASSERT(flags == 0);
+
+ const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ const int ec = LastError();
+ switch (ec) {
+ case WSAEWOULDBLOCK:
+ LOG_DEBUG(Service, "EAGAIN generated");
+ return {-1, Errno::AGAIN};
+ case WSAENOTCONN:
+ LOG_ERROR(Service, "ENOTCONN generated");
+ return {-1, Errno::NOTCONN};
+ default:
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {-1, Errno::SUCCESS};
+ }
+}
+
+std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message,
+ const SockAddrIn* addr) {
+ ASSERT(flags == 0);
+
+ const sockaddr* to = nullptr;
+ const int tolen = addr ? 0 : sizeof(sockaddr);
+ sockaddr host_addr_in;
+
+ if (addr) {
+ host_addr_in = TranslateFromSockAddrIn(*addr);
+ to = &host_addr_in;
+ }
+
+ const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0, to, tolen);
+ if (result != SOCKET_ERROR) {
+ return {static_cast<s32>(result), Errno::SUCCESS};
+ }
+
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return {-1, Errno::SUCCESS};
+}
+
+Errno Socket::Close() {
+ [[maybe_unused]] const int result = closesocket(fd);
+ ASSERT(result == 0);
+ fd = INVALID_SOCKET;
+
+ return Errno::SUCCESS;
+}
+
+Errno Socket::SetLinger(bool enable, u32 linger) {
+ return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger));
+}
+
+Errno Socket::SetReuseAddr(bool enable) {
+ return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
+}
+
+Errno Socket::SetBroadcast(bool enable) {
+ return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
+}
+
+Errno Socket::SetSndBuf(u32 value) {
+ return SetSockOpt(fd, SO_SNDBUF, value);
+}
+
+Errno Socket::SetRcvBuf(u32 value) {
+ return SetSockOpt(fd, SO_RCVBUF, value);
+}
+
+Errno Socket::SetSndTimeo(u32 value) {
+ return SetSockOpt(fd, SO_SNDTIMEO, value);
+}
+
+Errno Socket::SetRcvTimeo(u32 value) {
+ return SetSockOpt(fd, SO_RCVTIMEO, value);
+}
+
+Errno Socket::SetNonBlock(bool enable) {
+ if (EnableNonBlock(fd, enable)) {
+ return Errno::SUCCESS;
+ }
+ const int ec = LastError();
+ UNREACHABLE_MSG("Unhandled host socket error={}", ec);
+ return Errno::SUCCESS;
+}
+
+bool Socket::IsOpened() const {
+ return fd != INVALID_SOCKET;
+}
+
+} // namespace Network
diff --git a/src/core/network/network.h b/src/core/network/network.h
new file mode 100644
index 000000000..0622e4593
--- /dev/null
+++ b/src/core/network/network.h
@@ -0,0 +1,87 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <utility>
+
+#include "common/common_types.h"
+
+namespace Network {
+
+class Socket;
+
+/// Error code for network functions
+enum class Errno {
+ SUCCESS,
+ BADF,
+ INVAL,
+ MFILE,
+ NOTCONN,
+ AGAIN,
+};
+
+/// Address families
+enum class Domain {
+ INET, ///< Address family for IPv4
+};
+
+/// Socket types
+enum class Type {
+ STREAM,
+ DGRAM,
+ RAW,
+ SEQPACKET,
+};
+
+/// Protocol values for sockets
+enum class Protocol {
+ ICMP,
+ TCP,
+ UDP,
+};
+
+/// Shutdown mode
+enum class ShutdownHow {
+ RD,
+ WR,
+ RDWR,
+};
+
+/// Array of IPv4 address
+using IPv4Address = std::array<u8, 4>;
+
+/// Cross-platform sockaddr structure
+struct SockAddrIn {
+ Domain family;
+ IPv4Address ip;
+ u16 portno;
+};
+
+/// Cross-platform poll fd structure
+struct PollFD {
+ Socket* socket;
+ u16 events;
+ u16 revents;
+};
+
+constexpr u16 POLL_IN = 1 << 0;
+constexpr u16 POLL_PRI = 1 << 1;
+constexpr u16 POLL_OUT = 1 << 2;
+constexpr u16 POLL_ERR = 1 << 3;
+constexpr u16 POLL_HUP = 1 << 4;
+constexpr u16 POLL_NVAL = 1 << 5;
+
+class NetworkInstance {
+public:
+ explicit NetworkInstance();
+ ~NetworkInstance();
+};
+
+/// @brief Returns host's IPv4 address
+/// @return Pair of an array of human ordered IPv4 address (e.g. 192.168.0.1) and an error code
+std::pair<IPv4Address, Errno> GetHostIPv4Address();
+
+} // namespace Network
diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h
new file mode 100644
index 000000000..7bdff0fe4
--- /dev/null
+++ b/src/core/network/sockets.h
@@ -0,0 +1,94 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <utility>
+
+#if defined(_WIN32)
+#include <winsock.h>
+#elif !defined(__unix__)
+#error "Platform not implemented"
+#endif
+
+#include "common/common_types.h"
+#include "core/network/network.h"
+
+// TODO: C++20 Replace std::vector usages with std::span
+
+namespace Network {
+
+class Socket {
+public:
+ struct AcceptResult {
+ std::unique_ptr<Socket> socket;
+ SockAddrIn sockaddr_in;
+ };
+
+ explicit Socket() = default;
+ ~Socket();
+
+ Socket(const Socket&) = delete;
+ Socket& operator=(const Socket&) = delete;
+
+ Socket(Socket&& rhs) noexcept;
+
+ // Avoid closing sockets implicitly
+ Socket& operator=(Socket&&) noexcept = delete;
+
+ Errno Initialize(Domain domain, Type type, Protocol protocol);
+
+ Errno Close();
+
+ std::pair<AcceptResult, Errno> Accept();
+
+ Errno Connect(SockAddrIn addr_in);
+
+ std::pair<SockAddrIn, Errno> GetPeerName();
+
+ std::pair<SockAddrIn, Errno> GetSockName();
+
+ Errno Bind(SockAddrIn addr);
+
+ Errno Listen(s32 backlog);
+
+ Errno Shutdown(ShutdownHow how);
+
+ std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message);
+
+ std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr);
+
+ std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags);
+
+ std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr);
+
+ Errno SetLinger(bool enable, u32 linger);
+
+ Errno SetReuseAddr(bool enable);
+
+ Errno SetBroadcast(bool enable);
+
+ Errno SetSndBuf(u32 value);
+
+ Errno SetRcvBuf(u32 value);
+
+ Errno SetSndTimeo(u32 value);
+
+ Errno SetRcvTimeo(u32 value);
+
+ Errno SetNonBlock(bool enable);
+
+ bool IsOpened() const;
+
+#if defined(_WIN32)
+ SOCKET fd = INVALID_SOCKET;
+#elif defined(__unix__)
+ int fd = -1;
+#endif
+};
+
+std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
+
+} // namespace Network
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index f1ae9d4df..b93396a80 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -38,11 +38,11 @@ PerfStats::~PerfStats() {
std::ostringstream stream;
std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
std::ostream_iterator<double>(stream, "\n"));
- const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
+ const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
// %F Date format expanded is "%Y-%m-%d"
const std::string filename =
fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
- FileUtil::IOFile file(filename, "w");
+ Common::FS::IOFile file(filename, "w");
file.WriteString(stream.str());
}
@@ -74,15 +74,16 @@ void PerfStats::EndGameFrame() {
game_frames += 1;
}
-double PerfStats::GetMeanFrametime() {
+double PerfStats::GetMeanFrametime() const {
std::lock_guard lock{object_mutex};
if (current_index <= IgnoreFrames) {
return 0;
}
+
const double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
perf_history.begin() + current_index, 0.0);
- return sum / (current_index - IgnoreFrames);
+ return sum / static_cast<double>(current_index - IgnoreFrames);
}
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
@@ -94,12 +95,13 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
- PerfStatsResults results{};
- results.system_fps = static_cast<double>(system_frames) / interval;
- results.game_fps = static_cast<double>(game_frames) / interval;
- results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
- static_cast<double>(system_frames);
- results.emulation_speed = system_us_per_second.count() / 1'000'000.0;
+ const PerfStatsResults results{
+ .system_fps = static_cast<double>(system_frames) / interval,
+ .game_fps = static_cast<double>(game_frames) / interval,
+ .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
+ static_cast<double>(system_frames),
+ .emulation_speed = system_us_per_second.count() / 1'000'000.0,
+ };
// Reset counters
reset_point = now;
@@ -111,7 +113,7 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
return results;
}
-double PerfStats::GetLastFrameTimeScale() {
+double PerfStats::GetLastFrameTimeScale() const {
std::lock_guard lock{object_mutex};
constexpr double FRAME_LENGTH = 1.0 / 60;
@@ -119,13 +121,14 @@ double PerfStats::GetLastFrameTimeScale() {
}
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
- if (!Settings::values.use_frame_limit) {
+ if (!Settings::values.use_frame_limit.GetValue() ||
+ Settings::values.use_multi_core.GetValue()) {
return;
}
auto now = Clock::now();
- const double sleep_scale = Settings::values.frame_limit / 100.0;
+ const double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0;
// Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
// speed percent or it will clamp too much and prevent this from properly limiting to that
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index d9a64f072..69256b960 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -30,7 +30,6 @@ struct PerfStatsResults {
class PerfStats {
public:
explicit PerfStats(u64 title_id);
-
~PerfStats();
using Clock = std::chrono::high_resolution_clock;
@@ -42,18 +41,18 @@ public:
PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
/**
- * Returns the Arthimetic Mean of all frametime values stored in the performance history.
+ * Returns the arithmetic mean of all frametime values stored in the performance history.
*/
- double GetMeanFrametime();
+ double GetMeanFrametime() const;
/**
* Gets the ratio between walltime and the emulated time of the previous system frame. This is
* useful for scaling inputs or outputs moving between the two time domains.
*/
- double GetLastFrameTimeScale();
+ double GetLastFrameTimeScale() const;
private:
- std::mutex object_mutex{};
+ mutable std::mutex object_mutex;
/// Title ID for the game that is running. 0 if there is no game running yet
u64 title_id{0};
@@ -61,7 +60,7 @@ private:
std::size_t current_index{0};
/// Stores an hour of historical frametime data useful for processing and tracking performance
/// regressions with code changes.
- std::array<double, 216000> perf_history = {};
+ std::array<double, 216000> perf_history{};
/// Point when the cumulative counters were reset
Clock::time_point reset_point = Clock::now();
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 85ac81ef7..0becdf642 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -4,11 +4,12 @@
#include <ctime>
#include <fstream>
+#include <iomanip>
#include <fmt/chrono.h>
#include <fmt/format.h>
#include <fmt/ostream.h>
-#include <json.hpp>
+#include <nlohmann/json.hpp>
#include "common/file_util.h"
#include "common/hex_util.h"
@@ -16,17 +17,20 @@
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
#include "core/hle/service/lm/manager.h"
+#include "core/memory.h"
#include "core/reporter.h"
#include "core/settings.h"
namespace {
std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
- return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
- type, title_id, timestamp);
+ return fmt::format("{}{}/{:016X}_{}.json",
+ Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id,
+ timestamp);
}
std::string GetTimestamp() {
@@ -37,13 +41,13 @@ std::string GetTimestamp() {
using namespace nlohmann;
void SaveToFile(json json, const std::string& filename) {
- if (!FileUtil::CreateFullPath(filename)) {
+ if (!Common::FS::CreateFullPath(filename)) {
LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
return;
}
std::ofstream file(
- FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
+ Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault));
file << std::setw(4) << json << std::endl;
}
@@ -108,14 +112,13 @@ json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64
json GetProcessorStateDataAuto(Core::System& system) {
const auto* process{system.CurrentProcess()};
- const auto& vm_manager{process->VMManager()};
auto& arm{system.CurrentArmInterface()};
Core::ARM_Interface::ThreadContext64 context{};
arm.SaveContext(context);
return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
- vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
+ process->PageTable().GetCodeRegionStart(), context.sp, context.pc,
context.pstate, context.cpu_registers);
}
@@ -147,7 +150,8 @@ json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& s
}
template <bool read_value, typename DescriptorType>
-json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer, Memory::Memory& memory) {
+json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer,
+ Core::Memory::Memory& memory) {
auto buffer_out = json::array();
for (const auto& desc : buffer) {
auto entry = json{
@@ -167,7 +171,7 @@ json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer, Memor
return buffer_out;
}
-json GetHLERequestContextData(Kernel::HLERequestContext& ctx, Memory::Memory& memory) {
+json GetHLERequestContextData(Kernel::HLERequestContext& ctx, Core::Memory::Memory& memory) {
json out;
auto cmd_buf = json::array();
diff --git a/src/core/reporter.h b/src/core/reporter.h
index 380941b1b..86d760cf0 100644
--- a/src/core/reporter.h
+++ b/src/core/reporter.h
@@ -56,6 +56,7 @@ public:
enum class PlayReportType {
Old,
+ Old2,
New,
System,
};
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index c1282cb80..aadbc3932 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
+
#include "common/file_util.h"
#include "core/core.h"
#include "core/gdbstub/gdbstub.h"
@@ -11,58 +13,24 @@
namespace Settings {
-namespace NativeButton {
-const std::array<const char*, NumButtons> mapping = {{
- "button_a",
- "button_b",
- "button_x",
- "button_y",
- "button_lstick",
- "button_rstick",
- "button_l",
- "button_r",
- "button_zl",
- "button_zr",
- "button_plus",
- "button_minus",
- "button_dleft",
- "button_dup",
- "button_dright",
- "button_ddown",
- "button_lstick_left",
- "button_lstick_up",
- "button_lstick_right",
- "button_lstick_down",
- "button_rstick_left",
- "button_rstick_up",
- "button_rstick_right",
- "button_rstick_down",
- "button_sl",
- "button_sr",
- "button_home",
- "button_screenshot",
-}};
-}
+Values values = {};
+static bool configuring_global = true;
-namespace NativeAnalog {
-const std::array<const char*, NumAnalogs> mapping = {{
- "lstick",
- "rstick",
-}};
-}
+std::string GetTimeZoneString() {
+ static constexpr std::array timezones{
+ "auto", "default", "CET", "CST6CDT", "Cuba", "EET", "Egypt", "Eire",
+ "EST", "EST5EDT", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0",
+ "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan",
+ "Kwajalein", "Libya", "MET", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT",
+ "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey",
+ "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu",
+ };
-namespace NativeMouseButton {
-const std::array<const char*, NumMouseButtons> mapping = {{
- "left",
- "right",
- "middle",
- "forward",
- "back",
-}};
+ const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue());
+ ASSERT(time_zone_index < timezones.size());
+ return timezones[time_zone_index];
}
-Values values = {};
-
void Apply() {
GDBStub::SetServerPort(values.gdbstub_port);
GDBStub::ToggleServer(values.use_gdbstub);
@@ -75,38 +43,119 @@ void Apply() {
Service::HID::ReloadInputDevices();
}
-template <typename T>
-void LogSetting(const std::string& name, const T& value) {
- LOG_INFO(Config, "{}: {}", name, value);
-}
-
void LogSettings() {
+ const auto log_setting = [](std::string_view name, const auto& value) {
+ LOG_INFO(Config, "{}: {}", name, value);
+ };
+
LOG_INFO(Config, "yuzu Configuration:");
- LogSetting("System_UseDockedMode", Settings::values.use_docked_mode);
- LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0));
- LogSetting("System_CurrentUser", Settings::values.current_user);
- LogSetting("System_LanguageIndex", Settings::values.language_index);
- LogSetting("System_RegionIndex", Settings::values.region_index);
- LogSetting("Core_UseMultiCore", Settings::values.use_multi_core);
- LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor);
- LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit);
- LogSetting("Renderer_FrameLimit", Settings::values.frame_limit);
- LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache);
- LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation);
- LogSetting("Renderer_UseAsynchronousGpuEmulation",
- Settings::values.use_asynchronous_gpu_emulation);
- LogSetting("Renderer_UseVsync", Settings::values.use_vsync);
- LogSetting("Audio_OutputEngine", Settings::values.sink_id);
- LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
- LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
- LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd);
- LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
- LogSetting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
- LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
- LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
- LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
- LogSetting("Services_BCATBackend", Settings::values.bcat_backend);
- LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local);
+ log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
+ log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
+ log_setting("System_CurrentUser", values.current_user);
+ log_setting("System_LanguageIndex", values.language_index.GetValue());
+ log_setting("System_RegionIndex", values.region_index.GetValue());
+ log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
+ log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
+ log_setting("CPU_Accuracy", values.cpu_accuracy);
+ log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue());
+ log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue());
+ log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
+ log_setting("Renderer_UseDiskShaderCache", values.use_disk_shader_cache.GetValue());
+ log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue());
+ log_setting("Renderer_UseAsynchronousGpuEmulation",
+ values.use_asynchronous_gpu_emulation.GetValue());
+ log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue());
+ log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
+ log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue());
+ log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
+ log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
+ log_setting("Audio_OutputEngine", values.sink_id);
+ log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
+ log_setting("Audio_OutputDevice", values.audio_device_id);
+ log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
+ log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
+ log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
+ log_setting("Debugging_UseGdbstub", values.use_gdbstub);
+ log_setting("Debugging_GdbstubPort", values.gdbstub_port);
+ log_setting("Debugging_ProgramArgs", values.program_args);
+ log_setting("Services_BCATBackend", values.bcat_backend);
+ log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local);
+}
+
+bool IsConfiguringGlobal() {
+ return configuring_global;
+}
+
+void SetConfiguringGlobal(bool is_global) {
+ configuring_global = is_global;
+}
+
+bool IsGPULevelExtreme() {
+ return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme;
+}
+
+bool IsGPULevelHigh() {
+ return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme ||
+ values.gpu_accuracy.GetValue() == GPUAccuracy::High;
+}
+
+float Volume() {
+ if (values.audio_muted) {
+ return 0.0f;
+ }
+ return values.volume.GetValue();
+}
+
+void RestoreGlobalState() {
+ // If a game is running, DO NOT restore the global settings state
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ return;
+ }
+
+ // Audio
+ values.enable_audio_stretching.SetGlobal(true);
+ values.volume.SetGlobal(true);
+
+ // Core
+ values.use_multi_core.SetGlobal(true);
+
+ // Renderer
+ values.renderer_backend.SetGlobal(true);
+ values.vulkan_device.SetGlobal(true);
+ values.aspect_ratio.SetGlobal(true);
+ values.max_anisotropy.SetGlobal(true);
+ values.use_frame_limit.SetGlobal(true);
+ values.frame_limit.SetGlobal(true);
+ values.use_disk_shader_cache.SetGlobal(true);
+ values.gpu_accuracy.SetGlobal(true);
+ values.use_asynchronous_gpu_emulation.SetGlobal(true);
+ values.use_nvdec_emulation.SetGlobal(true);
+ values.use_vsync.SetGlobal(true);
+ values.use_assembly_shaders.SetGlobal(true);
+ values.use_asynchronous_shaders.SetGlobal(true);
+ values.use_fast_gpu_time.SetGlobal(true);
+ values.bg_red.SetGlobal(true);
+ values.bg_green.SetGlobal(true);
+ values.bg_blue.SetGlobal(true);
+
+ // System
+ values.language_index.SetGlobal(true);
+ values.region_index.SetGlobal(true);
+ values.time_zone_index.SetGlobal(true);
+ values.rng_seed.SetGlobal(true);
+ values.custom_rtc.SetGlobal(true);
+ values.sound_index.SetGlobal(true);
+
+ // Controls
+ values.players.SetGlobal(true);
+ values.use_docked_mode.SetGlobal(true);
+ values.vibration_enabled.SetGlobal(true);
+ values.motion_enabled.SetGlobal(true);
+}
+
+void Sanitize() {
+ values.use_asynchronous_gpu_emulation.SetValue(
+ values.use_asynchronous_gpu_emulation.GetValue() || values.use_multi_core.GetValue());
}
} // namespace Settings
diff --git a/src/core/settings.h b/src/core/settings.h
index 79ec01731..1143aba5d 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -12,386 +12,171 @@
#include <string>
#include <vector>
#include "common/common_types.h"
+#include "input_common/settings.h"
namespace Settings {
-namespace NativeButton {
-enum Values {
- A,
- B,
- X,
- Y,
- LStick,
- RStick,
- L,
- R,
- ZL,
- ZR,
- Plus,
- Minus,
-
- DLeft,
- DUp,
- DRight,
- DDown,
-
- LStick_Left,
- LStick_Up,
- LStick_Right,
- LStick_Down,
-
- RStick_Left,
- RStick_Up,
- RStick_Right,
- RStick_Down,
-
- SL,
- SR,
-
- Home,
- Screenshot,
-
- NumButtons,
-};
-
-constexpr int BUTTON_HID_BEGIN = A;
-constexpr int BUTTON_NS_BEGIN = Home;
-
-constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
-constexpr int BUTTON_NS_END = NumButtons;
-
-constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
-constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
-
-extern const std::array<const char*, NumButtons> mapping;
-
-} // namespace NativeButton
-
-namespace NativeAnalog {
-enum Values {
- LStick,
- RStick,
-
- NumAnalogs,
+enum class RendererBackend {
+ OpenGL = 0,
+ Vulkan = 1,
};
-constexpr int STICK_HID_BEGIN = LStick;
-constexpr int STICK_HID_END = NumAnalogs;
-constexpr int NUM_STICKS_HID = NumAnalogs;
-
-extern const std::array<const char*, NumAnalogs> mapping;
-} // namespace NativeAnalog
-
-namespace NativeMouseButton {
-enum Values {
- Left,
- Right,
- Middle,
- Forward,
- Back,
-
- NumMouseButtons,
+enum class GPUAccuracy : u32 {
+ Normal = 0,
+ High = 1,
+ Extreme = 2,
};
-constexpr int MOUSE_HID_BEGIN = Left;
-constexpr int MOUSE_HID_END = NumMouseButtons;
-constexpr int NUM_MOUSE_HID = NumMouseButtons;
-
-extern const std::array<const char*, NumMouseButtons> mapping;
-} // namespace NativeMouseButton
-
-namespace NativeKeyboard {
-enum Keys {
- None,
- Error,
-
- A = 4,
- B,
- C,
- D,
- E,
- F,
- G,
- H,
- I,
- J,
- K,
- L,
- M,
- N,
- O,
- P,
- Q,
- R,
- S,
- T,
- U,
- V,
- W,
- X,
- Y,
- Z,
- N1,
- N2,
- N3,
- N4,
- N5,
- N6,
- N7,
- N8,
- N9,
- N0,
- Enter,
- Escape,
- Backspace,
- Tab,
- Space,
- Minus,
- Equal,
- LeftBrace,
- RightBrace,
- Backslash,
- Tilde,
- Semicolon,
- Apostrophe,
- Grave,
- Comma,
- Dot,
- Slash,
- CapsLockKey,
-
- F1,
- F2,
- F3,
- F4,
- F5,
- F6,
- F7,
- F8,
- F9,
- F10,
- F11,
- F12,
-
- SystemRequest,
- ScrollLockKey,
- Pause,
- Insert,
- Home,
- PageUp,
- Delete,
- End,
- PageDown,
- Right,
- Left,
- Down,
- Up,
-
- NumLockKey,
- KPSlash,
- KPAsterisk,
- KPMinus,
- KPPlus,
- KPEnter,
- KP1,
- KP2,
- KP3,
- KP4,
- KP5,
- KP6,
- KP7,
- KP8,
- KP9,
- KP0,
- KPDot,
-
- Key102,
- Compose,
- Power,
- KPEqual,
-
- F13,
- F14,
- F15,
- F16,
- F17,
- F18,
- F19,
- F20,
- F21,
- F22,
- F23,
- F24,
-
- Open,
- Help,
- Properties,
- Front,
- Stop,
- Repeat,
- Undo,
- Cut,
- Copy,
- Paste,
- Find,
- Mute,
- VolumeUp,
- VolumeDown,
- CapsLockActive,
- NumLockActive,
- ScrollLockActive,
- KPComma,
-
- KPLeftParenthesis,
- KPRightParenthesis,
-
- LeftControlKey = 0xE0,
- LeftShiftKey,
- LeftAltKey,
- LeftMetaKey,
- RightControlKey,
- RightShiftKey,
- RightAltKey,
- RightMetaKey,
-
- MediaPlayPause,
- MediaStopCD,
- MediaPrevious,
- MediaNext,
- MediaEject,
- MediaVolumeUp,
- MediaVolumeDown,
- MediaMute,
- MediaWebsite,
- MediaBack,
- MediaForward,
- MediaStop,
- MediaFind,
- MediaScrollUp,
- MediaScrollDown,
- MediaEdit,
- MediaSleep,
- MediaCoffee,
- MediaRefresh,
- MediaCalculator,
-
- NumKeyboardKeys,
+enum class CPUAccuracy {
+ Accurate = 0,
+ Unsafe = 1,
+ DebugMode = 2,
};
-static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
-
-enum Modifiers {
- LeftControl,
- LeftShift,
- LeftAlt,
- LeftMeta,
- RightControl,
- RightShift,
- RightAlt,
- RightMeta,
- CapsLock,
- ScrollLock,
- NumLock,
-
- NumKeyboardMods,
+template <typename Type>
+class Setting final {
+public:
+ Setting() = default;
+ explicit Setting(Type val) : global{val} {}
+ ~Setting() = default;
+ void SetGlobal(bool to_global) {
+ use_global = to_global;
+ }
+ bool UsingGlobal() const {
+ return use_global;
+ }
+ Type GetValue(bool need_global = false) const {
+ if (use_global || need_global) {
+ return global;
+ }
+ return local;
+ }
+ void SetValue(const Type& value) {
+ if (use_global) {
+ global = value;
+ } else {
+ local = value;
+ }
+ }
+
+private:
+ bool use_global = true;
+ Type global{};
+ Type local{};
};
-constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
-constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
-constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
-
-constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
-constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
-constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
-
-} // namespace NativeKeyboard
-
-using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
-using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
-using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
-using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
-using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
-
-constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
-constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
-constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
-constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
-
-enum class ControllerType {
- ProController,
- DualJoycon,
- RightJoycon,
- LeftJoycon,
+/**
+ * The InputSetting class allows for getting a reference to either the global or local members.
+ * This is required as we cannot easily modify the values of user-defined types within containers
+ * using the SetValue() member function found in the Setting class. The primary purpose of this
+ * class is to store an array of 10 PlayerInput structs for both the global and local (per-game)
+ * setting and allows for easily accessing and modifying both settings.
+ */
+template <typename Type>
+class InputSetting final {
+public:
+ InputSetting() = default;
+ explicit InputSetting(Type val) : global{val} {}
+ ~InputSetting() = default;
+ void SetGlobal(bool to_global) {
+ use_global = to_global;
+ }
+ bool UsingGlobal() const {
+ return use_global;
+ }
+ Type& GetValue(bool need_global = false) {
+ if (use_global || need_global) {
+ return global;
+ }
+ return local;
+ }
+
+private:
+ bool use_global = true;
+ Type global{};
+ Type local{};
};
-struct PlayerInput {
- bool connected;
- ControllerType type;
- ButtonsRaw buttons;
- AnalogsRaw analogs;
-
- u32 body_color_right;
- u32 button_color_right;
- u32 body_color_left;
- u32 button_color_left;
+struct TouchFromButtonMap {
+ std::string name;
+ std::vector<std::string> buttons;
};
-struct TouchscreenInput {
- bool enabled;
- std::string device;
+struct Values {
+ // Audio
+ std::string audio_device_id;
+ std::string sink_id;
+ bool audio_muted;
+ Setting<bool> enable_audio_stretching;
+ Setting<float> volume;
- u32 finger;
- u32 diameter_x;
- u32 diameter_y;
- u32 rotation_angle;
-};
+ // Core
+ Setting<bool> use_multi_core;
-enum class NANDTotalSize : u64 {
- S29_1GB = 0x747C00000ULL,
-};
+ // Cpu
+ CPUAccuracy cpu_accuracy;
-enum class NANDUserSize : u64 {
- S26GB = 0x680000000ULL,
-};
+ bool cpuopt_page_tables;
+ bool cpuopt_block_linking;
+ bool cpuopt_return_stack_buffer;
+ bool cpuopt_fast_dispatcher;
+ bool cpuopt_context_elimination;
+ bool cpuopt_const_prop;
+ bool cpuopt_misc_ir;
+ bool cpuopt_reduce_misalign_checks;
-enum class NANDSystemSize : u64 {
- S2_5GB = 0xA0000000,
-};
+ bool cpuopt_unsafe_unfuse_fma;
+ bool cpuopt_unsafe_reduce_fp_error;
-enum class SDMCSize : u64 {
- S1GB = 0x40000000,
- S2GB = 0x80000000,
- S4GB = 0x100000000ULL,
- S8GB = 0x200000000ULL,
- S16GB = 0x400000000ULL,
- S32GB = 0x800000000ULL,
- S64GB = 0x1000000000ULL,
- S128GB = 0x2000000000ULL,
- S256GB = 0x4000000000ULL,
- S1TB = 0x10000000000ULL,
-};
-
-enum class RendererBackend {
- OpenGL = 0,
- Vulkan = 1,
-};
+ // Renderer
+ Setting<RendererBackend> renderer_backend;
+ bool renderer_debug;
+ Setting<int> vulkan_device;
+
+ Setting<u16> resolution_factor{1};
+ Setting<int> aspect_ratio;
+ Setting<int> max_anisotropy;
+ Setting<bool> use_frame_limit;
+ Setting<u16> frame_limit;
+ Setting<bool> use_disk_shader_cache;
+ Setting<GPUAccuracy> gpu_accuracy;
+ Setting<bool> use_asynchronous_gpu_emulation;
+ Setting<bool> use_nvdec_emulation;
+ Setting<bool> use_vsync;
+ Setting<bool> use_assembly_shaders;
+ Setting<bool> use_asynchronous_shaders;
+ Setting<bool> use_fast_gpu_time;
+
+ Setting<float> bg_red;
+ Setting<float> bg_green;
+ Setting<float> bg_blue;
-struct Values {
// System
- bool use_docked_mode;
- std::optional<u32> rng_seed;
+ Setting<std::optional<u32>> rng_seed;
// Measured in seconds since epoch
- std::optional<std::chrono::seconds> custom_rtc;
+ Setting<std::optional<std::chrono::seconds>> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
std::chrono::seconds custom_rtc_differential;
s32 current_user;
- s32 language_index;
- s32 region_index;
- s32 sound_index;
+ Setting<s32> language_index;
+ Setting<s32> region_index;
+ Setting<s32> time_zone_index;
+ Setting<s32> sound_index;
// Controls
- std::array<PlayerInput, 10> players;
+ InputSetting<std::array<PlayerInput, 10>> players;
+
+ Setting<bool> use_docked_mode;
+
+ Setting<bool> vibration_enabled;
+ Setting<bool> enable_accurate_vibrations;
+
+ Setting<bool> motion_enabled;
+ std::string motion_device;
+ std::string udp_input_address;
+ u16 udp_input_port;
+ u8 udp_pad_index;
bool mouse_enabled;
std::string mouse_device;
@@ -405,55 +190,20 @@ struct Values {
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
- std::string motion_device;
TouchscreenInput touchscreen;
- std::atomic_bool is_device_reload_pending{true};
- std::string udp_input_address;
- u16 udp_input_port;
- u8 udp_pad_index;
- // Core
- bool use_multi_core;
+ bool use_touch_from_button;
+ std::string touch_device;
+ int touch_from_button_map_index;
+ std::vector<TouchFromButtonMap> touch_from_button_maps;
+
+ std::atomic_bool is_device_reload_pending{true};
// Data Storage
bool use_virtual_sd;
bool gamecard_inserted;
bool gamecard_current_game;
std::string gamecard_path;
- NANDTotalSize nand_total_size;
- NANDSystemSize nand_system_size;
- NANDUserSize nand_user_size;
- SDMCSize sdmc_size;
-
- // Renderer
- RendererBackend renderer_backend;
- bool renderer_debug;
- int vulkan_device;
-
- float resolution_factor;
- int aspect_ratio;
- int max_anisotropy;
- bool use_frame_limit;
- u16 frame_limit;
- bool use_disk_shader_cache;
- bool use_accurate_gpu_emulation;
- bool use_asynchronous_gpu_emulation;
- bool use_vsync;
- bool force_30fps_mode;
-
- float bg_red;
- float bg_green;
- float bg_blue;
-
- std::string log_filter;
-
- bool use_dev_keys;
-
- // Audio
- std::string sink_id;
- bool enable_audio_stretching;
- std::string audio_device_id;
- float volume;
// Debugging
bool record_frame_times;
@@ -464,8 +214,14 @@ struct Values {
bool dump_nso;
bool reporting_services;
bool quest_flag;
+ bool disable_macro_jit;
+ bool extended_logging;
+
+ // Misceallaneous
+ std::string log_filter;
+ bool use_dev_keys;
- // BCAT
+ // Services
std::string bcat_backend;
bool bcat_boxcat_local;
@@ -477,8 +233,27 @@ struct Values {
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
-} extern values;
+};
+
+extern Values values;
+
+bool IsConfiguringGlobal();
+void SetConfiguringGlobal(bool is_global);
+
+bool IsGPULevelExtreme();
+bool IsGPULevelHigh();
+
+float Volume();
+
+std::string GetTimeZoneString();
void Apply();
void LogSettings();
+
+// Restore the global state of all applicable settings in the Values struct
+void RestoreGlobalState();
+
+// Fixes settings that are known to cause issues with the emulator
+void Sanitize();
+
} // namespace Settings
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 0f3685d1c..d11b15f38 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -25,6 +25,8 @@
namespace Core {
+namespace Telemetry = Common::Telemetry;
+
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
@@ -56,14 +58,26 @@ static const char* TranslateRenderer(Settings::RendererBackend backend) {
return "Unknown";
}
+static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
+ switch (backend) {
+ case Settings::GPUAccuracy::Normal:
+ return "Normal";
+ case Settings::GPUAccuracy::High:
+ return "High";
+ case Settings::GPUAccuracy::Extreme:
+ return "Extreme";
+ }
+ return "Unknown";
+}
+
u64 GetTelemetryId() {
u64 telemetry_id{};
- const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
+ const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
"telemetry_id"};
- bool generate_new_id = !FileUtil::Exists(filename);
+ bool generate_new_id = !Common::FS::Exists(filename);
if (!generate_new_id) {
- FileUtil::IOFile file(filename, "rb");
+ Common::FS::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
@@ -76,7 +90,7 @@ u64 GetTelemetryId() {
}
if (generate_new_id) {
- FileUtil::IOFile file(filename, "wb");
+ Common::FS::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
@@ -90,10 +104,10 @@ u64 GetTelemetryId() {
u64 RegenerateTelemetryId() {
const u64 new_telemetry_id{GenerateTelemetryId()};
- const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
+ const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
"telemetry_id"};
- FileUtil::IOFile file(filename, "wb");
+ Common::FS::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
@@ -133,7 +147,9 @@ TelemetrySession::~TelemetrySession() {
}
}
-void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
+void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider) {
// Log one-time top-level information
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
@@ -153,9 +169,12 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
app_loader.ReadTitle(name);
if (name.empty()) {
- auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
- if (nacp != nullptr) {
- name = nacp->GetApplicationName();
+ const auto metadata = [&content_provider, &fsc, program_id] {
+ const FileSys::PatchManager pm{program_id, fsc, content_provider};
+ return pm.GetControlMetadata();
+ }();
+ if (metadata.first != nullptr) {
+ name = metadata.first->GetApplicationName();
}
}
@@ -177,19 +196,29 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
// Log user configuration information
constexpr auto field_type = Telemetry::FieldType::UserConfig;
AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
- AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
- AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core);
- AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend));
- AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor);
- AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit);
- AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit);
- AddField(field_type, "Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache);
- AddField(field_type, "Renderer_UseAccurateGpuEmulation",
- Settings::values.use_accurate_gpu_emulation);
+ AddField(field_type, "Audio_EnableAudioStretching",
+ Settings::values.enable_audio_stretching.GetValue());
+ AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
+ AddField(field_type, "Renderer_Backend",
+ TranslateRenderer(Settings::values.renderer_backend.GetValue()));
+ AddField(field_type, "Renderer_ResolutionFactor",
+ Settings::values.resolution_factor.GetValue());
+ AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit.GetValue());
+ AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit.GetValue());
+ AddField(field_type, "Renderer_UseDiskShaderCache",
+ Settings::values.use_disk_shader_cache.GetValue());
+ AddField(field_type, "Renderer_GPUAccuracyLevel",
+ TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
- Settings::values.use_asynchronous_gpu_emulation);
- AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync);
- AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
+ Settings::values.use_asynchronous_gpu_emulation.GetValue());
+ AddField(field_type, "Renderer_UseNvdecEmulation",
+ Settings::values.use_nvdec_emulation.GetValue());
+ AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
+ AddField(field_type, "Renderer_UseAssemblyShaders",
+ Settings::values.use_assembly_shaders.GetValue());
+ AddField(field_type, "Renderer_UseAsynchronousShaders",
+ Settings::values.use_asynchronous_shaders.GetValue());
+ AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
}
bool TelemetrySession::SubmitTestcase() {
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 17ac22377..6f3d45bea 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -7,10 +7,18 @@
#include <string>
#include "common/telemetry.h"
+namespace FileSys {
+class ContentProvider;
+}
+
namespace Loader {
class AppLoader;
}
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Core {
/**
@@ -40,10 +48,14 @@ public:
* - Title file format
* - Miscellaneous settings values.
*
- * @param app_loader The application loader to use to retrieve
- * title-specific information.
+ * @param app_loader The application loader to use to retrieve
+ * title-specific information.
+ * @param fsc Filesystem controller to use to retrieve info.
+ * @param content_provider Content provider to use to retrieve info.
*/
- void AddInitialInfo(Loader::AppLoader& app_loader);
+ void AddInitialInfo(Loader::AppLoader& app_loader,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider);
/**
* Wrapper around the Telemetry::FieldCollection::AddField method.
@@ -52,7 +64,7 @@ public:
* @param value Value for the field to add.
*/
template <typename T>
- void AddField(Telemetry::FieldType type, const char* name, T value) {
+ void AddField(Common::Telemetry::FieldType type, const char* name, T value) {
field_collection.AddField(type, name, std::move(value));
}
@@ -63,7 +75,8 @@ public:
bool SubmitTestcase();
private:
- Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
+ /// Tracks all added fields for the session
+ Common::Telemetry::FieldCollection field_collection;
};
/**
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 1e060f009..5c674a099 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -14,9 +14,9 @@
namespace Tools {
namespace {
-constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60);
+constexpr auto memory_freezer_ns = std::chrono::nanoseconds{1000000000 / 60};
-u64 MemoryReadWidth(Memory::Memory& memory, u32 width, VAddr addr) {
+u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) {
switch (width) {
case 1:
return memory.Read8(addr);
@@ -32,7 +32,7 @@ u64 MemoryReadWidth(Memory::Memory& memory, u32 width, VAddr addr) {
}
}
-void MemoryWriteWidth(Memory::Memory& memory, u32 width, VAddr addr, u64 value) {
+void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 value) {
switch (width) {
case 1:
memory.Write8(addr, static_cast<u8>(value));
@@ -53,12 +53,14 @@ void MemoryWriteWidth(Memory::Memory& memory, u32 width, VAddr addr, u64 value)
} // Anonymous namespace
-Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Memory::Memory& memory_)
+Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_)
: core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent(
"MemoryFreezer::FrameCallback",
- [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
- core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ FrameCallback(user_data, ns_late);
+ });
+ core_timing.ScheduleEvent(memory_freezer_ns, event);
}
Freezer::~Freezer() {
@@ -68,7 +70,7 @@ Freezer::~Freezer() {
void Freezer::SetActive(bool active) {
if (!this->active.exchange(active)) {
FillEntryReads();
- core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
+ core_timing.ScheduleEvent(memory_freezer_ns, event);
LOG_DEBUG(Common_Memory, "Memory freezer activated!");
} else {
LOG_DEBUG(Common_Memory, "Memory freezer deactivated!");
@@ -105,28 +107,21 @@ void Freezer::Unfreeze(VAddr address) {
LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
- entries.erase(
- std::remove_if(entries.begin(), entries.end(),
- [&address](const Entry& entry) { return entry.address == address; }),
- entries.end());
+ std::erase_if(entries, [address](const Entry& entry) { return entry.address == address; });
}
bool Freezer::IsFrozen(VAddr address) const {
std::lock_guard lock{entries_mutex};
- return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- }) != entries.end();
+ return FindEntry(address) != entries.cend();
}
void Freezer::SetFrozenValue(VAddr address, u64 value) {
std::lock_guard lock{entries_mutex};
- const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- });
+ const auto iter = FindEntry(address);
- if (iter == entries.end()) {
+ if (iter == entries.cend()) {
LOG_ERROR(Common_Memory,
"Tried to set freeze value for address={:016X} that is not frozen!", address);
return;
@@ -141,11 +136,9 @@ void Freezer::SetFrozenValue(VAddr address, u64 value) {
std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
std::lock_guard lock{entries_mutex};
- const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- });
+ const auto iter = FindEntry(address);
- if (iter == entries.end()) {
+ if (iter == entries.cend()) {
return std::nullopt;
}
@@ -158,7 +151,17 @@ std::vector<Freezer::Entry> Freezer::GetEntries() const {
return entries;
}
-void Freezer::FrameCallback(u64 userdata, s64 cycles_late) {
+Freezer::Entries::iterator Freezer::FindEntry(VAddr address) {
+ return std::find_if(entries.begin(), entries.end(),
+ [address](const Entry& entry) { return entry.address == address; });
+}
+
+Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const {
+ return std::find_if(entries.begin(), entries.end(),
+ [address](const Entry& entry) { return entry.address == address; });
+}
+
+void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
if (!IsActive()) {
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
return;
@@ -173,7 +176,7 @@ void Freezer::FrameCallback(u64 userdata, s64 cycles_late) {
MemoryWriteWidth(memory, entry.width, entry.address, entry.value);
}
- core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event);
+ core_timing.ScheduleEvent(memory_freezer_ns - ns_late, event);
}
void Freezer::FillEntryReads() {
diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h
index 916339c6c..0fdb701a7 100644
--- a/src/core/tools/freezer.h
+++ b/src/core/tools/freezer.h
@@ -5,6 +5,7 @@
#pragma once
#include <atomic>
+#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
@@ -16,7 +17,7 @@ class CoreTiming;
struct EventType;
} // namespace Core::Timing
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
@@ -38,7 +39,7 @@ public:
u64 value;
};
- explicit Freezer(Core::Timing::CoreTiming& core_timing_, Memory::Memory& memory_);
+ explicit Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_);
~Freezer();
// Enables or disables the entire memory freezer.
@@ -72,17 +73,22 @@ public:
std::vector<Entry> GetEntries() const;
private:
- void FrameCallback(u64 userdata, s64 cycles_late);
+ using Entries = std::vector<Entry>;
+
+ Entries::iterator FindEntry(VAddr address);
+ Entries::const_iterator FindEntry(VAddr address) const;
+
+ void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void FillEntryReads();
std::atomic_bool active{false};
mutable std::mutex entries_mutex;
- std::vector<Entry> entries;
+ Entries entries;
std::shared_ptr<Core::Timing::EventType> event;
Core::Timing::CoreTiming& core_timing;
- Memory::Memory& memory;
+ Core::Memory::Memory& memory;
};
} // namespace Tools
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 2520ba321..1d1b2e08a 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,18 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
+ motion_from_button.cpp
+ motion_from_button.h
+ motion_input.cpp
+ motion_input.h
+ settings.cpp
+ settings.h
+ touch_from_button.cpp
+ touch_from_button.h
+ gcadapter/gc_adapter.cpp
+ gcadapter/gc_adapter.h
+ gcadapter/gc_poller.cpp
+ gcadapter/gc_poller.h
sdl/sdl.cpp
sdl/sdl.h
udp/client.cpp
@@ -17,6 +29,39 @@ add_library(input_common STATIC
udp/udp.h
)
+if (MSVC)
+ target_compile_options(input_common PRIVATE
+ /W4
+ /WX
+
+ # 'expression' : signed/unsigned mismatch
+ /we4018
+ # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
+ /we4244
+ # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
+ /we4245
+ # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
+ /we4254
+ # 'var' : conversion from 'size_t' to 'type', possible loss of data
+ /we4267
+ # 'context' : truncation from 'type1' to 'type2'
+ /we4305
+ )
+else()
+ target_compile_options(input_common PRIVATE
+ -Werror
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=reorder
+ -Werror=shadow
+ -Werror=sign-compare
+ -Werror=unused-but-set-parameter
+ -Werror=unused-but-set-variable
+ -Werror=unused-variable
+ )
+endif()
+
if(SDL2_FOUND)
target_sources(input_common PRIVATE
sdl/sdl_impl.cpp
@@ -26,5 +71,8 @@ if(SDL2_FOUND)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
endif()
+target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR})
+target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
+
create_target_directory_groups(input_common)
-target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES})
+target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost)
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
index 6cabdaa3c..74744d7f3 100755
--- a/src/input_common/analog_from_button.cpp
+++ b/src/input_common/analog_from_button.cpp
@@ -20,18 +20,22 @@ public:
constexpr float SQRT_HALF = 0.707106781f;
int x = 0, y = 0;
- if (right->GetStatus())
+ if (right->GetStatus()) {
++x;
- if (left->GetStatus())
+ }
+ if (left->GetStatus()) {
--x;
- if (up->GetStatus())
+ }
+ if (up->GetStatus()) {
++y;
- if (down->GetStatus())
+ }
+ if (down->GetStatus()) {
--y;
+ }
- float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
- return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
- y * coef * (x == 0 ? 1.0f : SQRT_HALF));
+ const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
+ return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
+ static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
new file mode 100644
index 000000000..d80195c82
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -0,0 +1,509 @@
+// Copyright 2014 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <thread>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
+#endif
+#include <libusb.h>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include "common/logging/log.h"
+#include "common/param_package.h"
+#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/settings.h"
+
+namespace GCAdapter {
+
+Adapter::Adapter() {
+ if (usb_adapter_handle != nullptr) {
+ return;
+ }
+ LOG_INFO(Input, "GC Adapter Initialization started");
+
+ const int init_res = libusb_init(&libusb_ctx);
+ if (init_res == LIBUSB_SUCCESS) {
+ adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
+ } else {
+ LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
+ }
+}
+
+Adapter::~Adapter() {
+ Reset();
+}
+
+void Adapter::AdapterInputThread() {
+ LOG_DEBUG(Input, "GC Adapter input thread started");
+ s32 payload_size{};
+ AdapterPayload adapter_payload{};
+
+ if (adapter_scan_thread.joinable()) {
+ adapter_scan_thread.join();
+ }
+
+ while (adapter_input_thread_running) {
+ libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
+ static_cast<s32>(adapter_payload.size()), &payload_size, 16);
+ if (IsPayloadCorrect(adapter_payload, payload_size)) {
+ UpdateControllers(adapter_payload);
+ UpdateVibrations();
+ }
+ std::this_thread::yield();
+ }
+
+ if (restart_scan_thread) {
+ adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
+ restart_scan_thread = false;
+ }
+}
+
+bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
+ if (payload_size != static_cast<s32>(adapter_payload.size()) ||
+ adapter_payload[0] != LIBUSB_DT_HID) {
+ LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
+ adapter_payload[0]);
+ if (input_error_counter++ > 20) {
+ LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?");
+ adapter_input_thread_running = false;
+ restart_scan_thread = true;
+ }
+ return false;
+ }
+
+ input_error_counter = 0;
+ return true;
+}
+
+void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
+ for (std::size_t port = 0; port < pads.size(); ++port) {
+ const std::size_t offset = 1 + (9 * port);
+ const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
+ UpdatePadType(port, type);
+ if (DeviceConnected(port)) {
+ const u8 b1 = adapter_payload[offset + 1];
+ const u8 b2 = adapter_payload[offset + 2];
+ UpdateStateButtons(port, b1, b2);
+ UpdateStateAxes(port, adapter_payload);
+ if (configuring) {
+ UpdateYuzuSettings(port);
+ }
+ }
+ }
+}
+
+void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
+ if (pads[port].type == pad_type) {
+ return;
+ }
+ // Device changed reset device and set new type
+ ResetDevice(port);
+ pads[port].type = pad_type;
+}
+
+void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
+ if (port >= pads.size()) {
+ return;
+ }
+
+ static constexpr std::array<PadButton, 8> b1_buttons{
+ PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
+ PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
+ };
+
+ static constexpr std::array<PadButton, 4> b2_buttons{
+ PadButton::ButtonStart,
+ PadButton::TriggerZ,
+ PadButton::TriggerR,
+ PadButton::TriggerL,
+ };
+ pads[port].buttons = 0;
+ for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
+ if ((b1 & (1U << i)) != 0) {
+ pads[port].buttons =
+ static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
+ pads[port].last_button = b1_buttons[i];
+ }
+ }
+
+ for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
+ if ((b2 & (1U << j)) != 0) {
+ pads[port].buttons =
+ static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
+ pads[port].last_button = b2_buttons[j];
+ }
+ }
+}
+
+void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
+ if (port >= pads.size()) {
+ return;
+ }
+
+ const std::size_t offset = 1 + (9 * port);
+ static constexpr std::array<PadAxes, 6> axes{
+ PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
+ PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
+ };
+
+ for (const PadAxes axis : axes) {
+ const auto index = static_cast<std::size_t>(axis);
+ const u8 axis_value = adapter_payload[offset + 3 + index];
+ if (pads[port].axis_origin[index] == 255) {
+ pads[port].axis_origin[index] = axis_value;
+ }
+ pads[port].axis_values[index] =
+ static_cast<s16>(axis_value - pads[port].axis_origin[index]);
+ }
+}
+
+void Adapter::UpdateYuzuSettings(std::size_t port) {
+ if (port >= pads.size()) {
+ return;
+ }
+
+ constexpr u8 axis_threshold = 50;
+ GCPadStatus pad_status = {.port = port};
+
+ if (pads[port].buttons != 0) {
+ pad_status.button = pads[port].last_button;
+ pad_queue.Push(pad_status);
+ }
+
+ // Accounting for a threshold here to ensure an intentional press
+ for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
+ const s16 value = pads[port].axis_values[i];
+
+ if (value > axis_threshold || value < -axis_threshold) {
+ pad_status.axis = static_cast<PadAxes>(i);
+ pad_status.axis_value = value;
+ pad_status.axis_threshold = axis_threshold;
+ pad_queue.Push(pad_status);
+ }
+ }
+}
+
+void Adapter::UpdateVibrations() {
+ // Use 8 states to keep the switching between on/off fast enough for
+ // a human to not notice the difference between switching from on/off
+ // More states = more rumble strengths = slower update time
+ constexpr u8 vibration_states = 8;
+
+ vibration_counter = (vibration_counter + 1) % vibration_states;
+
+ for (GCController& pad : pads) {
+ const bool vibrate = pad.rumble_amplitude > vibration_counter;
+ vibration_changed |= vibrate != pad.enable_vibration;
+ pad.enable_vibration = vibrate;
+ }
+ SendVibrations();
+}
+
+void Adapter::SendVibrations() {
+ if (!rumble_enabled || !vibration_changed) {
+ return;
+ }
+ s32 size{};
+ constexpr u8 rumble_command = 0x11;
+ const u8 p1 = pads[0].enable_vibration;
+ const u8 p2 = pads[1].enable_vibration;
+ const u8 p3 = pads[2].enable_vibration;
+ const u8 p4 = pads[3].enable_vibration;
+ std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
+ const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
+ static_cast<s32>(payload.size()), &size, 16);
+ if (err) {
+ LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
+ if (output_error_counter++ > 5) {
+ LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
+ rumble_enabled = false;
+ }
+ return;
+ }
+ output_error_counter = 0;
+ vibration_changed = false;
+}
+
+bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
+ pads[port].rumble_amplitude = amplitude;
+
+ return rumble_enabled;
+}
+
+void Adapter::AdapterScanThread() {
+ adapter_scan_thread_running = true;
+ adapter_input_thread_running = false;
+ if (adapter_input_thread.joinable()) {
+ adapter_input_thread.join();
+ }
+ ClearLibusbHandle();
+ ResetDevices();
+ while (adapter_scan_thread_running && !adapter_input_thread_running) {
+ Setup();
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+}
+
+void Adapter::Setup() {
+ usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337);
+
+ if (usb_adapter_handle == NULL) {
+ return;
+ }
+ if (!CheckDeviceAccess()) {
+ ClearLibusbHandle();
+ return;
+ }
+
+ libusb_device* device = libusb_get_device(usb_adapter_handle);
+
+ LOG_INFO(Input, "GC adapter is now connected");
+ // GC Adapter found and accessible, registering it
+ if (GetGCEndpoint(device)) {
+ adapter_scan_thread_running = false;
+ adapter_input_thread_running = true;
+ rumble_enabled = true;
+ input_error_counter = 0;
+ output_error_counter = 0;
+ adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
+ }
+}
+
+bool Adapter::CheckDeviceAccess() {
+ // This fixes payload problems from offbrand GCAdapters
+ const s32 control_transfer_error =
+ libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
+ if (control_transfer_error < 0) {
+ LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
+ }
+
+ s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
+ if (kernel_driver_error == 1) {
+ kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
+ if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
+ LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
+ kernel_driver_error);
+ }
+ }
+
+ if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ return false;
+ }
+
+ const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
+ if (interface_claim_error) {
+ LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool Adapter::GetGCEndpoint(libusb_device* device) {
+ libusb_config_descriptor* config = nullptr;
+ const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
+ if (config_descriptor_return != LIBUSB_SUCCESS) {
+ LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
+ config_descriptor_return);
+ return false;
+ }
+
+ for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
+ const libusb_interface* interfaceContainer = &config->interface[ic];
+ for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
+ const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
+ for (u8 e = 0; e < interface->bNumEndpoints; e++) {
+ const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
+ if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {
+ input_endpoint = endpoint->bEndpointAddress;
+ } else {
+ output_endpoint = endpoint->bEndpointAddress;
+ }
+ }
+ }
+ }
+ // This transfer seems to be responsible for clearing the state of the adapter
+ // Used to clear the "busy" state of when the device is unexpectedly unplugged
+ unsigned char clear_payload = 0x13;
+ libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
+ sizeof(clear_payload), nullptr, 16);
+ return true;
+}
+
+void Adapter::JoinThreads() {
+ restart_scan_thread = false;
+ adapter_input_thread_running = false;
+ adapter_scan_thread_running = false;
+
+ if (adapter_scan_thread.joinable()) {
+ adapter_scan_thread.join();
+ }
+
+ if (adapter_input_thread.joinable()) {
+ adapter_input_thread.join();
+ }
+}
+
+void Adapter::ClearLibusbHandle() {
+ if (usb_adapter_handle) {
+ libusb_release_interface(usb_adapter_handle, 1);
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ }
+}
+
+void Adapter::ResetDevices() {
+ for (std::size_t i = 0; i < pads.size(); ++i) {
+ ResetDevice(i);
+ }
+}
+
+void Adapter::ResetDevice(std::size_t port) {
+ pads[port].type = ControllerTypes::None;
+ pads[port].enable_vibration = false;
+ pads[port].rumble_amplitude = 0;
+ pads[port].buttons = 0;
+ pads[port].last_button = PadButton::Undefined;
+ pads[port].axis_values.fill(0);
+ pads[port].axis_origin.fill(255);
+}
+
+void Adapter::Reset() {
+ JoinThreads();
+ ClearLibusbHandle();
+ ResetDevices();
+
+ if (libusb_ctx) {
+ libusb_exit(libusb_ctx);
+ }
+}
+
+std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t port = 0; port < pads.size(); ++port) {
+ if (!DeviceConnected(port)) {
+ continue;
+ }
+ std::string name = fmt::format("Gamecube Controller {}", port + 1);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "gcpad"},
+ {"display", std::move(name)},
+ {"port", std::to_string(port)},
+ });
+ }
+ return devices;
+}
+
+InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ // This list is missing ZL/ZR since those are not considered buttons.
+ // We will add those afterwards
+ // This list also excludes any button that can't be really mapped
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
+ switch_to_gcadapter_button = {
+ std::pair{Settings::NativeButton::A, PadButton::ButtonA},
+ {Settings::NativeButton::B, PadButton::ButtonB},
+ {Settings::NativeButton::X, PadButton::ButtonX},
+ {Settings::NativeButton::Y, PadButton::ButtonY},
+ {Settings::NativeButton::Plus, PadButton::ButtonStart},
+ {Settings::NativeButton::DLeft, PadButton::ButtonLeft},
+ {Settings::NativeButton::DUp, PadButton::ButtonUp},
+ {Settings::NativeButton::DRight, PadButton::ButtonRight},
+ {Settings::NativeButton::DDown, PadButton::ButtonDown},
+ {Settings::NativeButton::SL, PadButton::TriggerL},
+ {Settings::NativeButton::SR, PadButton::TriggerR},
+ {Settings::NativeButton::R, PadButton::TriggerZ},
+ };
+ if (!params.Has("port")) {
+ return {};
+ }
+
+ InputCommon::ButtonMapping mapping{};
+ for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<int>(gcadapter_button));
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+
+ // Add the missing bindings for ZL/ZR
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
+ switch_to_gcadapter_axis = {
+ std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
+ {Settings::NativeButton::ZR, PadAxes::TriggerRight},
+ };
+ for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<s32>(PadButton::Stick));
+ button_params.Set("axis", static_cast<s32>(gcadapter_axis));
+ button_params.Set("threshold", 0.5f);
+ button_params.Set("direction", "+");
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+ return mapping;
+}
+
+InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("port")) {
+ return {};
+ }
+
+ InputCommon::AnalogMapping mapping = {};
+ Common::ParamPackage left_analog_params;
+ left_analog_params.Set("engine", "gcpad");
+ left_analog_params.Set("port", params.Get("port", 0));
+ left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
+ left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
+ Common::ParamPackage right_analog_params;
+ right_analog_params.Set("engine", "gcpad");
+ right_analog_params.Set("port", params.Get("port", 0));
+ right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
+ right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
+ return mapping;
+}
+
+bool Adapter::DeviceConnected(std::size_t port) const {
+ return pads[port].type != ControllerTypes::None;
+}
+
+void Adapter::BeginConfiguration() {
+ pad_queue.Clear();
+ configuring = true;
+}
+
+void Adapter::EndConfiguration() {
+ pad_queue.Clear();
+ configuring = false;
+}
+
+Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
+ return pad_queue;
+}
+
+const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
+ return pad_queue;
+}
+
+GCController& Adapter::GetPadState(std::size_t port) {
+ return pads.at(port);
+}
+
+const GCController& Adapter::GetPadState(std::size_t port) const {
+ return pads.at(port);
+}
+
+} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
new file mode 100644
index 000000000..f1256c9da
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -0,0 +1,167 @@
+// Copyright 2014 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include <algorithm>
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
+#include "input_common/main.h"
+
+struct libusb_context;
+struct libusb_device;
+struct libusb_device_handle;
+
+namespace GCAdapter {
+
+enum class PadButton {
+ Undefined = 0x0000,
+ ButtonLeft = 0x0001,
+ ButtonRight = 0x0002,
+ ButtonDown = 0x0004,
+ ButtonUp = 0x0008,
+ TriggerZ = 0x0010,
+ TriggerR = 0x0020,
+ TriggerL = 0x0040,
+ ButtonA = 0x0100,
+ ButtonB = 0x0200,
+ ButtonX = 0x0400,
+ ButtonY = 0x0800,
+ ButtonStart = 0x1000,
+ // Below is for compatibility with "AxisButton" type
+ Stick = 0x2000,
+};
+
+enum class PadAxes : u8 {
+ StickX,
+ StickY,
+ SubstickX,
+ SubstickY,
+ TriggerLeft,
+ TriggerRight,
+ Undefined,
+};
+
+enum class ControllerTypes {
+ None,
+ Wired,
+ Wireless,
+};
+
+struct GCPadStatus {
+ std::size_t port{};
+
+ PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
+
+ PadAxes axis{PadAxes::Undefined};
+ s16 axis_value{};
+ u8 axis_threshold{50};
+};
+
+struct GCController {
+ ControllerTypes type{};
+ bool enable_vibration{};
+ u8 rumble_amplitude{};
+ u16 buttons{};
+ PadButton last_button{};
+ std::array<s16, 6> axis_values{};
+ std::array<u8, 6> axis_origin{};
+};
+
+class Adapter {
+public:
+ Adapter();
+ ~Adapter();
+
+ /// Request a vibration for a controller
+ bool RumblePlay(std::size_t port, u8 amplitude);
+
+ /// Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ Common::SPSCQueue<GCPadStatus>& GetPadQueue();
+ const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
+
+ GCController& GetPadState(std::size_t port);
+ const GCController& GetPadState(std::size_t port) const;
+
+ /// Returns true if there is a device connected to port
+ bool DeviceConnected(std::size_t port) const;
+
+ /// Used for automapping features
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+ InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
+ InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
+
+private:
+ using AdapterPayload = std::array<u8, 37>;
+
+ void UpdatePadType(std::size_t port, ControllerTypes pad_type);
+ void UpdateControllers(const AdapterPayload& adapter_payload);
+ void UpdateYuzuSettings(std::size_t port);
+ void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
+ void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
+ void UpdateVibrations();
+
+ void AdapterInputThread();
+
+ void AdapterScanThread();
+
+ bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
+
+ // Updates vibration state of all controllers
+ void SendVibrations();
+
+ /// For use in initialization, querying devices to find the adapter
+ void Setup();
+
+ /// Resets status of all GC controller devices to a disconected state
+ void ResetDevices();
+
+ /// Resets status of device connected to a disconected state
+ void ResetDevice(std::size_t port);
+
+ /// Returns true if we successfully gain access to GC Adapter
+ bool CheckDeviceAccess();
+
+ /// Captures GC Adapter endpoint address
+ /// Returns true if the endpoind was set correctly
+ bool GetGCEndpoint(libusb_device* device);
+
+ /// For shutting down, clear all data, join all threads, release usb
+ void Reset();
+
+ // Join all threads
+ void JoinThreads();
+
+ // Release usb handles
+ void ClearLibusbHandle();
+
+ libusb_device_handle* usb_adapter_handle = nullptr;
+ std::array<GCController, 4> pads;
+ Common::SPSCQueue<GCPadStatus> pad_queue;
+
+ std::thread adapter_input_thread;
+ std::thread adapter_scan_thread;
+ bool adapter_input_thread_running;
+ bool adapter_scan_thread_running;
+ bool restart_scan_thread;
+
+ libusb_context* libusb_ctx;
+
+ u8 input_endpoint{0};
+ u8 output_endpoint{0};
+ u8 input_error_counter{0};
+ u8 output_error_counter{0};
+ int vibration_counter{0};
+
+ bool configuring{false};
+ bool rumble_enabled{true};
+ bool vibration_changed{true};
+};
+} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
new file mode 100644
index 000000000..4d1052414
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -0,0 +1,332 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <list>
+#include <mutex>
+#include <utility>
+#include "common/assert.h"
+#include "common/threadsafe_queue.h"
+#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/gcadapter/gc_poller.h"
+
+namespace InputCommon {
+
+class GCButton final : public Input::ButtonDevice {
+public:
+ explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
+ : port(port_), button(button_), gcadapter(adapter) {}
+
+ ~GCButton() override;
+
+ bool GetStatus() const override {
+ if (gcadapter->DeviceConnected(port)) {
+ return (gcadapter->GetPadState(port).buttons & button) != 0;
+ }
+ return false;
+ }
+
+private:
+ const u32 port;
+ const s32 button;
+ const GCAdapter::Adapter* gcadapter;
+};
+
+class GCAxisButton final : public Input::ButtonDevice {
+public:
+ explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
+ const GCAdapter::Adapter* adapter)
+ : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
+ gcadapter(adapter) {}
+
+ bool GetStatus() const override {
+ if (gcadapter->DeviceConnected(port)) {
+ const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
+ const float axis_value = current_axis_value / 128.0f;
+ if (trigger_if_greater) {
+ // TODO: Might be worthwile to set a slider for the trigger threshold. It is
+ // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
+ return axis_value > threshold;
+ }
+ return axis_value < -threshold;
+ }
+ return false;
+ }
+
+private:
+ const u32 port;
+ const u32 axis;
+ float threshold;
+ bool trigger_if_greater;
+ const GCAdapter::Adapter* gcadapter;
+};
+
+GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+GCButton::~GCButton() = default;
+
+std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
+ const auto button_id = params.Get("button", 0);
+ const auto port = static_cast<u32>(params.Get("port", 0));
+
+ constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
+
+ // button is not an axis/stick button
+ if (button_id != PAD_STICK_ID) {
+ return std::make_unique<GCButton>(port, button_id, adapter.get());
+ }
+
+ // For Axis buttons, used by the binary sticks.
+ if (button_id == PAD_STICK_ID) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.25f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
+ adapter.get());
+ }
+
+ return nullptr;
+}
+
+Common::ParamPackage GCButtonFactory::GetNextInput() const {
+ Common::ParamPackage params;
+ GCAdapter::GCPadStatus pad;
+ auto& queue = adapter->GetPadQueue();
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ params.Set("engine", "gcpad");
+ params.Set("port", static_cast<s32>(pad.port));
+ if (pad.button != GCAdapter::PadButton::Undefined) {
+ params.Set("button", static_cast<u16>(pad.button));
+ }
+
+ // For Axis button implementation
+ if (pad.axis != GCAdapter::PadAxes::Undefined) {
+ params.Set("axis", static_cast<u8>(pad.axis));
+ params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
+ params.Set("threshold", "0.25");
+ if (pad.axis_value > 0) {
+ params.Set("direction", "+");
+ } else {
+ params.Set("direction", "-");
+ }
+ break;
+ }
+ }
+ return params;
+}
+
+void GCButtonFactory::BeginConfiguration() {
+ polling = true;
+ adapter->BeginConfiguration();
+}
+
+void GCButtonFactory::EndConfiguration() {
+ polling = false;
+ adapter->EndConfiguration();
+}
+
+class GCAnalog final : public Input::AnalogDevice {
+public:
+ explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, float deadzone_,
+ const GCAdapter::Adapter* adapter, float range_)
+ : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
+ range(range_) {}
+
+ float GetAxis(u32 axis) const {
+ if (gcadapter->DeviceConnected(port)) {
+ std::lock_guard lock{mutex};
+ const auto axis_value =
+ static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
+ return (axis_value) / (100.0f * range);
+ }
+ return 0.0f;
+ }
+
+ std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
+ float x = GetAxis(analog_axis_x);
+ float y = GetAxis(analog_axis_y);
+
+ // Make sure the coordinates are in the unit circle,
+ // otherwise normalize it.
+ float r = x * x + y * y;
+ if (r > 1.0f) {
+ r = std::sqrt(r);
+ x /= r;
+ y /= r;
+ }
+
+ return {x, y};
+ }
+
+ std::tuple<float, float> GetStatus() const override {
+ const auto [x, y] = GetAnalog(axis_x, axis_y);
+ const float r = std::sqrt((x * x) + (y * y));
+ if (r > deadzone) {
+ return {x / r * (r - deadzone) / (1 - deadzone),
+ y / r * (r - deadzone) / (1 - deadzone)};
+ }
+ return {0.0f, 0.0f};
+ }
+
+ bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
+ const auto [x, y] = GetStatus();
+ const float directional_deadzone = 0.5f;
+ switch (direction) {
+ case Input::AnalogDirection::RIGHT:
+ return x > directional_deadzone;
+ case Input::AnalogDirection::LEFT:
+ return x < -directional_deadzone;
+ case Input::AnalogDirection::UP:
+ return y > directional_deadzone;
+ case Input::AnalogDirection::DOWN:
+ return y < -directional_deadzone;
+ }
+ return false;
+ }
+
+private:
+ const u32 port;
+ const u32 axis_x;
+ const u32 axis_y;
+ const float deadzone;
+ const GCAdapter::Adapter* gcadapter;
+ const float range;
+ mutable std::mutex mutex;
+};
+
+/// An analog device factory that creates analog devices from GC Adapter
+GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+/**
+ * Creates analog device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "port": the nth gcpad on the adapter
+ * - "axis_x": the index of the axis to be bind as x-axis
+ * - "axis_y": the index of the axis to be bind as y-axis
+ */
+std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
+ const auto port = static_cast<u32>(params.Get("port", 0));
+ const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
+ const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
+ const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
+
+ return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
+}
+
+void GCAnalogFactory::BeginConfiguration() {
+ polling = true;
+ adapter->BeginConfiguration();
+}
+
+void GCAnalogFactory::EndConfiguration() {
+ polling = false;
+ adapter->EndConfiguration();
+}
+
+Common::ParamPackage GCAnalogFactory::GetNextInput() {
+ GCAdapter::GCPadStatus pad;
+ Common::ParamPackage params;
+ auto& queue = adapter->GetPadQueue();
+ while (queue.Pop(pad)) {
+ if (pad.button != GCAdapter::PadButton::Undefined) {
+ params.Set("engine", "gcpad");
+ params.Set("port", static_cast<s32>(pad.port));
+ params.Set("button", static_cast<u16>(pad.button));
+ return params;
+ }
+ if (pad.axis == GCAdapter::PadAxes::Undefined ||
+ std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
+ continue;
+ }
+ // An analog device needs two axes, so we need to store the axis for later and wait for
+ // a second input event. The axes also must be from the same joystick.
+ const u8 axis = static_cast<u8>(pad.axis);
+ if (axis == 0 || axis == 1) {
+ analog_x_axis = 0;
+ analog_y_axis = 1;
+ controller_number = static_cast<s32>(pad.port);
+ break;
+ }
+ if (axis == 2 || axis == 3) {
+ analog_x_axis = 2;
+ analog_y_axis = 3;
+ controller_number = static_cast<s32>(pad.port);
+ break;
+ }
+
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ controller_number = static_cast<s32>(pad.port);
+ } else if (analog_y_axis == -1 && analog_x_axis != axis &&
+ controller_number == static_cast<s32>(pad.port)) {
+ analog_y_axis = axis;
+ break;
+ }
+ }
+ if (analog_x_axis != -1 && analog_y_axis != -1) {
+ params.Set("engine", "gcpad");
+ params.Set("port", controller_number);
+ params.Set("axis_x", analog_x_axis);
+ params.Set("axis_y", analog_y_axis);
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ controller_number = -1;
+ return params;
+ }
+ return params;
+}
+
+class GCVibration final : public Input::VibrationDevice {
+public:
+ explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
+ : port(port_), gcadapter(adapter) {}
+
+ u8 GetStatus() const override {
+ return gcadapter->RumblePlay(port, 0);
+ }
+
+ bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const override {
+ const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
+ const auto processed_amplitude =
+ static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
+
+ return gcadapter->RumblePlay(port, processed_amplitude);
+ }
+
+private:
+ const u32 port;
+ GCAdapter::Adapter* gcadapter;
+};
+
+/// An vibration device factory that creates vibration devices from GC Adapter
+GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+/**
+ * Creates a vibration device from a joystick
+ * @param params contains parameters for creating the device:
+ * - "port": the nth gcpad on the adapter
+ */
+std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
+ const Common::ParamPackage& params) {
+ const auto port = static_cast<u32>(params.Get("port", 0));
+
+ return std::make_unique<GCVibration>(port, adapter.get());
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
new file mode 100644
index 000000000..d1271e3ea
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -0,0 +1,78 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+#include "input_common/gcadapter/gc_adapter.h"
+
+namespace InputCommon {
+
+/**
+ * A button device factory representing a gcpad. It receives gcpad events and forward them
+ * to all button devices it created.
+ */
+class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ /**
+ * Creates a button device from a button press
+ * @param params contains parameters for creating the device:
+ * - "code": the code of the key to bind with the button
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput() const;
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+ bool polling = false;
+};
+
+/// An analog device factory that creates analog devices from GC Adapter
+class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+ int analog_x_axis = -1;
+ int analog_y_axis = -1;
+ int controller_number = -1;
+ bool polling = false;
+};
+
+/// A vibration device factory creates vibration devices from GC Adapter
+class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+ explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
index 078374be5..24a6f7a33 100644
--- a/src/input_common/keyboard.cpp
+++ b/src/input_common/keyboard.cpp
@@ -49,8 +49,9 @@ public:
void ChangeKeyStatus(int key_code, bool pressed) {
std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
- if (pair.key_code == key_code)
+ if (pair.key_code == key_code) {
pair.key_button->status.store(pressed);
+ }
}
}
@@ -73,10 +74,10 @@ KeyButton::~KeyButton() {
}
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
- int key_code = params.Get("code", 0);
+ const int key_code = params.Get("code", 0);
std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
key_button_list->AddKeyButton(key_code, button.get());
- return std::move(button);
+ return button;
}
void Keyboard::PressKey(int key_code) {
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index c98c848cf..e59ad4ff5 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -6,9 +6,14 @@
#include <thread>
#include "common/param_package.h"
#include "input_common/analog_from_button.h"
+#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/gcadapter/gc_poller.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
+#include "input_common/motion_from_button.h"
+#include "input_common/touch_from_button.h"
+#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
#ifdef HAVE_SDL2
#include "input_common/sdl/sdl.h"
@@ -16,40 +21,228 @@
namespace InputCommon {
-static std::shared_ptr<Keyboard> keyboard;
-static std::shared_ptr<MotionEmu> motion_emu;
-static std::unique_ptr<SDL::State> sdl;
-static std::unique_ptr<CemuhookUDP::State> udp;
+struct InputSubsystem::Impl {
+ void Initialize() {
+ gcadapter = std::make_shared<GCAdapter::Adapter>();
+ gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
+ Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
+ gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
+ Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+ gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
+ Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
-void Init() {
- keyboard = std::make_shared<Keyboard>();
- Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
- Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
- std::make_shared<AnalogFromButton>());
- motion_emu = std::make_shared<MotionEmu>();
- Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ keyboard = std::make_shared<Keyboard>();
+ Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
+ Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
+ std::make_shared<AnalogFromButton>());
+ Input::RegisterFactory<Input::MotionDevice>("keyboard",
+ std::make_shared<MotionFromButton>());
+ motion_emu = std::make_shared<MotionEmu>();
+ Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
+ std::make_shared<TouchFromButtonFactory>());
- sdl = SDL::Init();
+#ifdef HAVE_SDL2
+ sdl = SDL::Init();
+#endif
+
+ udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
+ udpmotion = std::make_shared<UDPMotionFactory>(udp);
+ Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
+ udptouch = std::make_shared<UDPTouchFactory>(udp);
+ Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
+ }
+
+ void Shutdown() {
+ Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
+ Input::UnregisterFactory<Input::MotionDevice>("keyboard");
+ keyboard.reset();
+ Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
+ Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
+ motion_emu.reset();
+ Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
+#ifdef HAVE_SDL2
+ sdl.reset();
+#endif
+ Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
+ Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+ Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
+
+ gcbuttons.reset();
+ gcanalog.reset();
+ gcvibration.reset();
+
+ Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+ Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+
+ udpmotion.reset();
+ udptouch.reset();
+ }
+
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices = {
+ Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
+ Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
+ };
+#ifdef HAVE_SDL2
+ auto sdl_devices = sdl->GetInputDevices();
+ devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
+#endif
+ auto udp_devices = udp->GetInputDevices();
+ devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
+ auto gcpad_devices = gcadapter->GetInputDevices();
+ devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
+ return devices;
+ }
+
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetAnalogMappingForDevice(params);
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetAnalogMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
+
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetButtonMappingForDevice(params);
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetButtonMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
+
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "cemuhookudp") {
+ // TODO return the correct motion device
+ return {};
+ }
+ return {};
+ }
+
+ std::shared_ptr<Keyboard> keyboard;
+ std::shared_ptr<MotionEmu> motion_emu;
+#ifdef HAVE_SDL2
+ std::unique_ptr<SDL::State> sdl;
+#endif
+ std::shared_ptr<GCButtonFactory> gcbuttons;
+ std::shared_ptr<GCAnalogFactory> gcanalog;
+ std::shared_ptr<GCVibrationFactory> gcvibration;
+ std::shared_ptr<UDPMotionFactory> udpmotion;
+ std::shared_ptr<UDPTouchFactory> udptouch;
+ std::shared_ptr<CemuhookUDP::Client> udp;
+ std::shared_ptr<GCAdapter::Adapter> gcadapter;
+};
+
+InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
+
+InputSubsystem::~InputSubsystem() = default;
+
+void InputSubsystem::Initialize() {
+ impl->Initialize();
+}
+
+void InputSubsystem::Shutdown() {
+ impl->Shutdown();
+}
+
+Keyboard* InputSubsystem::GetKeyboard() {
+ return impl->keyboard.get();
+}
+
+const Keyboard* InputSubsystem::GetKeyboard() const {
+ return impl->keyboard.get();
+}
+
+MotionEmu* InputSubsystem::GetMotionEmu() {
+ return impl->motion_emu.get();
+}
+
+const MotionEmu* InputSubsystem::GetMotionEmu() const {
+ return impl->motion_emu.get();
+}
+
+std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
+ return impl->GetInputDevices();
+}
+
+AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetAnalogMappingForDevice(device);
+}
+
+ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetButtonMappingForDevice(device);
+}
+
+MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetMotionMappingForDevice(device);
+}
+
+GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
+ return impl->gcanalog.get();
+}
+
+const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
+ return impl->gcanalog.get();
+}
+
+GCButtonFactory* InputSubsystem::GetGCButtons() {
+ return impl->gcbuttons.get();
+}
- udp = CemuhookUDP::Init();
+const GCButtonFactory* InputSubsystem::GetGCButtons() const {
+ return impl->gcbuttons.get();
}
-void Shutdown() {
- Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
- keyboard.reset();
- Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
- Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
- motion_emu.reset();
- sdl.reset();
- udp.reset();
+UDPMotionFactory* InputSubsystem::GetUDPMotions() {
+ return impl->udpmotion.get();
}
-Keyboard* GetKeyboard() {
- return keyboard.get();
+const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
+ return impl->udpmotion.get();
}
-MotionEmu* GetMotionEmu() {
- return motion_emu.get();
+UDPTouchFactory* InputSubsystem::GetUDPTouch() {
+ return impl->udptouch.get();
+}
+
+const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
+ return impl->udptouch.get();
+}
+
+void InputSubsystem::ReloadInputDevices() {
+ if (!impl->udp) {
+ return;
+ }
+ impl->udp->ReloadUDPClient();
+}
+
+std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
+ Polling::DeviceType type) const {
+#ifdef HAVE_SDL2
+ return impl->sdl->GetPollers(type);
+#else
+ return {};
+#endif
}
std::string GenerateKeyboardParam(int key_code) {
@@ -73,18 +266,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
};
return circle_pad_param.Serialize();
}
-
-namespace Polling {
-
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
- std::vector<std::unique_ptr<DevicePoller>> pollers;
-
-#ifdef HAVE_SDL2
- pollers = sdl->GetPollers(type);
-#endif
-
- return pollers;
-}
-
-} // namespace Polling
} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 77a0ce90b..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -6,40 +6,29 @@
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
namespace Common {
class ParamPackage;
}
-namespace InputCommon {
-
-/// Initializes and registers all built-in input device factories.
-void Init();
-
-/// Deregisters all built-in input device factories and shuts them down.
-void Shutdown();
-
-class Keyboard;
-
-/// Gets the keyboard button device factory.
-Keyboard* GetKeyboard();
-
-class MotionEmu;
-
-/// Gets the motion emulation factory.
-MotionEmu* GetMotionEmu();
+namespace Settings::NativeAnalog {
+enum Values : int;
+}
-/// Generates a serialized param package for creating a keyboard button device
-std::string GenerateKeyboardParam(int key_code);
+namespace Settings::NativeButton {
+enum Values : int;
+}
-/// Generates a serialized param package for creating an analog device taking input from keyboard
-std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
- int key_modifier, float modifier_scale);
+namespace Settings::NativeMotion {
+enum Values : int;
+}
+namespace InputCommon {
namespace Polling {
-enum class DeviceType { Button, Analog };
+enum class DeviceType { Button, AnalogPreferred, Motion };
/**
* A class that can be used to get inputs from an input device like controllers without having to
@@ -49,7 +38,9 @@ class DevicePoller {
public:
virtual ~DevicePoller() = default;
/// Setup and start polling for inputs, should be called before GetNextInput
- virtual void Start() = 0;
+ /// If a device_id is provided, events should be filtered to only include events from this
+ /// device id
+ virtual void Start(const std::string& device_id = "") = 0;
/// Stop polling
virtual void Stop() = 0;
/**
@@ -59,8 +50,110 @@ public:
*/
virtual Common::ParamPackage GetNextInput() = 0;
};
-
-// Get all DevicePoller from all backends for a specific device type
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
} // namespace Polling
+
+class GCAnalogFactory;
+class GCButtonFactory;
+class UDPMotionFactory;
+class UDPTouchFactory;
+class Keyboard;
+class MotionEmu;
+
+/**
+ * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
+ * mapping for the device. This is currently only implemented for the SDL backend devices.
+ */
+using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
+using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
+using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
+
+class InputSubsystem {
+public:
+ explicit InputSubsystem();
+ ~InputSubsystem();
+
+ InputSubsystem(const InputSubsystem&) = delete;
+ InputSubsystem& operator=(const InputSubsystem&) = delete;
+
+ InputSubsystem(InputSubsystem&&) = delete;
+ InputSubsystem& operator=(InputSubsystem&&) = delete;
+
+ /// Initializes and registers all built-in input device factories.
+ void Initialize();
+
+ /// Unregisters all built-in input device factories and shuts them down.
+ void Shutdown();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] Keyboard* GetKeyboard();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] const Keyboard* GetKeyboard() const;
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] MotionEmu* GetMotionEmu();
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] const MotionEmu* GetMotionEmu() const;
+
+ /**
+ * Returns all available input devices that this Factory can create a new device with.
+ * Each returned ParamPackage should have a `display` field used for display, a class field for
+ * backends to determine if this backend is meant to service the request and any other
+ * information needed to identify this in the backend later.
+ */
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ /// Retrieves the analog mappings for the given device.
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the button mappings for the given device.
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the motion mappings for the given device.
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] GCButtonFactory* GetGCButtons();
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] UDPMotionFactory* GetUDPMotions();
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] UDPTouchFactory* GetUDPTouch();
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
+
+ /// Reloads the input devices
+ void ReloadInputDevices();
+
+ /// Get all DevicePoller from all backends for a specific device type
+ [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
+ Polling::DeviceType type) const;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/// Generates a serialized param package for creating a keyboard button device
+std::string GenerateKeyboardParam(int key_code);
+
+/// Generates a serialized param package for creating an analog device taking input from keyboard
+std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
+ int key_modifier, float modifier_scale);
+
} // namespace InputCommon
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index 868251628..d4da5596b 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -18,11 +18,11 @@ namespace InputCommon {
// Implementation class of the motion emulation device
class MotionEmuDevice {
public:
- MotionEmuDevice(int update_millisecond, float sensitivity)
- : update_millisecond(update_millisecond),
+ explicit MotionEmuDevice(int update_millisecond_, float sensitivity_)
+ : update_millisecond(update_millisecond_),
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::milliseconds(update_millisecond))),
- sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
+ sensitivity(sensitivity_), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
~MotionEmuDevice() {
if (motion_emu_thread.joinable()) {
@@ -37,16 +37,18 @@ public:
}
void Tilt(int x, int y) {
- auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
- if (is_tilting) {
- std::lock_guard guard{tilt_mutex};
- if (mouse_move.x == 0 && mouse_move.y == 0) {
- tilt_angle = 0;
- } else {
- tilt_direction = mouse_move.Cast<float>();
- tilt_angle =
- std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
- }
+ if (!is_tilting) {
+ return;
+ }
+
+ std::lock_guard guard{tilt_mutex};
+ const auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
+ if (mouse_move.x == 0 && mouse_move.y == 0) {
+ tilt_angle = 0;
+ } else {
+ tilt_direction = mouse_move.Cast<float>();
+ tilt_angle =
+ std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
}
}
@@ -56,7 +58,7 @@ public:
is_tilting = false;
}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
+ Input::MotionStatus GetStatus() {
std::lock_guard guard{status_mutex};
return status;
}
@@ -76,7 +78,7 @@ private:
Common::Event shutdown_event;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
+ Input::MotionStatus status;
std::mutex status_mutex;
// Note: always keep the thread declaration at the end so that other objects are initialized
@@ -86,11 +88,10 @@ private:
void MotionEmuThread() {
auto update_time = std::chrono::steady_clock::now();
Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
- Common::Quaternion<float> old_q;
while (!shutdown_event.WaitUntil(update_time)) {
update_time += update_duration;
- old_q = q;
+ const Common::Quaternion<float> old_q = q;
{
std::lock_guard guard{tilt_mutex};
@@ -100,23 +101,32 @@ private:
Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle);
}
- auto inv_q = q.Inverse();
+ const auto inv_q = q.Inverse();
// Set the gravity vector in world space
auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f);
// Find the angular rate vector in world space
auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
- angular_rate *= 1000 / update_millisecond / Common::PI * 180;
+ angular_rate *= static_cast<float>(1000 / update_millisecond) / Common::PI * 180.0f;
// Transform the two vectors from world space to 3DS space
gravity = QuaternionRotate(inv_q, gravity);
angular_rate = QuaternionRotate(inv_q, angular_rate);
+ // TODO: Calculate the correct rotation vector and orientation matrix
+ const auto matrix4x4 = q.ToMatrix();
+ const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
+ const std::array orientation{
+ Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
+ };
+
// Update the sensor state
{
std::lock_guard guard{status_mutex};
- status = std::make_tuple(gravity, angular_rate);
+ status = std::make_tuple(gravity, angular_rate, rotation, orientation);
}
}
}
@@ -127,11 +137,11 @@ private:
// can forward all the inputs to the implementation only when it is valid.
class MotionEmuDeviceWrapper : public Input::MotionDevice {
public:
- MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
+ explicit MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
+ Input::MotionStatus GetStatus() const override {
return device->GetStatus();
}
@@ -139,13 +149,13 @@ public:
};
std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
- int update_period = params.Get("update_period", 100);
- float sensitivity = params.Get("sensitivity", 0.01f);
+ const int update_period = params.Get("update_period", 100);
+ const float sensitivity = params.Get("sensitivity", 0.01f);
auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
// Previously created device is disconnected here. Having two motion devices for 3DS is not
// expected.
current_device = device_wrapper->device;
- return std::move(device_wrapper);
+ return device_wrapper;
}
void MotionEmu::BeginTilt(int x, int y) {
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
new file mode 100644
index 000000000..29045a673
--- /dev/null
+++ b/src/input_common/motion_from_button.cpp
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/motion_from_button.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+class MotionKey final : public Input::MotionDevice {
+public:
+ using Button = std::unique_ptr<Input::ButtonDevice>;
+
+ explicit MotionKey(Button key_) : key(std::move(key_)) {}
+
+ Input::MotionStatus GetStatus() const override {
+
+ if (key->GetStatus()) {
+ return motion.GetRandomMotion(2, 6);
+ }
+ return motion.GetRandomMotion(0, 0);
+ }
+
+private:
+ Button key;
+ InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
+};
+
+std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
+ auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
+ return std::make_unique<MotionKey>(std::move(key));
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h
new file mode 100644
index 000000000..a959046fb
--- /dev/null
+++ b/src/input_common/motion_from_button.h
@@ -0,0 +1,25 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * An motion device factory that takes a keyboard button and uses it as a random
+ * motion device.
+ */
+class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
+public:
+ /**
+ * Creates an motion device from button devices
+ * @param params contains parameters for creating the device:
+ * - "key": a serialized ParamPackage for creating a button device
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..f77ba535d
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,301 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <random>
+#include "common/math_util.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
+
+void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
+ accel = acceleration;
+}
+
+void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
+ gyro = gyroscope - gyro_drift;
+
+ // Auto adjust drift to minimize drift
+ if (!IsMoving(0.1f)) {
+ gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
+ }
+
+ if (gyro.Length2() < gyro_threshold) {
+ gyro = {};
+ } else {
+ only_accelerometer = false;
+ }
+}
+
+void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
+ quat = quaternion;
+}
+
+void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
+ gyro_drift = drift;
+}
+
+void MotionInput::SetGyroThreshold(f32 threshold) {
+ gyro_threshold = threshold;
+}
+
+void MotionInput::EnableReset(bool reset) {
+ reset_enabled = reset;
+}
+
+void MotionInput::ResetRotations() {
+ rotations = {};
+}
+
+bool MotionInput::IsMoving(f32 sensitivity) const {
+ return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
+}
+
+bool MotionInput::IsCalibrated(f32 sensitivity) const {
+ return real_error.Length() < sensitivity;
+}
+
+void MotionInput::UpdateRotation(u64 elapsed_time) {
+ const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
+ if (sample_period > 0.1f) {
+ return;
+ }
+ rotations += gyro * sample_period;
+}
+
+void MotionInput::UpdateOrientation(u64 elapsed_time) {
+ if (!IsCalibrated(0.1f)) {
+ ResetOrientation();
+ }
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+ const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
+
+ // Ignore invalid elapsed time
+ if (sample_period > 0.1f) {
+ return;
+ }
+
+ const auto normal_accel = accel.Normalized();
+ auto rad_gyro = gyro * Common::PI * 2;
+ const f32 swap = rad_gyro.x;
+ rad_gyro.x = rad_gyro.y;
+ rad_gyro.y = -swap;
+ rad_gyro.z = -rad_gyro.z;
+
+ // Clear gyro values if there is no gyro present
+ if (only_accelerometer) {
+ rad_gyro.x = 0;
+ rad_gyro.y = 0;
+ rad_gyro.z = 0;
+ }
+
+ // Ignore drift correction if acceleration is not reliable
+ if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ // Prevent integral windup
+ if (ki != 0.0f && !IsCalibrated(0.05f)) {
+ integral_error += real_error;
+ } else {
+ integral_error = {};
+ }
+
+ // Apply feedback terms
+ if (!only_accelerometer) {
+ rad_gyro += kp * real_error;
+ rad_gyro += ki * integral_error;
+ rad_gyro += kd * derivative_error;
+ } else {
+ // Give more weight to acelerometer values to compensate for the lack of gyro
+ rad_gyro += 35.0f * kp * real_error;
+ rad_gyro += 10.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ // Emulate gyro values for games that need them
+ gyro.x = -rad_gyro.y;
+ gyro.y = rad_gyro.x;
+ gyro.z = -rad_gyro.z;
+ UpdateRotation(elapsed_time);
+ }
+ }
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+}
+
+std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
+ const Common::Quaternion<float> quad{
+ .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
+ .w = -quat.xyz[2],
+ };
+ const std::array<float, 16> matrix4x4 = quad.ToMatrix();
+
+ return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
+}
+
+Common::Vec3f MotionInput::GetAcceleration() const {
+ return accel;
+}
+
+Common::Vec3f MotionInput::GetGyroscope() const {
+ return gyro;
+}
+
+Common::Quaternion<f32> MotionInput::GetQuaternion() const {
+ return quat;
+}
+
+Common::Vec3f MotionInput::GetRotations() const {
+ return rotations;
+}
+
+Input::MotionStatus MotionInput::GetMotion() const {
+ const Common::Vec3f gyroscope = GetGyroscope();
+ const Common::Vec3f accelerometer = GetAcceleration();
+ const Common::Vec3f rotation = GetRotations();
+ const std::array<Common::Vec3f, 3> orientation = GetOrientation();
+ return {accelerometer, gyroscope, rotation, orientation};
+}
+
+Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<s16> distribution(-1000, 1000);
+ const Common::Vec3f gyroscope{
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ };
+ const Common::Vec3f accelerometer{
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ };
+ constexpr Common::Vec3f rotation;
+ constexpr std::array orientation{
+ Common::Vec3f{1.0f, 0.0f, 0.0f},
+ Common::Vec3f{0.0f, 1.0f, 0.0f},
+ Common::Vec3f{0.0f, 0.0f, 1.0f},
+ };
+ return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation};
+}
+
+void MotionInput::ResetOrientation() {
+ if (!reset_enabled || only_accelerometer) {
+ return;
+ }
+ if (!IsMoving(0.5f) && accel.z <= -0.9f) {
+ ++reset_counter;
+ if (reset_counter > 900) {
+ quat.w = 0;
+ quat.xyz[0] = 0;
+ quat.xyz[1] = 0;
+ quat.xyz[2] = -1;
+ SetOrientationFromAccelerometer();
+ integral_error = {};
+ reset_counter = 0;
+ }
+ } else {
+ reset_counter = 0;
+ }
+}
+
+void MotionInput::SetOrientationFromAccelerometer() {
+ int iterations = 0;
+ const f32 sample_period = 0.015f;
+
+ const auto normal_accel = accel.Normalized();
+
+ while (!IsCalibrated(0.01f) && ++iterations < 100) {
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+
+ Common::Vec3f rad_gyro;
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ rad_gyro += 10.0f * kp * real_error;
+ rad_gyro += 5.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+ }
+}
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..efe74cf19
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,74 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/quaternion.h"
+#include "common/vector_math.h"
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+class MotionInput {
+public:
+ explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
+
+ MotionInput(const MotionInput&) = default;
+ MotionInput& operator=(const MotionInput&) = default;
+
+ MotionInput(MotionInput&&) = default;
+ MotionInput& operator=(MotionInput&&) = default;
+
+ void SetAcceleration(const Common::Vec3f& acceleration);
+ void SetGyroscope(const Common::Vec3f& gyroscope);
+ void SetQuaternion(const Common::Quaternion<f32>& quaternion);
+ void SetGyroDrift(const Common::Vec3f& drift);
+ void SetGyroThreshold(f32 threshold);
+
+ void EnableReset(bool reset);
+ void ResetRotations();
+
+ void UpdateRotation(u64 elapsed_time);
+ void UpdateOrientation(u64 elapsed_time);
+
+ [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
+ [[nodiscard]] Common::Vec3f GetAcceleration() const;
+ [[nodiscard]] Common::Vec3f GetGyroscope() const;
+ [[nodiscard]] Common::Vec3f GetRotations() const;
+ [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
+ [[nodiscard]] Input::MotionStatus GetMotion() const;
+ [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
+ int gyro_magnitude) const;
+
+ [[nodiscard]] bool IsMoving(f32 sensitivity) const;
+ [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
+
+private:
+ void ResetOrientation();
+ void SetOrientationFromAccelerometer();
+
+ // PID constants
+ f32 kp;
+ f32 ki;
+ f32 kd;
+
+ // PID errors
+ Common::Vec3f real_error;
+ Common::Vec3f integral_error;
+ Common::Vec3f derivative_error;
+
+ Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
+ Common::Vec3f rotations;
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f gyro_drift;
+
+ f32 gyro_threshold = 0.0f;
+ u32 reset_counter = 0;
+ bool reset_enabled = true;
+ bool only_accelerometer = true;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 5306daa70..42bbf14d4 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -6,6 +6,7 @@
#include <memory>
#include <vector>
+#include "common/param_package.h"
#include "input_common/main.h"
namespace InputCommon::Polling {
@@ -22,14 +23,24 @@ public:
/// Unregisters SDL device factories and shut them down.
virtual ~State() = default;
- virtual Pollers GetPollers(Polling::DeviceType type) = 0;
+ virtual Pollers GetPollers(Polling::DeviceType) {
+ return {};
+ }
+
+ virtual std::vector<Common::ParamPackage> GetInputDevices() {
+ return {};
+ }
+
+ virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
+ virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
};
class NullState : public State {
public:
- Pollers GetPollers(Polling::DeviceType type) override {
- return {};
- }
};
std::unique_ptr<State> Init();
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index a2e0c0bd2..7827e324c 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,10 +3,14 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <atomic>
+#include <chrono>
#include <cmath>
#include <functional>
#include <mutex>
+#include <optional>
+#include <sstream>
#include <string>
#include <thread>
#include <tuple>
@@ -15,15 +19,17 @@
#include <vector>
#include <SDL.h>
#include "common/logging/log.h"
-#include "common/math_util.h"
#include "common/param_package.h"
#include "common/threadsafe_queue.h"
#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
#include "input_common/sdl/sdl_impl.h"
+#include "input_common/settings.h"
namespace InputCommon::SDL {
-static std::string GetGUID(SDL_Joystick* joystick) {
+namespace {
+std::string GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
char guid_str[33];
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
@@ -31,7 +37,8 @@ static std::string GetGUID(SDL_Joystick* joystick) {
}
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+} // Anonymous namespace
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
auto* const sdl_state = static_cast<SDLState*>(user_data);
@@ -48,8 +55,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
class SDLJoystick {
public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {}
+ SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
+ SDL_GameController* game_controller)
+ : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
+ sdl_controller{game_controller, &SDL_GameControllerClose} {}
void SetButton(int button, bool value) {
std::lock_guard lock{mutex};
@@ -66,14 +75,24 @@ public:
state.axes.insert_or_assign(axis, value);
}
- float GetAxis(int axis) const {
+ float GetAxis(int axis, float range) const {
std::lock_guard lock{mutex};
- return state.axes.at(axis) / 32767.0f;
+ return static_cast<float>(state.axes.at(axis)) / (32767.0f * range);
}
- std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
+ bool RumblePlay(u16 amp_low, u16 amp_high) {
+ if (sdl_controller) {
+ return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0;
+ } else if (sdl_joystick) {
+ return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0;
+ }
+
+ return false;
+ }
+
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
+ float x = GetAxis(axis_x, range);
+ float y = GetAxis(axis_y, range);
y = -y; // 3DS uses an y-axis inverse from SDL
// Make sure the coordinates are in the unit circle,
@@ -88,6 +107,10 @@ public:
return std::make_tuple(x, y);
}
+ const MotionInput& GetMotion() const {
+ return motion;
+ }
+
void SetHat(int hat, Uint8 direction) {
std::lock_guard lock{mutex};
state.hats.insert_or_assign(hat, direction);
@@ -115,8 +138,13 @@ public:
return sdl_joystick.get();
}
- void SetSDLJoystick(SDL_Joystick* joystick) {
+ SDL_GameController* GetSDLGameController() const {
+ return sdl_controller.get();
+ }
+
+ void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
sdl_joystick.reset(joystick);
+ sdl_controller.reset(controller);
}
private:
@@ -128,21 +156,29 @@ private:
std::string guid;
int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
+ std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
+
+ // Motion is initialized without PID values as motion input is not aviable for SDL2
+ MotionInput motion{0.0f, 0.0f, 0.0f};
};
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
std::lock_guard lock{joystick_map_mutex};
const auto it = joystick_map.find(guid);
+
if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) {
- auto joystick =
- std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr);
+ auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
+ nullptr, nullptr);
it->second.emplace_back(std::move(joystick));
}
- return it->second[port];
+
+ return it->second[static_cast<std::size_t>(port)];
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr);
+
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
+
return joystick_map[guid].emplace_back(std::move(joystick));
}
@@ -152,86 +188,72 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
std::lock_guard lock{joystick_map_mutex};
const auto map_it = joystick_map.find(guid);
- if (map_it != joystick_map.end()) {
- const auto vec_it =
- std::find_if(map_it->second.begin(), map_it->second.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return sdl_joystick == joystick->GetSDLJoystick();
- });
- if (vec_it != map_it->second.end()) {
- // This is the common case: There is already an existing SDL_Joystick maped to a
- // SDLJoystick. return the SDLJoystick
- return *vec_it;
- }
- // Search for a SDLJoystick without a mapped SDL_Joystick...
- const auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) {
- return !joystick->GetSDLJoystick();
- });
- if (nullptr_it != map_it->second.end()) {
- // ... and map it
- (*nullptr_it)->SetSDLJoystick(sdl_joystick);
- return *nullptr_it;
- }
+ if (map_it == joystick_map.end()) {
+ return nullptr;
+ }
+
+ const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
+ [&sdl_joystick](const auto& joystick) {
+ return joystick->GetSDLJoystick() == sdl_joystick;
+ });
- // There is no SDLJoystick without a mapped SDL_Joystick
- // Create a new SDLJoystick
- const int port = static_cast<int>(map_it->second.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
- return map_it->second.emplace_back(std::move(joystick));
+ if (vec_it == map_it->second.end()) {
+ return nullptr;
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
- return joystick_map[guid].emplace_back(std::move(joystick));
+ return *vec_it;
}
void SDLState::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
+ SDL_GameController* sdl_gamecontroller = nullptr;
+
+ if (SDL_IsGameController(joystick_index)) {
+ sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
+ }
+
if (!sdl_joystick) {
- LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+ LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
return;
}
+
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
+
auto& joystick_guid_list = joystick_map[guid];
- const auto it = std::find_if(
- joystick_guid_list.begin(), joystick_guid_list.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
- if (it != joystick_guid_list.end()) {
- (*it)->SetSDLJoystick(sdl_joystick);
+ const auto joystick_it =
+ std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
+ [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
+
+ if (joystick_it != joystick_guid_list.end()) {
+ (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
return;
}
+
const int port = static_cast<int>(joystick_guid_list.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
joystick_guid_list.emplace_back(std::move(joystick));
}
void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
const std::string guid = GetGUID(sdl_joystick);
- std::shared_ptr<SDLJoystick> joystick;
- {
- std::lock_guard lock{joystick_map_mutex};
- // This call to guid is safe since the joystick is guaranteed to be in the map
- const auto& joystick_guid_list = joystick_map[guid];
- const auto joystick_it =
- std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return joystick->GetSDLJoystick() == sdl_joystick;
- });
- joystick = *joystick_it;
- }
-
- // Destruct SDL_Joystick outside the lock guard because SDL can internally call the
- // event callback which locks the mutex again.
- joystick->SetSDLJoystick(nullptr);
+ std::lock_guard lock{joystick_map_mutex};
+ // This call to guid is safe since the joystick is guaranteed to be in the map
+ const auto& joystick_guid_list = joystick_map[guid];
+ const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
+ [&sdl_joystick](const auto& joystick) {
+ return joystick->GetSDLJoystick() == sdl_joystick;
+ });
+
+ (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
}
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -313,7 +335,7 @@ public:
trigger_if_greater(trigger_if_greater_) {}
bool GetStatus() const override {
- const float axis_value = joystick->GetAxis(axis);
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
if (trigger_if_greater) {
return axis_value > threshold;
}
@@ -329,22 +351,24 @@ private:
class SDLAnalog final : public Input::AnalogDevice {
public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
+ explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
+ float deadzone_, float range_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
+ range(range_) {}
std::tuple<float, float> GetStatus() const override {
- const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
+ const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone));
}
- return std::make_tuple<float, float>(0.0f, 0.0f);
+ return {};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
- const float directional_deadzone = 0.4f;
+ const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
@@ -363,6 +387,95 @@ private:
const int axis_x;
const int axis_y;
const float deadzone;
+ const float range;
+};
+
+class SDLVibration final : public Input::VibrationDevice {
+public:
+ explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
+ : joystick(std::move(joystick_)) {}
+
+ u8 GetStatus() const override {
+ joystick->RumblePlay(1, 1);
+ return joystick->RumblePlay(0, 0);
+ }
+
+ bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const override {
+ const auto process_amplitude = [](f32 amplitude) {
+ return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
+ };
+
+ const auto processed_amp_low = process_amplitude(amp_low);
+ const auto processed_amp_high = process_amplitude(amp_high);
+
+ return joystick->RumblePlay(processed_amp_low, processed_amp_high);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+};
+
+class SDLDirectionMotion final : public Input::MotionDevice {
+public:
+ explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetHatDirection(hat, direction)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAxisMotion final : public Input::MotionDevice {
+public:
+ explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
+ bool trigger_if_greater_)
+ : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
+ trigger_if_greater(trigger_if_greater_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
+ bool trigger = axis_value < threshold;
+ if (trigger_if_greater) {
+ trigger = axis_value > threshold;
+ }
+
+ if (trigger) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis;
+ float threshold;
+ bool trigger_if_greater;
+};
+
+class SDLButtonMotion final : public Input::MotionDevice {
+public:
+ explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(std::move(joystick_)), button(button_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetButton(button)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
};
/// A button device factory that creates button devices from SDL joystick
@@ -445,7 +558,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
/**
- * Creates analog device from joystick axes
+ * Creates an analog device from joystick axes
* @param params contains parameters for creating the device:
* - "guid": the guid of the joystick to bind
* - "port": the nth joystick of the same type
@@ -457,14 +570,98 @@ public:
const int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
-
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
auto joystick = state.GetSDLJoystickByGUID(guid, port);
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
joystick->SetAxis(axis_x, 0);
joystick->SetAxis(axis_y, 0);
- return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
+ return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
+ }
+
+private:
+ SDLState& state;
+};
+
+/// An vibration device factory that creates vibration devices from SDL joystick
+class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+ explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates a vibration device from a joystick
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ */
+ std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+ return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
+ }
+
+private:
+ SDLState& state;
+};
+
+/// A motion device factory that creates motion devices from SDL joystick
+class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+ explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates motion device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+
+ auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+ if (params.Has("hat")) {
+ const int hat = params.Get("hat", 0);
+ const std::string direction_name = params.Get("direction", "");
+ Uint8 direction;
+ if (direction_name == "up") {
+ direction = SDL_HAT_UP;
+ } else if (direction_name == "down") {
+ direction = SDL_HAT_DOWN;
+ } else if (direction_name == "left") {
+ direction = SDL_HAT_LEFT;
+ } else if (direction_name == "right") {
+ direction = SDL_HAT_RIGHT;
+ } else {
+ direction = 0;
+ }
+ // This is necessary so accessing GetHat with hat won't crash
+ joystick->SetHat(hat, SDL_HAT_CENTERED);
+ return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
+ }
+
+ if (params.Has("axis")) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.5f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ // This is necessary so accessing GetAxis with axis won't crash
+ joystick->SetAxis(axis, 0);
+ return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
+ }
+
+ const int button = params.Get("button", 0);
+ // This is necessary so accessing GetButton with button won't crash
+ joystick->SetButton(button, false);
+ return std::make_unique<SDLButtonMotion>(joystick, button);
}
private:
@@ -473,15 +670,22 @@ private:
SDLState::SDLState() {
using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
-
- // If the frontend is going to manage the event loop, then we dont start one here
- start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
+ button_factory = std::make_shared<SDLButtonFactory>(*this);
+ analog_factory = std::make_shared<SDLAnalogFactory>(*this);
+ vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
+ motion_factory = std::make_shared<SDLMotionFactory>(*this);
+ RegisterFactory<ButtonDevice>("sdl", button_factory);
+ RegisterFactory<AnalogDevice>("sdl", analog_factory);
+ RegisterFactory<VibrationDevice>("sdl", vibration_factory);
+ RegisterFactory<MotionDevice>("sdl", motion_factory);
+
+ // If the frontend is going to manage the event loop, then we don't start one here
+ start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
return;
}
+ has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
}
@@ -494,7 +698,7 @@ SDLState::SDLState() {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
- std::this_thread::sleep_for(10ms);
+ std::this_thread::sleep_for(1ms);
}
});
}
@@ -509,6 +713,8 @@ SDLState::~SDLState() {
using namespace Input;
UnregisterFactory<ButtonDevice>("sdl");
UnregisterFactory<AnalogDevice>("sdl");
+ UnregisterFactory<VibrationDevice>("sdl");
+ UnregisterFactory<MotionDevice>("sdl");
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -520,65 +726,268 @@ SDLState::~SDLState() {
}
}
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
+ std::scoped_lock lock(joystick_map_mutex);
+ std::vector<Common::ParamPackage> devices;
+ for (const auto& [key, value] : joystick_map) {
+ for (const auto& joystick : value) {
+ if (auto* const controller = joystick->GetSDLGameController()) {
+ std::string name =
+ fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ } else if (auto* const joy = joystick->GetSDLJoystick()) {
+ std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ }
+ }
+ }
+ return devices;
+}
+
+namespace {
+Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
+ float value = 0.1f) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("axis", axis);
+ if (value > 0) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.5");
+ }
+ return params;
+}
+
+Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("button", button);
+ return params;
+}
+
+Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("hat", hat);
+ switch (value) {
+ case SDL_HAT_UP:
+ params.Set("direction", "up");
+ break;
+ case SDL_HAT_DOWN:
+ params.Set("direction", "down");
+ break;
+ case SDL_HAT_LEFT:
+ params.Set("direction", "left");
+ break;
+ case SDL_HAT_RIGHT:
+ params.Set("direction", "right");
+ break;
+ default:
+ return {};
+ }
+ return params;
+}
+
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jaxis.axis),
+ event.jaxis.value);
+ }
+ break;
+ }
+ case SDL_JOYBUTTONUP: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jbutton.button));
+ }
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jhat.hat),
+ static_cast<s32>(event.jhat.value));
+ }
+ break;
+ }
+ }
+ return {};
+}
+
+Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
switch (event.type) {
case SDL_JOYAXISMOTION: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis", event.jaxis.axis);
- if (event.jaxis.value > 0) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.5");
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jaxis.axis),
+ event.jaxis.value);
}
break;
}
case SDL_JOYBUTTONUP: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("button", event.jbutton.button);
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jbutton.button));
+ }
break;
}
case SDL_JOYHATMOTION: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("hat", event.jhat.hat);
- switch (event.jhat.value) {
- case SDL_HAT_UP:
- params.Set("direction", "up");
- break;
- case SDL_HAT_DOWN:
- params.Set("direction", "down");
- break;
- case SDL_HAT_LEFT:
- params.Set("direction", "left");
- break;
- case SDL_HAT_RIGHT:
- params.Set("direction", "right");
- break;
- default:
- return {};
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jhat.hat),
+ static_cast<s32>(event.jhat.value));
}
break;
}
}
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
+ const SDL_GameControllerButtonBind& binding) {
+ switch (binding.bindType) {
+ case SDL_CONTROLLER_BINDTYPE_NONE:
+ break;
+ case SDL_CONTROLLER_BINDTYPE_AXIS:
+ return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
+ case SDL_CONTROLLER_BINDTYPE_BUTTON:
+ return BuildButtonParamPackageForButton(port, guid, binding.value.button);
+ case SDL_CONTROLLER_BINDTYPE_HAT:
+ return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
+ binding.value.hat.hat_mask);
+ }
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
+ int axis_y) {
+ Common::ParamPackage params;
+ params.Set("engine", "sdl");
+ params.Set("port", port);
+ params.Set("guid", guid);
+ params.Set("axis_x", axis_x);
+ params.Set("axis_y", axis_y);
return params;
}
+} // Anonymous namespace
-namespace Polling {
+ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+ // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
+ // We will add those afterwards
+ // This list also excludes Screenshot since theres not really a mapping for that
+ using ButtonBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
+ static constexpr ButtonBindings switch_to_sdl_button{{
+ {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
+ {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
+ {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
+ {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
+ {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
+ {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
+ {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
+ {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
+ {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
+ {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
+ {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
+ {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
+ {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+ }};
+
+ // Add the missing bindings for ZL/ZR
+ using ZBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
+ static constexpr ZBindings switch_to_sdl_axis{{
+ {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
+ {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
+ }};
+
+ ButtonMapping mapping;
+ mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
+
+ for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
+ const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+ for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
+ const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+
+ return mapping;
+}
+
+AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+
+ AnalogMapping mapping = {};
+ const auto& binding_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
+ const auto& binding_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_left_x.value.axis,
+ binding_left_y.value.axis));
+ const auto& binding_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
+ const auto& binding_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_right_x.value.axis,
+ binding_right_y.value.axis));
+ return mapping;
+}
+
+namespace Polling {
class SDLPoller : public InputCommon::Polling::DevicePoller {
public:
explicit SDLPoller(SDLState& state_) : state(state_) {}
- void Start() override {
+ void Start([[maybe_unused]] const std::string& device_id) override {
state.event_queue.Clear();
state.polling = true;
}
@@ -598,70 +1007,116 @@ public:
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- switch (event.type) {
- case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
- break;
- }
- case SDL_JOYBUTTONUP:
- case SDL_JOYHATMOTION:
- return SDLEventToButtonParamPackage(state, event);
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
}
}
return {};
}
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToButtonParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
};
-class SDLAnalogPoller final : public SDLPoller {
+class SDLMotionPoller final : public SDLPoller {
public:
- explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
+ explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
+ }
+ }
+ return {};
+ }
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToMotionParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
+};
- void Start() override {
- SDLPoller::Start();
+/**
+ * Attempts to match the press to a controller joy axis (left/right stick) and if a match
+ * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
+ * instead
+ */
+class SDLAnalogPreferredPoller final : public SDLPoller {
+public:
+ explicit SDLAnalogPreferredPoller(SDLState& state_)
+ : SDLPoller(state_), button_poller(state_) {}
+ void Start(const std::string& device_id) override {
+ SDLPoller::Start(device_id);
// Reset stored axes
analog_x_axis = -1;
analog_y_axis = -1;
- analog_axes_joystick = -1;
}
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ // Filter out axis events that are below a threshold
+ if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
continue;
}
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second SDL event. The axes also must be from the same joystick.
- const int axis = event.jaxis.axis;
- if (analog_x_axis == -1) {
- analog_x_axis = axis;
- analog_axes_joystick = event.jaxis.which;
- } else if (analog_y_axis == -1 && analog_x_axis != axis &&
- analog_axes_joystick == event.jaxis.which) {
- analog_y_axis = axis;
+ if (event.type == SDL_JOYAXISMOTION) {
+ const auto axis = event.jaxis.axis;
+ // In order to return a complete analog param, we need inputs for both axes.
+ // First we take the x-axis (horizontal) input, then the y-axis (vertical) input.
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ } else if (analog_y_axis == -1 && analog_x_axis != axis) {
+ analog_y_axis = axis;
+ }
+ } else {
+ // If the press wasn't accepted as a joy axis, check for a button press
+ auto button_press = button_poller.FromEvent(event);
+ if (button_press) {
+ return *button_press;
+ }
}
}
- Common::ParamPackage params;
+
if (analog_x_axis != -1 && analog_y_axis != -1) {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("engine", "sdl");
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis_x", analog_x_axis);
- params.Set("axis_y", analog_y_axis);
- analog_x_axis = -1;
- analog_y_axis = -1;
- analog_axes_joystick = -1;
- return params;
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ analog_x_axis, analog_y_axis);
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ return params;
+ }
}
- return params;
+
+ return {};
}
private:
int analog_x_axis = -1;
int analog_y_axis = -1;
- SDL_JoystickID analog_axes_joystick = -1;
+ SDLButtonPoller button_poller;
};
} // namespace Polling
@@ -669,12 +1124,15 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
Pollers pollers;
switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
+ case InputCommon::Polling::DeviceType::AnalogPreferred:
+ pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
break;
case InputCommon::Polling::DeviceType::Button:
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
break;
+ case InputCommon::Polling::DeviceType::Motion:
+ pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
+ break;
}
return pollers;
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 606a32c5b..08044b00d 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -21,6 +21,8 @@ namespace InputCommon::SDL {
class SDLAnalogFactory;
class SDLButtonFactory;
+class SDLMotionFactory;
+class SDLVibrationFactory;
class SDLJoystick;
class SDLState : public State {
@@ -50,6 +52,11 @@ public:
std::atomic<bool> polling = false;
Common::SPSCQueue<SDL_Event> event_queue;
+ std::vector<Common::ParamPackage> GetInputDevices() override;
+
+ ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+ AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+
private:
void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick);
@@ -57,12 +64,17 @@ private:
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
+ // Set to true if SDL supports game controller subsystem
+ bool has_gamecontroller = false;
+
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
+ std::shared_ptr<SDLVibrationFactory> vibration_factory;
+ std::shared_ptr<SDLMotionFactory> motion_factory;
bool start_thread = false;
std::atomic<bool> initialized = false;
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
new file mode 100644
index 000000000..557e7a9a0
--- /dev/null
+++ b/src/input_common/settings.cpp
@@ -0,0 +1,47 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/settings.h"
+
+namespace Settings {
+namespace NativeButton {
+const std::array<const char*, NumButtons> mapping = {{
+ "button_a", "button_b", "button_x", "button_y", "button_lstick",
+ "button_rstick", "button_l", "button_r", "button_zl", "button_zr",
+ "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
+ "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot",
+}};
+}
+
+namespace NativeAnalog {
+const std::array<const char*, NumAnalogs> mapping = {{
+ "lstick",
+ "rstick",
+}};
+}
+
+namespace NativeVibration {
+const std::array<const char*, NumVibrations> mapping = {{
+ "left_vibration_device",
+ "right_vibration_device",
+}};
+}
+
+namespace NativeMotion {
+const std::array<const char*, NumMotions> mapping = {{
+ "motionleft",
+ "motionright",
+}};
+}
+
+namespace NativeMouseButton {
+const std::array<const char*, NumMouseButtons> mapping = {{
+ "left",
+ "right",
+ "middle",
+ "forward",
+ "back",
+}};
+}
+} // namespace Settings
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
new file mode 100644
index 000000000..75486554b
--- /dev/null
+++ b/src/input_common/settings.h
@@ -0,0 +1,371 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include "common/common_types.h"
+
+namespace Settings {
+namespace NativeButton {
+enum Values : int {
+ A,
+ B,
+ X,
+ Y,
+ LStick,
+ RStick,
+ L,
+ R,
+ ZL,
+ ZR,
+ Plus,
+ Minus,
+
+ DLeft,
+ DUp,
+ DRight,
+ DDown,
+
+ SL,
+ SR,
+
+ Home,
+ Screenshot,
+
+ NumButtons,
+};
+
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_NS_BEGIN = Home;
+
+constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
+
+extern const std::array<const char*, NumButtons> mapping;
+
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values : int {
+ LStick,
+ RStick,
+
+ NumAnalogs,
+};
+
+constexpr int STICK_HID_BEGIN = LStick;
+constexpr int STICK_HID_END = NumAnalogs;
+constexpr int NUM_STICKS_HID = NumAnalogs;
+
+extern const std::array<const char*, NumAnalogs> mapping;
+} // namespace NativeAnalog
+
+namespace NativeVibration {
+enum Values : int {
+ LeftVibrationDevice,
+ RightVibrationDevice,
+
+ NumVibrations,
+};
+
+constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
+constexpr int VIBRATION_HID_END = NumVibrations;
+constexpr int NUM_VIBRATIONS_HID = NumVibrations;
+
+extern const std::array<const char*, NumVibrations> mapping;
+}; // namespace NativeVibration
+
+namespace NativeMotion {
+enum Values : int {
+ MotionLeft,
+ MotionRight,
+
+ NumMotions,
+};
+
+constexpr int MOTION_HID_BEGIN = MotionLeft;
+constexpr int MOTION_HID_END = NumMotions;
+constexpr int NUM_MOTIONS_HID = NumMotions;
+
+extern const std::array<const char*, NumMotions> mapping;
+} // namespace NativeMotion
+
+namespace NativeMouseButton {
+enum Values {
+ Left,
+ Right,
+ Middle,
+ Forward,
+ Back,
+
+ NumMouseButtons,
+};
+
+constexpr int MOUSE_HID_BEGIN = Left;
+constexpr int MOUSE_HID_END = NumMouseButtons;
+constexpr int NUM_MOUSE_HID = NumMouseButtons;
+
+extern const std::array<const char*, NumMouseButtons> mapping;
+} // namespace NativeMouseButton
+
+namespace NativeKeyboard {
+enum Keys {
+ None,
+ Error,
+
+ A = 4,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ N1,
+ N2,
+ N3,
+ N4,
+ N5,
+ N6,
+ N7,
+ N8,
+ N9,
+ N0,
+ Enter,
+ Escape,
+ Backspace,
+ Tab,
+ Space,
+ Minus,
+ Equal,
+ LeftBrace,
+ RightBrace,
+ Backslash,
+ Tilde,
+ Semicolon,
+ Apostrophe,
+ Grave,
+ Comma,
+ Dot,
+ Slash,
+ CapsLockKey,
+
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+
+ SystemRequest,
+ ScrollLockKey,
+ Pause,
+ Insert,
+ Home,
+ PageUp,
+ Delete,
+ End,
+ PageDown,
+ Right,
+ Left,
+ Down,
+ Up,
+
+ NumLockKey,
+ KPSlash,
+ KPAsterisk,
+ KPMinus,
+ KPPlus,
+ KPEnter,
+ KP1,
+ KP2,
+ KP3,
+ KP4,
+ KP5,
+ KP6,
+ KP7,
+ KP8,
+ KP9,
+ KP0,
+ KPDot,
+
+ Key102,
+ Compose,
+ Power,
+ KPEqual,
+
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+
+ Open,
+ Help,
+ Properties,
+ Front,
+ Stop,
+ Repeat,
+ Undo,
+ Cut,
+ Copy,
+ Paste,
+ Find,
+ Mute,
+ VolumeUp,
+ VolumeDown,
+ CapsLockActive,
+ NumLockActive,
+ ScrollLockActive,
+ KPComma,
+
+ KPLeftParenthesis,
+ KPRightParenthesis,
+
+ LeftControlKey = 0xE0,
+ LeftShiftKey,
+ LeftAltKey,
+ LeftMetaKey,
+ RightControlKey,
+ RightShiftKey,
+ RightAltKey,
+ RightMetaKey,
+
+ MediaPlayPause,
+ MediaStopCD,
+ MediaPrevious,
+ MediaNext,
+ MediaEject,
+ MediaVolumeUp,
+ MediaVolumeDown,
+ MediaMute,
+ MediaWebsite,
+ MediaBack,
+ MediaForward,
+ MediaStop,
+ MediaFind,
+ MediaScrollUp,
+ MediaScrollDown,
+ MediaEdit,
+ MediaSleep,
+ MediaCoffee,
+ MediaRefresh,
+ MediaCalculator,
+
+ NumKeyboardKeys,
+};
+
+static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
+
+enum Modifiers {
+ LeftControl,
+ LeftShift,
+ LeftAlt,
+ LeftMeta,
+ RightControl,
+ RightShift,
+ RightAlt,
+ RightMeta,
+ CapsLock,
+ ScrollLock,
+ NumLock,
+
+ NumKeyboardMods,
+};
+
+constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
+constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
+constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
+
+constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
+constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
+constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
+
+} // namespace NativeKeyboard
+
+using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
+using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
+using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
+using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
+
+using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
+using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
+
+constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
+constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
+constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
+constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
+
+enum class ControllerType {
+ ProController,
+ DualJoyconDetached,
+ LeftJoycon,
+ RightJoycon,
+ Handheld,
+};
+
+struct PlayerInput {
+ bool connected;
+ ControllerType controller_type;
+ ButtonsRaw buttons;
+ AnalogsRaw analogs;
+ VibrationsRaw vibrations;
+ MotionsRaw motions;
+
+ bool vibration_enabled;
+ int vibration_strength;
+
+ u32 body_color_left;
+ u32 body_color_right;
+ u32 button_color_left;
+ u32 button_color_right;
+};
+
+struct TouchscreenInput {
+ bool enabled;
+ std::string device;
+
+ u32 finger;
+ u32 diameter_x;
+ u32 diameter_y;
+ u32 rotation_angle;
+};
+} // namespace Settings
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..a07124a86
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,51 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/touch_from_button.h"
+
+namespace InputCommon {
+
+class TouchFromButtonDevice final : public Input::TouchDevice {
+public:
+ TouchFromButtonDevice() {
+ const auto button_index =
+ static_cast<std::size_t>(Settings::values.touch_from_button_map_index);
+ const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
+
+ for (const auto& config_entry : buttons) {
+ const Common::ParamPackage package{config_entry};
+ map.emplace_back(
+ Input::CreateDevice<Input::ButtonDevice>(config_entry),
+ std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
+ std::clamp(package.Get("y", 0), 0,
+ static_cast<int>(Layout::ScreenUndocked::Height)));
+ }
+ }
+
+ std::tuple<float, float, bool> GetStatus() const override {
+ for (const auto& m : map) {
+ const bool state = std::get<0>(m)->GetStatus();
+ if (state) {
+ const float x = static_cast<float>(std::get<1>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Width);
+ const float y = static_cast<float>(std::get<2>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Height);
+ return {x, y, true};
+ }
+ }
+ return {};
+ }
+
+private:
+ // A vector of the mapped button, its x and its y-coordinate
+ std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
+};
+
+std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
+ return std::make_unique<TouchFromButtonDevice>();
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * A touch device factory that takes a list of button devices and combines them into a touch device.
+ */
+class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ /**
+ * Creates a touch device from a list of button devices
+ */
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index da5227058..c0bb90048 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,15 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <array>
#include <chrono>
#include <cstring>
#include <functional>
#include <thread>
#include <boost/asio.hpp>
-#include <boost/bind.hpp>
#include "common/logging/log.h"
+#include "core/settings.h"
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
@@ -28,11 +26,11 @@ class Socket {
public:
using clock = std::chrono::system_clock;
- explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- SocketCallback callback)
- : callback(std::move(callback)), timer(io_service),
- socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
- pad_index(pad_index) {
+ explicit Socket(const std::string& host, u16 port, std::size_t pad_index_, u32 client_id_,
+ SocketCallback callback_)
+ : callback(std::move(callback_)), timer(io_service),
+ socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id_),
+ pad_index(pad_index_) {
boost::system::error_code ec{};
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
if (ec.value() != boost::system::errc::success) {
@@ -65,7 +63,7 @@ public:
}
private:
- void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
+ void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
switch (*type) {
case Type::Version: {
@@ -92,16 +90,20 @@ private:
StartReceive();
}
- void HandleSend(const boost::system::error_code& error) {
+ void HandleSend(const boost::system::error_code&) {
boost::system::error_code _ignored{};
// Send a request for getting port info for the pad
- Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
+ const Request::PortInfo port_info{1, {static_cast<u8>(pad_index), 0, 0, 0}};
const auto port_message = Request::Create(port_info, client_id);
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
// Send a request for getting pad data for the pad
- Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
+ const Request::PadData pad_data{
+ Request::PadData::Flags::Id,
+ static_cast<u8>(pad_index),
+ EMPTY_MAC_ADDRESS,
+ };
const auto pad_message = Request::Create(pad_data, client_id);
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
@@ -114,7 +116,7 @@ private:
udp::socket socket;
u32 client_id{};
- u8 pad_index{};
+ std::size_t pad_index{};
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
@@ -132,49 +134,100 @@ static void SocketLoop(Socket* socket) {
socket->Loop();
}
-Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
- u8 pad_index, u32 client_id)
- : status(std::move(status)) {
- StartCommunication(host, port, pad_index, client_id);
+Client::Client() {
+ LOG_INFO(Input, "Udp Initialization started");
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ const auto pad = client % 4;
+ StartCommunication(client, Settings::values.udp_input_address,
+ Settings::values.udp_input_port, pad, 24872);
+ // Set motion parameters
+ // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
+ // Real HW values are unknown, 0.0001 is an approximate to Standard
+ clients[client].motion.SetGyroThreshold(0.0001f);
+ }
}
Client::~Client() {
- socket->Stop();
- thread.join();
+ Reset();
+}
+
+std::vector<Common::ParamPackage> Client::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ if (!DeviceConnected(client)) {
+ continue;
+ }
+ std::string name = fmt::format("UDP Controller {}", client);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "cemuhookudp"},
+ {"display", std::move(name)},
+ {"port", std::to_string(client)},
+ });
+ }
+ return devices;
}
-void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
- socket->Stop();
- thread.join();
- StartCommunication(host, port, pad_index, client_id);
+bool Client::DeviceConnected(std::size_t pad) const {
+ // Use last timestamp to detect if the socket has stopped sending data
+ const auto now = std::chrono::system_clock::now();
+ const auto time_difference = static_cast<u64>(
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
+ .count());
+ return time_difference < 1000 && clients[pad].active == 1;
}
-void Client::OnVersion(Response::Version data) {
+void Client::ReloadUDPClient() {
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
+ }
+}
+void Client::ReloadSocket(const std::string& host, u16 port, std::size_t pad_index, u32 client_id) {
+ // client number must be determined from host / port and pad index
+ const std::size_t client = pad_index;
+ clients[client].socket->Stop();
+ clients[client].thread.join();
+ StartCommunication(client, host, port, pad_index, client_id);
+}
+
+void Client::OnVersion([[maybe_unused]] Response::Version data) {
LOG_TRACE(Input, "Version packet received: {}", data.version);
}
-void Client::OnPortInfo(Response::PortInfo data) {
+void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
}
void Client::OnPadData(Response::PadData data) {
+ // Client number must be determined from host / port and pad index
+ const std::size_t client = data.info.id;
LOG_TRACE(Input, "PadData packet received");
- if (data.packet_counter <= packet_sequence) {
+ if (data.packet_counter == clients[client].packet_sequence) {
LOG_WARNING(
Input,
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
- packet_sequence, data.packet_counter);
+ clients[client].packet_sequence, data.packet_counter);
return;
}
- packet_sequence = data.packet_counter;
- // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
- // directions correspond to the ones of the Switch
- Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
- Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
- {
- std::lock_guard guard(status->update_mutex);
+ clients[client].active = data.info.is_pad_active;
+ clients[client].packet_sequence = data.packet_counter;
+ const auto now = std::chrono::system_clock::now();
+ const auto time_difference =
+ static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>(
+ now - clients[client].last_motion_update)
+ .count());
+ clients[client].last_motion_update = now;
+ const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
+ clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
+ // Gyroscope values are not it the correct scale from better joy.
+ // Dividing by 312 allows us to make one full turn = 1 turn
+ // This must be a configurable valued called sensitivity
+ clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
+ clients[client].motion.UpdateRotation(time_difference);
+ clients[client].motion.UpdateOrientation(time_difference);
- status->motion_status = {accel, gyro};
+ {
+ std::lock_guard guard(clients[client].status.update_mutex);
+ clients[client].status.motion_status = clients[client].motion.GetMotion();
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
// between a simple "tap" and a hard press that causes the touch screen to click.
@@ -183,41 +236,115 @@ void Client::OnPadData(Response::PadData data) {
float x = 0;
float y = 0;
- if (is_active && status->touch_calibration) {
- const u16 min_x = status->touch_calibration->min_x;
- const u16 max_x = status->touch_calibration->max_x;
- const u16 min_y = status->touch_calibration->min_y;
- const u16 max_y = status->touch_calibration->max_y;
+ if (is_active && clients[client].status.touch_calibration) {
+ const u16 min_x = clients[client].status.touch_calibration->min_x;
+ const u16 max_x = clients[client].status.touch_calibration->max_x;
+ const u16 min_y = clients[client].status.touch_calibration->min_y;
+ const u16 max_y = clients[client].status.touch_calibration->max_y;
- x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
+ x = static_cast<float>(std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) -
+ min_x) /
static_cast<float>(max_x - min_x);
- y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
+ y = static_cast<float>(std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) -
+ min_y) /
static_cast<float>(max_y - min_y);
}
- status->touch_status = {x, y, is_active};
+ clients[client].status.touch_status = {x, y, is_active};
+
+ if (configuring) {
+ const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
+ const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
+ UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
+ }
}
}
-void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
+void Client::StartCommunication(std::size_t client, const std::string& host, u16 port,
+ std::size_t pad_index, u32 client_id) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this](Response::PadData data) { OnPadData(data); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
- socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
- thread = std::thread{SocketLoop, this->socket.get()};
+ clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
+ clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
+}
+
+void Client::Reset() {
+ for (auto& client : clients) {
+ client.socket->Stop();
+ client.thread.join();
+ }
+}
+
+void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro, bool touch) {
+ if (gyro.Length() > 0.2f) {
+ LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {}), touch={}",
+ client, gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2], touch);
+ }
+ UDPPadStatus pad;
+ if (touch) {
+ pad.touch = PadTouch::Click;
+ pad_queue[client].Push(pad);
+ }
+ for (size_t i = 0; i < 3; ++i) {
+ if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
+ pad.motion = static_cast<PadMotion>(i);
+ pad.motion_value = gyro[i];
+ pad_queue[client].Push(pad);
+ }
+ if (acc[i] > 1.75f || acc[i] < -1.75f) {
+ pad.motion = static_cast<PadMotion>(i + 3);
+ pad.motion_value = acc[i];
+ pad_queue[client].Push(pad);
+ }
+ }
+}
+
+void Client::BeginConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = true;
+}
+
+void Client::EndConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = false;
}
-void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- std::function<void()> success_callback,
- std::function<void()> failure_callback) {
+DeviceStatus& Client::GetPadState(std::size_t pad) {
+ return clients[pad].status;
+}
+
+const DeviceStatus& Client::GetPadState(std::size_t pad) const {
+ return clients[pad].status;
+}
+
+std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
+ return pad_queue;
+}
+
+const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
+ return pad_queue;
+}
+
+void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
+ const std::function<void()>& success_callback,
+ const std::function<void()>& failure_callback) {
std::thread([=] {
Common::Event success_event;
- SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
- [&](Response::PadData data) { success_event.Set(); }};
+ SocketCallback callback{
+ .version = [](Response::Version) {},
+ .port_info = [](Response::PortInfo) {},
+ .pad_data = [&](Response::PadData) { success_event.Set(); },
+ };
Socket socket{host, port, pad_index, client_id, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
- bool result = success_event.WaitFor(std::chrono::seconds(8));
+ const bool result = success_event.WaitFor(std::chrono::seconds(5));
socket.Stop();
worker_thread.join();
if (result) {
@@ -225,16 +352,15 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
} else {
failure_callback();
}
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
- const std::string& host, u16 port, u8 pad_index, u32 client_id,
+ const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback) {
- std::thread([=] {
+ std::thread([=, this] {
constexpr u16 CALIBRATION_THRESHOLD = 100;
u16 min_x{UINT16_MAX};
@@ -243,14 +369,14 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
u16 max_y{};
Status current_status{Status::Initialized};
- SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
+ SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
[&](Response::PadData data) {
if (current_status == Status::Initialized) {
// Receiving data means the communication is ready now
current_status = Status::Ready;
status_callback(current_status);
}
- if (!data.touch_1.is_active) {
+ if (data.touch_1.is_active == 0) {
return;
}
LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
@@ -280,8 +406,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
complete_event.Wait();
socket.Stop();
worker_thread.join();
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..747e0c0a2 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,8 +12,12 @@
#include <thread>
#include <tuple>
#include "common/common_types.h"
+#include "common/param_package.h"
#include "common/thread.h"
+#include "common/threadsafe_queue.h"
#include "common/vector_math.h"
+#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
namespace InputCommon::CemuhookUDP {
@@ -28,9 +32,30 @@ struct PortInfo;
struct Version;
} // namespace Response
+enum class PadMotion {
+ GyroX,
+ GyroY,
+ GyroZ,
+ AccX,
+ AccY,
+ AccZ,
+ Undefined,
+};
+
+enum class PadTouch {
+ Click,
+ Undefined,
+};
+
+struct UDPPadStatus {
+ PadTouch touch{PadTouch::Undefined};
+ PadMotion motion{PadMotion::Undefined};
+ f32 motion_value{0.0f};
+};
+
struct DeviceStatus {
std::mutex update_mutex;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
+ Input::MotionStatus motion_status;
std::tuple<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
@@ -45,22 +70,58 @@ struct DeviceStatus {
class Client {
public:
- explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
- u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
+ // Initialize the UDP client capture and read sequence
+ Client();
+
+ // Close and release the client
~Client();
- void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
- u32 client_id = 24872);
+
+ // Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ bool DeviceConnected(std::size_t pad) const;
+ void ReloadUDPClient();
+ void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760,
+ std::size_t pad_index = 0, u32 client_id = 24872);
+
+ std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
+ const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
+
+ DeviceStatus& GetPadState(std::size_t pad);
+ const DeviceStatus& GetPadState(std::size_t pad) const;
private:
+ struct ClientData {
+ std::unique_ptr<Socket> socket;
+ DeviceStatus status;
+ std::thread thread;
+ u64 packet_sequence = 0;
+ u8 active = 0;
+
+ // Realtime values
+ // motion is initalized with PID values for drift correction on joycons
+ InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
+ std::chrono::time_point<std::chrono::system_clock> last_motion_update;
+ };
+
+ // For shutting down, clear all data, join all threads, release usb
+ void Reset();
+
void OnVersion(Response::Version);
void OnPortInfo(Response::PortInfo);
void OnPadData(Response::PadData);
- void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
+ void StartCommunication(std::size_t client, const std::string& host, u16 port,
+ std::size_t pad_index, u32 client_id);
+ void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro, bool touch);
+
+ bool configuring = false;
- std::unique_ptr<Socket> socket;
- std::shared_ptr<DeviceStatus> status;
- std::thread thread;
- u64 packet_sequence = 0;
+ std::array<ClientData, 4> clients;
+ std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
};
/// An async job allowing configuration of the touchpad calibration.
@@ -78,7 +139,7 @@ public:
* @param status_callback Callback for job status updates
* @param data_callback Called when calibration data is ready
*/
- explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
+ explicit CalibrationConfigurationJob(const std::string& host, u16 port, std::size_t pad_index,
u32 client_id, std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback);
~CalibrationConfigurationJob();
@@ -88,8 +149,8 @@ private:
Common::Event complete_event;
};
-void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- std::function<void()> success_callback,
- std::function<void()> failure_callback);
+void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
+ const std::function<void()>& success_callback,
+ const std::function<void()>& failure_callback);
} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
index 3ba4d1fc8..fc1aea4b9 100644
--- a/src/input_common/udp/protocol.h
+++ b/src/input_common/udp/protocol.h
@@ -7,7 +7,16 @@
#include <array>
#include <optional>
#include <type_traits>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4701)
+#endif
#include <boost/crc.hpp>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/bit_field.h"
#include "common/swap.h"
@@ -93,7 +102,7 @@ static_assert(std::is_trivially_copyable_v<PadData>,
/**
* Creates a message with the proper header data that can be sent to the server.
- * @param T data Request body to send
+ * @param data Request body to send
* @param client_id ID of the udp client (usually not checked on the server)
*/
template <typename T>
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 8c6ef1394..71a76a7aa 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,99 +1,142 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
-#include <optional>
-#include <tuple>
-
-#include "common/param_package.h"
-#include "core/frontend/input.h"
-#include "core/settings.h"
+#include <utility>
+#include "common/assert.h"
+#include "common/threadsafe_queue.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class UDPTouchDevice final : public Input::TouchDevice {
+class UDPMotion final : public Input::MotionDevice {
public:
- explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<float, float, bool> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->touch_status;
+ explicit UDPMotion(std::string ip_, int port_, u32 pad_, CemuhookUDP::Client* client_)
+ : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ return client->GetPadState(pad).motion_status;
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ const int port;
+ const u32 pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-class UDPMotionDevice final : public Input::MotionDevice {
-public:
- explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->motion_status;
- }
+/// A motion device factory that creates motion devices from JC Adapter
+UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
+ auto ip = params.Get("ip", "127.0.0.1");
+ const auto port = params.Get("port", 26760);
+ const auto pad = static_cast<u32>(params.Get("pad_index", 0));
+
+ return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
+void UDPMotionFactory::BeginConfiguration() {
+ polling = true;
+ client->BeginConfiguration();
+}
-class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
- explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
-
- std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
- {
- std::lock_guard guard(status->update_mutex);
- status->touch_calibration = DeviceStatus::CalibrationData{};
- // These default values work well for DS4 but probably not other touch inputs
- status->touch_calibration->min_x = params.Get("min_x", 100);
- status->touch_calibration->min_y = params.Get("min_y", 50);
- status->touch_calibration->max_x = params.Get("max_x", 1800);
- status->touch_calibration->max_y = params.Get("max_y", 850);
+void UDPMotionFactory::EndConfiguration() {
+ polling = false;
+ client->EndConfiguration();
+}
+
+Common::ParamPackage UDPMotionFactory::GetNextInput() {
+ Common::ParamPackage params;
+ CemuhookUDP::UDPPadStatus pad;
+ auto& queue = client->GetPadQueue();
+ for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+ while (queue[pad_number].Pop(pad)) {
+ if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
+ continue;
+ }
+ params.Set("engine", "cemuhookudp");
+ params.Set("ip", "127.0.0.1");
+ params.Set("port", 26760);
+ params.Set("pad_index", static_cast<int>(pad_number));
+ params.Set("motion", static_cast<u16>(pad.motion));
+ return params;
}
- return std::make_unique<UDPTouchDevice>(status);
}
+ return params;
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
-
-class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
+class UDPTouch final : public Input::TouchDevice {
public:
- explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+ explicit UDPTouch(std::string ip_, int port_, u32 pad_, CemuhookUDP::Client* client_)
+ : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
- std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
- return std::make_unique<UDPMotionDevice>(status);
+ std::tuple<float, float, bool> GetStatus() const override {
+ return client->GetPadState(pad).touch_status;
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ const int port;
+ const u32 pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-State::State() {
- auto status = std::make_shared<DeviceStatus>();
- client =
- std::make_unique<Client>(status, Settings::values.udp_input_address,
- Settings::values.udp_input_port, Settings::values.udp_pad_index);
-
- Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
- std::make_shared<UDPTouchFactory>(status));
- Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
- std::make_shared<UDPMotionFactory>(status));
+/// A motion device factory that creates motion devices from JC Adapter
+UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
+ auto ip = params.Get("ip", "127.0.0.1");
+ const auto port = params.Get("port", 26760);
+ const auto pad = static_cast<u32>(params.Get("pad_index", 0));
+
+ return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
}
-State::~State() {
- Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
- Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+void UDPTouchFactory::BeginConfiguration() {
+ polling = true;
+ client->BeginConfiguration();
}
-void State::ReloadUDPClient() {
- client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
- Settings::values.udp_pad_index);
+void UDPTouchFactory::EndConfiguration() {
+ polling = false;
+ client->EndConfiguration();
}
-std::unique_ptr<State> Init() {
- return std::make_unique<State>();
+Common::ParamPackage UDPTouchFactory::GetNextInput() {
+ Common::ParamPackage params;
+ CemuhookUDP::UDPPadStatus pad;
+ auto& queue = client->GetPadQueue();
+ for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+ while (queue[pad_number].Pop(pad)) {
+ if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
+ continue;
+ }
+ params.Set("engine", "cemuhookudp");
+ params.Set("ip", "127.0.0.1");
+ params.Set("port", 26760);
+ params.Set("pad_index", static_cast<int>(pad_number));
+ params.Set("touch", static_cast<u16>(pad.touch));
+ return params;
+ }
+ }
+ return params;
}
-} // namespace InputCommon::CemuhookUDP
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 4f83f0441..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,25 +1,57 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
+#include "core/frontend/input.h"
+#include "input_common/udp/client.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class Client;
-
-class State {
+/// A motion device factory that creates motion devices from udp clients
+class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
- State();
- ~State();
- void ReloadUDPClient();
+ explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
private:
- std::unique_ptr<Client> client;
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
};
-std::unique_ptr<State> Init();
+/// A touch device factory that creates touch devices from udp clients
+class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
+};
-} // namespace InputCommon::CemuhookUDP
+} // namespace InputCommon
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c7038b217..47ef30aa9 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,6 +1,7 @@
add_executable(tests
common/bit_field.cpp
common/bit_utils.cpp
+ common/fibers.cpp
common/multi_level_queue.cpp
common/param_package.cpp
common/ring_buffer.cpp
diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp
new file mode 100644
index 000000000..4757dd2b4
--- /dev/null
+++ b/src/tests/common/fibers.cpp
@@ -0,0 +1,367 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <cstdlib>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <catch2/catch.hpp>
+
+#include "common/common_types.h"
+#include "common/fiber.h"
+
+namespace Common {
+
+class ThreadIds {
+public:
+ void Register(u32 id) {
+ const auto thread_id = std::this_thread::get_id();
+ std::scoped_lock lock{mutex};
+ if (ids.contains(thread_id)) {
+ throw std::logic_error{"Registering the same thread twice"};
+ }
+ ids.emplace(thread_id, id);
+ }
+
+ [[nodiscard]] u32 Get() const {
+ std::scoped_lock lock{mutex};
+ return ids.at(std::this_thread::get_id());
+ }
+
+private:
+ mutable std::mutex mutex;
+ std::unordered_map<std::thread::id, u32> ids;
+};
+
+class TestControl1 {
+public:
+ TestControl1() = default;
+
+ void DoWork();
+
+ void ExecuteThread(u32 id);
+
+ ThreadIds thread_ids;
+ std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
+ std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
+ std::vector<u32> items;
+ std::vector<u32> results;
+};
+
+static void WorkControl1(void* control) {
+ auto* test_control = static_cast<TestControl1*>(control);
+ test_control->DoWork();
+}
+
+void TestControl1::DoWork() {
+ const u32 id = thread_ids.Get();
+ u32 value = items[id];
+ for (u32 i = 0; i < id; i++) {
+ value++;
+ }
+ results[id] = value;
+ Fiber::YieldTo(work_fibers[id], thread_fibers[id]);
+}
+
+void TestControl1::ExecuteThread(u32 id) {
+ thread_ids.Register(id);
+ auto thread_fiber = Fiber::ThreadToFiber();
+ thread_fibers[id] = thread_fiber;
+ work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this);
+ items[id] = rand() % 256;
+ Fiber::YieldTo(thread_fibers[id], work_fibers[id]);
+ thread_fibers[id]->Exit();
+}
+
+static void ThreadStart1(u32 id, TestControl1& test_control) {
+ test_control.ExecuteThread(id);
+}
+
+/** This test checks for fiber setup configuration and validates that fibers are
+ * doing all the work required.
+ */
+TEST_CASE("Fibers::Setup", "[common]") {
+ constexpr std::size_t num_threads = 7;
+ TestControl1 test_control{};
+ test_control.thread_fibers.resize(num_threads);
+ test_control.work_fibers.resize(num_threads);
+ test_control.items.resize(num_threads, 0);
+ test_control.results.resize(num_threads, 0);
+ std::vector<std::thread> threads;
+ for (u32 i = 0; i < num_threads; i++) {
+ threads.emplace_back(ThreadStart1, i, std::ref(test_control));
+ }
+ for (u32 i = 0; i < num_threads; i++) {
+ threads[i].join();
+ }
+ for (u32 i = 0; i < num_threads; i++) {
+ REQUIRE(test_control.items[i] + i == test_control.results[i]);
+ }
+}
+
+class TestControl2 {
+public:
+ TestControl2() = default;
+
+ void DoWork1() {
+ trap2 = false;
+ while (trap.load())
+ ;
+ for (u32 i = 0; i < 12000; i++) {
+ value1 += i;
+ }
+ Fiber::YieldTo(fiber1, fiber3);
+ const u32 id = thread_ids.Get();
+ assert1 = id == 1;
+ value2 += 5000;
+ Fiber::YieldTo(fiber1, thread_fibers[id]);
+ }
+
+ void DoWork2() {
+ while (trap2.load())
+ ;
+ value2 = 2000;
+ trap = false;
+ Fiber::YieldTo(fiber2, fiber1);
+ assert3 = false;
+ }
+
+ void DoWork3() {
+ const u32 id = thread_ids.Get();
+ assert2 = id == 0;
+ value1 += 1000;
+ Fiber::YieldTo(fiber3, thread_fibers[id]);
+ }
+
+ void ExecuteThread(u32 id);
+
+ void CallFiber1() {
+ const u32 id = thread_ids.Get();
+ Fiber::YieldTo(thread_fibers[id], fiber1);
+ }
+
+ void CallFiber2() {
+ const u32 id = thread_ids.Get();
+ Fiber::YieldTo(thread_fibers[id], fiber2);
+ }
+
+ void Exit();
+
+ bool assert1{};
+ bool assert2{};
+ bool assert3{true};
+ u32 value1{};
+ u32 value2{};
+ std::atomic<bool> trap{true};
+ std::atomic<bool> trap2{true};
+ ThreadIds thread_ids;
+ std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
+ std::shared_ptr<Common::Fiber> fiber1;
+ std::shared_ptr<Common::Fiber> fiber2;
+ std::shared_ptr<Common::Fiber> fiber3;
+};
+
+static void WorkControl2_1(void* control) {
+ auto* test_control = static_cast<TestControl2*>(control);
+ test_control->DoWork1();
+}
+
+static void WorkControl2_2(void* control) {
+ auto* test_control = static_cast<TestControl2*>(control);
+ test_control->DoWork2();
+}
+
+static void WorkControl2_3(void* control) {
+ auto* test_control = static_cast<TestControl2*>(control);
+ test_control->DoWork3();
+}
+
+void TestControl2::ExecuteThread(u32 id) {
+ thread_ids.Register(id);
+ auto thread_fiber = Fiber::ThreadToFiber();
+ thread_fibers[id] = thread_fiber;
+}
+
+void TestControl2::Exit() {
+ const u32 id = thread_ids.Get();
+ thread_fibers[id]->Exit();
+}
+
+static void ThreadStart2_1(u32 id, TestControl2& test_control) {
+ test_control.ExecuteThread(id);
+ test_control.CallFiber1();
+ test_control.Exit();
+}
+
+static void ThreadStart2_2(u32 id, TestControl2& test_control) {
+ test_control.ExecuteThread(id);
+ test_control.CallFiber2();
+ test_control.Exit();
+}
+
+/** This test checks for fiber thread exchange configuration and validates that fibers are
+ * that a fiber has been succesfully transfered from one thread to another and that the TLS
+ * region of the thread is kept while changing fibers.
+ */
+TEST_CASE("Fibers::InterExchange", "[common]") {
+ TestControl2 test_control{};
+ test_control.thread_fibers.resize(2);
+ test_control.fiber1 =
+ std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_1}, &test_control);
+ test_control.fiber2 =
+ std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_2}, &test_control);
+ test_control.fiber3 =
+ std::make_shared<Fiber>(std::function<void(void*)>{WorkControl2_3}, &test_control);
+ std::thread thread1(ThreadStart2_1, 0, std::ref(test_control));
+ std::thread thread2(ThreadStart2_2, 1, std::ref(test_control));
+ thread1.join();
+ thread2.join();
+ REQUIRE(test_control.assert1);
+ REQUIRE(test_control.assert2);
+ REQUIRE(test_control.assert3);
+ REQUIRE(test_control.value2 == 7000);
+ u32 cal_value = 0;
+ for (u32 i = 0; i < 12000; i++) {
+ cal_value += i;
+ }
+ cal_value += 1000;
+ REQUIRE(test_control.value1 == cal_value);
+}
+
+class TestControl3 {
+public:
+ TestControl3() = default;
+
+ void DoWork1() {
+ value1 += 1;
+ Fiber::YieldTo(fiber1, fiber2);
+ const u32 id = thread_ids.Get();
+ value3 += 1;
+ Fiber::YieldTo(fiber1, thread_fibers[id]);
+ }
+
+ void DoWork2() {
+ value2 += 1;
+ const u32 id = thread_ids.Get();
+ Fiber::YieldTo(fiber2, thread_fibers[id]);
+ }
+
+ void ExecuteThread(u32 id);
+
+ void CallFiber1() {
+ const u32 id = thread_ids.Get();
+ Fiber::YieldTo(thread_fibers[id], fiber1);
+ }
+
+ void Exit();
+
+ u32 value1{};
+ u32 value2{};
+ u32 value3{};
+ ThreadIds thread_ids;
+ std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
+ std::shared_ptr<Common::Fiber> fiber1;
+ std::shared_ptr<Common::Fiber> fiber2;
+};
+
+static void WorkControl3_1(void* control) {
+ auto* test_control = static_cast<TestControl3*>(control);
+ test_control->DoWork1();
+}
+
+static void WorkControl3_2(void* control) {
+ auto* test_control = static_cast<TestControl3*>(control);
+ test_control->DoWork2();
+}
+
+void TestControl3::ExecuteThread(u32 id) {
+ thread_ids.Register(id);
+ auto thread_fiber = Fiber::ThreadToFiber();
+ thread_fibers[id] = thread_fiber;
+}
+
+void TestControl3::Exit() {
+ const u32 id = thread_ids.Get();
+ thread_fibers[id]->Exit();
+}
+
+static void ThreadStart3(u32 id, TestControl3& test_control) {
+ test_control.ExecuteThread(id);
+ test_control.CallFiber1();
+ test_control.Exit();
+}
+
+/** This test checks for one two threads racing for starting the same fiber.
+ * It checks execution occured in an ordered manner and by no time there were
+ * two contexts at the same time.
+ */
+TEST_CASE("Fibers::StartRace", "[common]") {
+ TestControl3 test_control{};
+ test_control.thread_fibers.resize(2);
+ test_control.fiber1 =
+ std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_1}, &test_control);
+ test_control.fiber2 =
+ std::make_shared<Fiber>(std::function<void(void*)>{WorkControl3_2}, &test_control);
+ std::thread thread1(ThreadStart3, 0, std::ref(test_control));
+ std::thread thread2(ThreadStart3, 1, std::ref(test_control));
+ thread1.join();
+ thread2.join();
+ REQUIRE(test_control.value1 == 1);
+ REQUIRE(test_control.value2 == 1);
+ REQUIRE(test_control.value3 == 1);
+}
+
+class TestControl4;
+
+static void WorkControl4(void* control);
+
+class TestControl4 {
+public:
+ TestControl4() {
+ fiber1 = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl4}, this);
+ goal_reached = false;
+ rewinded = false;
+ }
+
+ void Execute() {
+ thread_fiber = Fiber::ThreadToFiber();
+ Fiber::YieldTo(thread_fiber, fiber1);
+ thread_fiber->Exit();
+ }
+
+ void DoWork() {
+ fiber1->SetRewindPoint(std::function<void(void*)>{WorkControl4}, this);
+ if (rewinded) {
+ goal_reached = true;
+ Fiber::YieldTo(fiber1, thread_fiber);
+ }
+ rewinded = true;
+ fiber1->Rewind();
+ }
+
+ std::shared_ptr<Common::Fiber> fiber1;
+ std::shared_ptr<Common::Fiber> thread_fiber;
+ bool goal_reached;
+ bool rewinded;
+};
+
+static void WorkControl4(void* control) {
+ auto* test_control = static_cast<TestControl4*>(control);
+ test_control->DoWork();
+}
+
+TEST_CASE("Fibers::Rewind", "[common]") {
+ TestControl4 test_control{};
+ test_control.Execute();
+ REQUIRE(test_control.goal_reached);
+ REQUIRE(test_control.rewinded);
+}
+
+} // namespace Common
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 17043346b..e54674d11 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -6,6 +6,7 @@
#include "common/page_table.h"
#include "core/core.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/memory.h"
#include "tests/core/arm/arm_test_common.h"
@@ -18,12 +19,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
auto& system = Core::System::GetInstance();
auto process = Kernel::Process::Create(system, "", Kernel::Process::ProcessType::Userland);
- page_table = &process->VMManager().page_table;
-
- std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
- page_table->special_regions.clear();
- std::fill(page_table->attributes.begin(), page_table->attributes.end(),
- Common::PageType::Unmapped);
+ page_table = &process->PageTable().PageTableImpl();
system.Memory().MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
system.Memory().MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index 1e3940801..b35459152 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -6,6 +6,7 @@
#include <array>
#include <bitset>
+#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
@@ -14,32 +15,29 @@
#include "core/core.h"
#include "core/core_timing.h"
+namespace {
// Numbers are chosen randomly to make sure the correct one is given.
-static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
-static constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
+constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
+constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
+std::array<s64, 5> delays{};
-static std::bitset<CB_IDS.size()> callbacks_ran_flags;
-static u64 expected_callback = 0;
-static s64 lateness = 0;
+std::bitset<CB_IDS.size()> callbacks_ran_flags;
+u64 expected_callback = 0;
template <unsigned int IDX>
-void CallbackTemplate(u64 userdata, s64 cycles_late) {
+void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
static_assert(IDX < CB_IDS.size(), "IDX out of range");
callbacks_ran_flags.set(IDX);
- REQUIRE(CB_IDS[IDX] == userdata);
- REQUIRE(CB_IDS[IDX] == expected_callback);
- REQUIRE(lateness == cycles_late);
-}
-
-static u64 callbacks_done = 0;
-
-void EmptyCallback(u64 userdata, s64 cycles_late) {
- ++callbacks_done;
+ REQUIRE(CB_IDS[IDX] == user_data);
+ REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
+ delays[IDX] = ns_late.count();
+ ++expected_callback;
}
struct ScopeInit final {
ScopeInit() {
- core_timing.Initialize();
+ core_timing.SetMulticore(true);
+ core_timing.Initialize([]() {});
}
~ScopeInit() {
core_timing.Shutdown();
@@ -48,109 +46,102 @@ struct ScopeInit final {
Core::Timing::CoreTiming core_timing;
};
-static void AdvanceAndCheck(Core::Timing::CoreTiming& core_timing, u32 idx, u32 context = 0,
- int expected_lateness = 0, int cpu_downcount = 0) {
- callbacks_ran_flags = 0;
- expected_callback = CB_IDS[idx];
- lateness = expected_lateness;
-
- // Pretend we executed X cycles of instructions.
- core_timing.SwitchContext(context);
- core_timing.AddTicks(core_timing.GetDowncount() - cpu_downcount);
- core_timing.Advance();
- core_timing.SwitchContext((context + 1) % 4);
-
- REQUIRE(decltype(callbacks_ran_flags)().set(idx) == callbacks_ran_flags);
+u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) {
+ const u64 start = core_timing.GetGlobalTimeNs().count();
+ volatile u64 placebo = 0;
+ for (std::size_t i = 0; i < 1000; i++) {
+ placebo = placebo + core_timing.GetGlobalTimeNs().count();
+ }
+ const u64 end = core_timing.GetGlobalTimeNs().count();
+ return end - start;
}
+} // Anonymous namespace
+
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
ScopeInit guard;
auto& core_timing = guard.core_timing;
+ std::vector<std::shared_ptr<Core::Timing::EventType>> events{
+ Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
+ Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
+ Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
+ Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
+ Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
+ };
- std::shared_ptr<Core::Timing::EventType> cb_a =
- Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>);
- std::shared_ptr<Core::Timing::EventType> cb_b =
- Core::Timing::CreateEvent("callbackB", CallbackTemplate<1>);
- std::shared_ptr<Core::Timing::EventType> cb_c =
- Core::Timing::CreateEvent("callbackC", CallbackTemplate<2>);
- std::shared_ptr<Core::Timing::EventType> cb_d =
- Core::Timing::CreateEvent("callbackD", CallbackTemplate<3>);
- std::shared_ptr<Core::Timing::EventType> cb_e =
- Core::Timing::CreateEvent("callbackE", CallbackTemplate<4>);
-
- // Enter slice 0
- core_timing.ResetRun();
-
- // D -> B -> C -> A -> E
- core_timing.SwitchContext(0);
- core_timing.ScheduleEvent(1000, cb_a, CB_IDS[0]);
- REQUIRE(1000 == core_timing.GetDowncount());
- core_timing.ScheduleEvent(500, cb_b, CB_IDS[1]);
- REQUIRE(500 == core_timing.GetDowncount());
- core_timing.ScheduleEvent(800, cb_c, CB_IDS[2]);
- REQUIRE(500 == core_timing.GetDowncount());
- core_timing.ScheduleEvent(100, cb_d, CB_IDS[3]);
- REQUIRE(100 == core_timing.GetDowncount());
- core_timing.ScheduleEvent(1200, cb_e, CB_IDS[4]);
- REQUIRE(100 == core_timing.GetDowncount());
-
- AdvanceAndCheck(core_timing, 3, 0);
- AdvanceAndCheck(core_timing, 1, 1);
- AdvanceAndCheck(core_timing, 2, 2);
- AdvanceAndCheck(core_timing, 0, 3);
- AdvanceAndCheck(core_timing, 4, 0);
-}
-
-TEST_CASE("CoreTiming[FairSharing]", "[core]") {
+ expected_callback = 0;
- ScopeInit guard;
- auto& core_timing = guard.core_timing;
+ core_timing.SyncPause(true);
- std::shared_ptr<Core::Timing::EventType> empty_callback =
- Core::Timing::CreateEvent("empty_callback", EmptyCallback);
+ const u64 one_micro = 1000U;
+ for (std::size_t i = 0; i < events.size(); i++) {
+ const u64 order = calls_order[i];
+ const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
- callbacks_done = 0;
- u64 MAX_CALLBACKS = 10;
- for (std::size_t i = 0; i < 10; i++) {
- core_timing.ScheduleEvent(i * 3333U, empty_callback, 0);
+ core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
}
+ /// test pause
+ REQUIRE(callbacks_ran_flags.none());
+
+ core_timing.Pause(false); // No need to sync
+
+ while (core_timing.HasPendingEvents())
+ ;
+
+ REQUIRE(callbacks_ran_flags.all());
- const s64 advances = MAX_SLICE_LENGTH / 10;
- core_timing.ResetRun();
- u64 current_time = core_timing.GetTicks();
- bool keep_running{};
- do {
- keep_running = false;
- for (u32 active_core = 0; active_core < 4; ++active_core) {
- core_timing.SwitchContext(active_core);
- if (core_timing.CanCurrentContextRun()) {
- core_timing.AddTicks(std::min<s64>(advances, core_timing.GetDowncount()));
- core_timing.Advance();
- }
- keep_running |= core_timing.CanCurrentContextRun();
- }
- } while (keep_running);
- u64 current_time_2 = core_timing.GetTicks();
-
- REQUIRE(MAX_CALLBACKS == callbacks_done);
- REQUIRE(current_time_2 == current_time + MAX_SLICE_LENGTH * 4);
+ for (std::size_t i = 0; i < delays.size(); i++) {
+ const double delay = static_cast<double>(delays[i]);
+ const double micro = delay / 1000.0f;
+ const double mili = micro / 1000.0f;
+ printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
+ }
}
-TEST_CASE("Core::Timing[PredictableLateness]", "[core]") {
+TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
ScopeInit guard;
auto& core_timing = guard.core_timing;
+ std::vector<std::shared_ptr<Core::Timing::EventType>> events{
+ Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
+ Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
+ Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
+ Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
+ Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
+ };
+
+ core_timing.SyncPause(true);
+ core_timing.SyncPause(false);
+
+ expected_callback = 0;
+
+ const u64 start = core_timing.GetGlobalTimeNs().count();
+ const u64 one_micro = 1000U;
+
+ for (std::size_t i = 0; i < events.size(); i++) {
+ const u64 order = calls_order[i];
+ const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
+ core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
+ }
- std::shared_ptr<Core::Timing::EventType> cb_a =
- Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>);
- std::shared_ptr<Core::Timing::EventType> cb_b =
- Core::Timing::CreateEvent("callbackB", CallbackTemplate<1>);
+ const u64 end = core_timing.GetGlobalTimeNs().count();
+ const double scheduling_time = static_cast<double>(end - start);
+ const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
- // Enter slice 0
- core_timing.ResetRun();
+ while (core_timing.HasPendingEvents())
+ ;
- core_timing.ScheduleEvent(100, cb_a, CB_IDS[0]);
- core_timing.ScheduleEvent(200, cb_b, CB_IDS[1]);
+ REQUIRE(callbacks_ran_flags.all());
+
+ for (std::size_t i = 0; i < delays.size(); i++) {
+ const double delay = static_cast<double>(delays[i]);
+ const double micro = delay / 1000.0f;
+ const double mili = micro / 1000.0f;
+ printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
+ }
- AdvanceAndCheck(core_timing, 0, 0, 10, -10); // (100 - 10)
- AdvanceAndCheck(core_timing, 1, 1, 50, -50);
+ const double micro = scheduling_time / 1000.0f;
+ const double mili = micro / 1000.0f;
+ printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
+ printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
+ timer_time / 1000000.f);
}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 258d58eba..abcee2a1c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,13 +1,37 @@
+add_subdirectory(host_shaders)
+
add_library(video_core STATIC
buffer_cache/buffer_block.h
buffer_cache/buffer_cache.h
+ buffer_cache/map_interval.cpp
buffer_cache/map_interval.h
+ cdma_pusher.cpp
+ cdma_pusher.h
+ command_classes/codecs/codec.cpp
+ command_classes/codecs/codec.h
+ command_classes/codecs/h264.cpp
+ command_classes/codecs/h264.h
+ command_classes/codecs/vp9.cpp
+ command_classes/codecs/vp9.h
+ command_classes/codecs/vp9_types.h
+ command_classes/host1x.cpp
+ command_classes/host1x.h
+ command_classes/nvdec.cpp
+ command_classes/nvdec.h
+ command_classes/nvdec_common.h
+ command_classes/sync_manager.cpp
+ command_classes/sync_manager.h
+ command_classes/vic.cpp
+ command_classes/vic.h
+ compatible_formats.cpp
+ compatible_formats.h
dirty_flags.cpp
dirty_flags.h
dma_pusher.cpp
dma_pusher.h
engines/const_buffer_engine_interface.h
engines/const_buffer_info.h
+ engines/engine_interface.h
engines/engine_upload.cpp
engines/engine_upload.h
engines/fermi_2d.cpp
@@ -23,6 +47,15 @@ add_library(video_core STATIC
engines/shader_bytecode.h
engines/shader_header.h
engines/shader_type.h
+ macro/macro.cpp
+ macro/macro.h
+ macro/macro_hle.cpp
+ macro/macro_hle.h
+ macro/macro_interpreter.cpp
+ macro/macro_interpreter.h
+ macro/macro_jit_x64.cpp
+ macro/macro_jit_x64.h
+ fence_manager.h
gpu.cpp
gpu.h
gpu_asynch.cpp
@@ -33,8 +66,6 @@ add_library(video_core STATIC
gpu_thread.h
guest_driver.cpp
guest_driver.h
- macro_interpreter.cpp
- macro_interpreter.h
memory_manager.cpp
memory_manager.h
morton.cpp
@@ -42,15 +73,17 @@ add_library(video_core STATIC
query_cache.h
rasterizer_accelerated.cpp
rasterizer_accelerated.h
- rasterizer_cache.cpp
- rasterizer_cache.h
rasterizer_interface.h
renderer_base.cpp
renderer_base.h
+ renderer_opengl/gl_arb_decompiler.cpp
+ renderer_opengl/gl_arb_decompiler.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
renderer_opengl/gl_device.cpp
renderer_opengl/gl_device.h
+ renderer_opengl/gl_fence_manager.cpp
+ renderer_opengl/gl_fence_manager.h
renderer_opengl/gl_framebuffer_cache.cpp
renderer_opengl/gl_framebuffer_cache.h
renderer_opengl/gl_rasterizer.cpp
@@ -84,6 +117,9 @@ add_library(video_core STATIC
renderer_opengl/utils.h
sampler_cache.cpp
sampler_cache.h
+ shader_cache.h
+ shader_notify.cpp
+ shader_notify.h
shader/decode/arithmetic.cpp
shader/decode/arithmetic_immediate.cpp
shader/decode/bfe.cpp
@@ -114,6 +150,8 @@ add_library(video_core STATIC
shader/decode/other.cpp
shader/ast.cpp
shader/ast.h
+ shader/async_shaders.cpp
+ shader/async_shaders.h
shader/compiler_settings.cpp
shader/compiler_settings.h
shader/control_flow.cpp
@@ -121,6 +159,8 @@ add_library(video_core STATIC
shader/decode.cpp
shader/expr.cpp
shader/expr.h
+ shader/memory_util.cpp
+ shader/memory_util.h
shader/node_helper.cpp
shader/node_helper.h
shader/node.h
@@ -160,12 +200,16 @@ if (ENABLE_VULKAN)
renderer_vulkan/fixed_pipeline_state.h
renderer_vulkan/maxwell_to_vk.cpp
renderer_vulkan/maxwell_to_vk.h
+ renderer_vulkan/nsight_aftermath_tracker.cpp
+ renderer_vulkan/nsight_aftermath_tracker.h
renderer_vulkan/renderer_vulkan.h
renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/vk_blit_screen.cpp
renderer_vulkan/vk_blit_screen.h
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
+ renderer_vulkan/vk_command_pool.cpp
+ renderer_vulkan/vk_command_pool.h
renderer_vulkan/vk_compute_pass.cpp
renderer_vulkan/vk_compute_pass.h
renderer_vulkan/vk_compute_pipeline.cpp
@@ -174,10 +218,14 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_descriptor_pool.h
renderer_vulkan/vk_device.cpp
renderer_vulkan/vk_device.h
+ renderer_vulkan/vk_fence_manager.cpp
+ renderer_vulkan/vk_fence_manager.h
renderer_vulkan/vk_graphics_pipeline.cpp
renderer_vulkan/vk_graphics_pipeline.h
renderer_vulkan/vk_image.cpp
renderer_vulkan/vk_image.h
+ renderer_vulkan/vk_master_semaphore.cpp
+ renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_memory_manager.cpp
renderer_vulkan/vk_memory_manager.h
renderer_vulkan/vk_pipeline_cache.cpp
@@ -188,8 +236,8 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_rasterizer.h
renderer_vulkan/vk_renderpass_cache.cpp
renderer_vulkan/vk_renderpass_cache.h
- renderer_vulkan/vk_resource_manager.cpp
- renderer_vulkan/vk_resource_manager.h
+ renderer_vulkan/vk_resource_pool.cpp
+ renderer_vulkan/vk_resource_pool.h
renderer_vulkan/vk_sampler_cache.cpp
renderer_vulkan/vk_sampler_cache.h
renderer_vulkan/vk_scheduler.cpp
@@ -213,21 +261,55 @@ if (ENABLE_VULKAN)
renderer_vulkan/wrapper.cpp
renderer_vulkan/wrapper.h
)
-
- target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
- target_compile_definitions(video_core PRIVATE HAS_VULKAN)
endif()
create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
-target_link_libraries(video_core PRIVATE glad)
+target_link_libraries(video_core PRIVATE glad xbyak)
+
+if (MSVC)
+ target_include_directories(video_core PRIVATE ${FFMPEG_INCLUDE_DIR})
+ target_link_libraries(video_core PUBLIC ${FFMPEG_LIBRARY_DIR}/swscale.lib ${FFMPEG_LIBRARY_DIR}/avcodec.lib ${FFMPEG_LIBRARY_DIR}/avutil.lib)
+else()
+ target_include_directories(video_core PRIVATE ${FFMPEG_INCLUDE_DIR})
+ target_link_libraries(video_core PRIVATE ${FFMPEG_LIBRARIES})
+endif()
+
+add_dependencies(video_core host_shaders)
+target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
+
if (ENABLE_VULKAN)
+ target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
+ target_compile_definitions(video_core PRIVATE HAS_VULKAN)
target_link_libraries(video_core PRIVATE sirit)
endif()
+if (ENABLE_NSIGHT_AFTERMATH)
+ if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
+ message(ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided")
+ endif()
+ if (NOT WIN32)
+ message(ERROR "Nsight Aftermath doesn't support non-Windows platforms")
+ endif()
+ target_compile_definitions(video_core PRIVATE HAS_NSIGHT_AFTERMATH)
+ target_include_directories(video_core PRIVATE "$ENV{NSIGHT_AFTERMATH_SDK}/include")
+endif()
+
if (MSVC)
target_compile_options(video_core PRIVATE /we4267)
else()
- target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion)
+ target_compile_options(video_core PRIVATE
+ -Werror=conversion
+ -Wno-error=sign-conversion
+ -Werror=pessimizing-move
+ -Werror=redundant-move
+ -Werror=switch
+ -Werror=type-limits
+ -Werror=unused-variable
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+ )
endif()
diff --git a/src/video_core/buffer_cache/buffer_block.h b/src/video_core/buffer_cache/buffer_block.h
index e35ee0b67..e64170e66 100644
--- a/src/video_core/buffer_cache/buffer_block.h
+++ b/src/video_core/buffer_cache/buffer_block.h
@@ -15,48 +15,47 @@ namespace VideoCommon {
class BufferBlock {
public:
- bool Overlaps(const VAddr start, const VAddr end) const {
+ bool Overlaps(VAddr start, VAddr end) const {
return (cpu_addr < end) && (cpu_addr_end > start);
}
- bool IsInside(const VAddr other_start, const VAddr other_end) const {
+ bool IsInside(VAddr other_start, VAddr other_end) const {
return cpu_addr <= other_start && other_end <= cpu_addr_end;
}
- std::size_t GetOffset(const VAddr in_addr) {
+ std::size_t Offset(VAddr in_addr) const {
return static_cast<std::size_t>(in_addr - cpu_addr);
}
- VAddr GetCpuAddr() const {
+ VAddr CpuAddr() const {
return cpu_addr;
}
- VAddr GetCpuAddrEnd() const {
+ VAddr CpuAddrEnd() const {
return cpu_addr_end;
}
- void SetCpuAddr(const VAddr new_addr) {
+ void SetCpuAddr(VAddr new_addr) {
cpu_addr = new_addr;
cpu_addr_end = new_addr + size;
}
- std::size_t GetSize() const {
+ std::size_t Size() const {
return size;
}
- void SetEpoch(u64 new_epoch) {
- epoch = new_epoch;
+ u64 Epoch() const {
+ return epoch;
}
- u64 GetEpoch() {
- return epoch;
+ void SetEpoch(u64 new_epoch) {
+ epoch = new_epoch;
}
protected:
- explicit BufferBlock(VAddr cpu_addr, const std::size_t size) : size{size} {
- SetCpuAddr(cpu_addr);
+ explicit BufferBlock(VAddr cpu_addr_, std::size_t size_) : size{size_} {
+ SetCpuAddr(cpu_addr_);
}
- ~BufferBlock() = default;
private:
VAddr cpu_addr{};
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index b57c0d4d4..e7edd733f 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -4,7 +4,7 @@
#pragma once
-#include <array>
+#include <list>
#include <memory>
#include <mutex>
#include <unordered_map>
@@ -12,14 +12,17 @@
#include <utility>
#include <vector>
-#include <boost/icl/interval_map.hpp>
+#include <boost/container/small_vector.hpp>
#include <boost/icl/interval_set.hpp>
-#include <boost/range/iterator_range.hpp>
+#include <boost/intrusive/set.hpp>
#include "common/alignment.h"
+#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "core/core.h"
#include "core/memory.h"
+#include "core/settings.h"
#include "video_core/buffer_cache/buffer_block.h"
#include "video_core/buffer_cache/map_interval.h"
#include "video_core/memory_manager.h"
@@ -27,105 +30,122 @@
namespace VideoCommon {
-using MapInterval = std::shared_ptr<MapIntervalBase>;
-
-template <typename TBuffer, typename TBufferType, typename StreamBuffer>
+template <typename Buffer, typename BufferType, typename StreamBuffer>
class BufferCache {
+ using IntervalSet = boost::icl::interval_set<VAddr>;
+ using IntervalType = typename IntervalSet::interval_type;
+ using VectorMapInterval = boost::container::small_vector<MapInterval*, 1>;
+
+ static constexpr u64 WRITE_PAGE_BIT = 11;
+ static constexpr u64 BLOCK_PAGE_BITS = 21;
+ static constexpr u64 BLOCK_PAGE_SIZE = 1ULL << BLOCK_PAGE_BITS;
+
public:
- using BufferInfo = std::pair<const TBufferType*, u64>;
+ struct BufferInfo {
+ BufferType handle;
+ u64 offset;
+ u64 address;
+ };
BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
bool is_written = false, bool use_fast_cbuf = false) {
std::lock_guard lock{mutex};
- const std::optional<VAddr> cpu_addr_opt =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
-
- if (!cpu_addr_opt) {
- return {GetEmptyBuffer(size), 0};
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (!cpu_addr) {
+ return GetEmptyBuffer(size);
}
- VAddr cpu_addr = *cpu_addr_opt;
-
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
constexpr std::size_t max_stream_size = 0x800;
if (use_fast_cbuf || size < max_stream_size) {
- if (!is_written && !IsRegionWritten(cpu_addr, cpu_addr + size - 1)) {
- auto& memory_manager = system.GPU().MemoryManager();
+ if (!is_written && !IsRegionWritten(*cpu_addr, *cpu_addr + size - 1)) {
+ const bool is_granular = gpu_memory.IsGranularRange(gpu_addr, size);
if (use_fast_cbuf) {
- if (memory_manager.IsGranularRange(gpu_addr, size)) {
- const auto host_ptr = memory_manager.GetPointer(gpu_addr);
- return ConstBufferUpload(host_ptr, size);
+ u8* dest;
+ if (is_granular) {
+ dest = gpu_memory.GetPointer(gpu_addr);
} else {
staging_buffer.resize(size);
- memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
- return ConstBufferUpload(staging_buffer.data(), size);
+ dest = staging_buffer.data();
+ gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
}
+ return ConstBufferUpload(dest, size);
+ }
+ if (is_granular) {
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
+ return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) {
+ std::memcpy(dest, host_ptr, size);
+ });
} else {
- if (memory_manager.IsGranularRange(gpu_addr, size)) {
- const auto host_ptr = memory_manager.GetPointer(gpu_addr);
- return StreamBufferUpload(host_ptr, size, alignment);
- } else {
- staging_buffer.resize(size);
- memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
- return StreamBufferUpload(staging_buffer.data(), size, alignment);
- }
+ return StreamBufferUpload(size, alignment, [this, gpu_addr, size](u8* dest) {
+ gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
+ });
}
}
}
- auto block = GetBlock(cpu_addr, size);
- auto map = MapAddress(block, gpu_addr, cpu_addr, size);
+ Buffer* const block = GetBlock(*cpu_addr, size);
+ MapInterval* const map = MapAddress(block, gpu_addr, *cpu_addr, size);
+ if (!map) {
+ return GetEmptyBuffer(size);
+ }
if (is_written) {
map->MarkAsModified(true, GetModifiedTicks());
- if (!map->IsWritten()) {
- map->MarkAsWritten(true);
- MarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1);
+ if (Settings::IsGPULevelHigh() &&
+ Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ MarkForAsyncFlush(map);
}
- } else {
- if (map->IsWritten()) {
- WriteBarrier();
+ if (!map->is_written) {
+ map->is_written = true;
+ MarkRegionAsWritten(map->start, map->end - 1);
}
}
- const u64 offset = static_cast<u64>(block->GetOffset(cpu_addr));
-
- return {ToHandle(block), offset};
+ return BufferInfo{block->Handle(), block->Offset(*cpu_addr), block->Address()};
}
/// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
std::size_t alignment = 4) {
std::lock_guard lock{mutex};
- return StreamBufferUpload(raw_pointer, size, alignment);
+ return StreamBufferUpload(size, alignment, [raw_pointer, size](u8* dest) {
+ std::memcpy(dest, raw_pointer, size);
+ });
}
- void Map(std::size_t max_size) {
+ /// Prepares the buffer cache for data uploading
+ /// @param max_size Maximum number of bytes that will be uploaded
+ /// @return True when a stream buffer invalidation was required, false otherwise
+ bool Map(std::size_t max_size) {
std::lock_guard lock{mutex};
+ bool invalidated;
std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
buffer_offset = buffer_offset_base;
+
+ return invalidated;
}
- /// Finishes the upload stream, returns true on bindings invalidation.
- bool Unmap() {
+ /// Finishes the upload stream
+ void Unmap() {
std::lock_guard lock{mutex};
-
stream_buffer->Unmap(buffer_offset - buffer_offset_base);
- return std::exchange(invalidated, false);
}
+ /// Function called at the end of each frame, inteded for deferred operations
void TickFrame() {
++epoch;
+
while (!pending_destruction.empty()) {
// Delay at least 4 frames before destruction.
// This is due to triple buffering happening on some drivers.
static constexpr u64 epochs_to_destroy = 5;
- if (pending_destruction.front()->GetEpoch() + epochs_to_destroy > epoch) {
+ if (pending_destruction.front()->Epoch() + epochs_to_destroy > epoch) {
break;
}
- pending_destruction.pop_front();
+ pending_destruction.pop();
}
}
@@ -133,117 +153,193 @@ public:
void FlushRegion(VAddr addr, std::size_t size) {
std::lock_guard lock{mutex};
- std::vector<MapInterval> objects = GetMapsInRange(addr, size);
- std::sort(objects.begin(), objects.end(), [](const MapInterval& a, const MapInterval& b) {
- return a->GetModificationTick() < b->GetModificationTick();
- });
- for (auto& object : objects) {
- if (object->IsModified() && object->IsRegistered()) {
+ VectorMapInterval objects = GetMapsInRange(addr, size);
+ std::sort(objects.begin(), objects.end(),
+ [](MapInterval* lhs, MapInterval* rhs) { return lhs->ticks < rhs->ticks; });
+ for (MapInterval* object : objects) {
+ if (object->is_modified && object->is_registered) {
+ mutex.unlock();
FlushMap(object);
+ mutex.lock();
}
}
}
+ bool MustFlushRegion(VAddr addr, std::size_t size) {
+ std::lock_guard lock{mutex};
+
+ const VectorMapInterval objects = GetMapsInRange(addr, size);
+ return std::any_of(objects.cbegin(), objects.cend(), [](const MapInterval* map) {
+ return map->is_modified && map->is_registered;
+ });
+ }
+
/// Mark the specified region as being invalidated
void InvalidateRegion(VAddr addr, u64 size) {
std::lock_guard lock{mutex};
- std::vector<MapInterval> objects = GetMapsInRange(addr, size);
- for (auto& object : objects) {
- if (object->IsRegistered()) {
+ for (auto& object : GetMapsInRange(addr, size)) {
+ if (object->is_registered) {
Unregister(object);
}
}
}
- virtual const TBufferType* GetEmptyBuffer(std::size_t size) = 0;
+ void OnCPUWrite(VAddr addr, std::size_t size) {
+ std::lock_guard lock{mutex};
-protected:
- explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- std::unique_ptr<StreamBuffer> stream_buffer)
- : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)},
- stream_buffer_handle{this->stream_buffer->GetHandle()} {}
+ for (MapInterval* object : GetMapsInRange(addr, size)) {
+ if (object->is_memory_marked && object->is_registered) {
+ UnmarkMemory(object);
+ object->is_sync_pending = true;
+ marked_for_unregister.emplace_back(object);
+ }
+ }
+ }
- ~BufferCache() = default;
+ void SyncGuestHost() {
+ std::lock_guard lock{mutex};
+
+ for (auto& object : marked_for_unregister) {
+ if (object->is_registered) {
+ object->is_sync_pending = false;
+ Unregister(object);
+ }
+ }
+ marked_for_unregister.clear();
+ }
+
+ void CommitAsyncFlushes() {
+ if (uncommitted_flushes) {
+ auto commit_list = std::make_shared<std::list<MapInterval*>>();
+ for (MapInterval* map : *uncommitted_flushes) {
+ if (map->is_registered && map->is_modified) {
+ // TODO(Blinkhawk): Implement backend asynchronous flushing
+ // AsyncFlushMap(map)
+ commit_list->push_back(map);
+ }
+ }
+ if (!commit_list->empty()) {
+ committed_flushes.push_back(commit_list);
+ } else {
+ committed_flushes.emplace_back();
+ }
+ } else {
+ committed_flushes.emplace_back();
+ }
+ uncommitted_flushes.reset();
+ }
- virtual const TBufferType* ToHandle(const TBuffer& storage) = 0;
+ bool ShouldWaitAsyncFlushes() const {
+ return !committed_flushes.empty() && committed_flushes.front() != nullptr;
+ }
- virtual void WriteBarrier() = 0;
+ bool HasUncommittedFlushes() const {
+ return uncommitted_flushes != nullptr;
+ }
+
+ void PopAsyncFlushes() {
+ if (committed_flushes.empty()) {
+ return;
+ }
+ auto& flush_list = committed_flushes.front();
+ if (!flush_list) {
+ committed_flushes.pop_front();
+ return;
+ }
+ for (MapInterval* map : *flush_list) {
+ if (map->is_registered) {
+ // TODO(Blinkhawk): Replace this for reading the asynchronous flush
+ FlushMap(map);
+ }
+ }
+ committed_flushes.pop_front();
+ }
- virtual TBuffer CreateBlock(VAddr cpu_addr, std::size_t size) = 0;
+ virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0;
- virtual void UploadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
- const u8* data) = 0;
+protected:
+ explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
+ std::unique_ptr<StreamBuffer> stream_buffer_)
+ : rasterizer{rasterizer_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_},
+ stream_buffer{std::move(stream_buffer_)}, stream_buffer_handle{stream_buffer->Handle()} {}
- virtual void DownloadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
- u8* data) = 0;
+ ~BufferCache() = default;
- virtual void CopyBlock(const TBuffer& src, const TBuffer& dst, std::size_t src_offset,
- std::size_t dst_offset, std::size_t size) = 0;
+ virtual std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) = 0;
virtual BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) {
return {};
}
/// Register an object into the cache
- void Register(const MapInterval& new_map, bool inherit_written = false) {
- const VAddr cpu_addr = new_map->GetStart();
+ MapInterval* Register(MapInterval new_map, bool inherit_written = false) {
+ const VAddr cpu_addr = new_map.start;
if (!cpu_addr) {
LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}",
- new_map->GetGpuAddress());
- return;
+ new_map.gpu_addr);
+ return nullptr;
}
- const std::size_t size = new_map->GetEnd() - new_map->GetStart();
- new_map->MarkAsRegistered(true);
- const IntervalType interval{new_map->GetStart(), new_map->GetEnd()};
- mapped_addresses.insert({interval, new_map});
+ const std::size_t size = new_map.end - new_map.start;
+ new_map.is_registered = true;
rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
+ new_map.is_memory_marked = true;
if (inherit_written) {
- MarkRegionAsWritten(new_map->GetStart(), new_map->GetEnd() - 1);
- new_map->MarkAsWritten(true);
+ MarkRegionAsWritten(new_map.start, new_map.end - 1);
+ new_map.is_written = true;
}
+ MapInterval* const storage = mapped_addresses_allocator.Allocate();
+ *storage = new_map;
+ mapped_addresses.insert(*storage);
+ return storage;
}
- /// Unregisters an object from the cache
- void Unregister(MapInterval& map) {
- const std::size_t size = map->GetEnd() - map->GetStart();
- rasterizer.UpdatePagesCachedCount(map->GetStart(), size, -1);
- map->MarkAsRegistered(false);
- if (map->IsWritten()) {
- UnmarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1);
+ void UnmarkMemory(MapInterval* map) {
+ if (!map->is_memory_marked) {
+ return;
}
- const IntervalType delete_interval{map->GetStart(), map->GetEnd()};
- mapped_addresses.erase(delete_interval);
+ const std::size_t size = map->end - map->start;
+ rasterizer.UpdatePagesCachedCount(map->start, size, -1);
+ map->is_memory_marked = false;
}
-private:
- MapInterval CreateMap(const VAddr start, const VAddr end, const GPUVAddr gpu_addr) {
- return std::make_shared<MapIntervalBase>(start, end, gpu_addr);
+ /// Unregisters an object from the cache
+ void Unregister(MapInterval* map) {
+ UnmarkMemory(map);
+ map->is_registered = false;
+ if (map->is_sync_pending) {
+ map->is_sync_pending = false;
+ marked_for_unregister.remove(map);
+ }
+ if (map->is_written) {
+ UnmarkRegionAsWritten(map->start, map->end - 1);
+ }
+ const auto it = mapped_addresses.find(*map);
+ ASSERT(it != mapped_addresses.end());
+ mapped_addresses.erase(it);
+ mapped_addresses_allocator.Release(map);
}
- MapInterval MapAddress(const TBuffer& block, const GPUVAddr gpu_addr, const VAddr cpu_addr,
- const std::size_t size) {
-
- std::vector<MapInterval> overlaps = GetMapsInRange(cpu_addr, size);
+private:
+ MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
+ const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
if (overlaps.empty()) {
- auto& memory_manager = system.GPU().MemoryManager();
const VAddr cpu_addr_end = cpu_addr + size;
- MapInterval new_map = CreateMap(cpu_addr, cpu_addr_end, gpu_addr);
- if (memory_manager.IsGranularRange(gpu_addr, size)) {
- u8* host_ptr = memory_manager.GetPointer(gpu_addr);
- UploadBlockData(block, block->GetOffset(cpu_addr), size, host_ptr);
+ if (gpu_memory.IsGranularRange(gpu_addr, size)) {
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
+ block->Upload(block->Offset(cpu_addr), size, host_ptr);
} else {
staging_buffer.resize(size);
- memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
- UploadBlockData(block, block->GetOffset(cpu_addr), size, staging_buffer.data());
+ gpu_memory.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
+ block->Upload(block->Offset(cpu_addr), size, staging_buffer.data());
}
- Register(new_map);
- return new_map;
+ return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr));
}
const VAddr cpu_addr_end = cpu_addr + size;
if (overlaps.size() == 1) {
- MapInterval& current_map = overlaps[0];
+ MapInterval* const current_map = overlaps[0];
if (current_map->IsInside(cpu_addr, cpu_addr_end)) {
return current_map;
}
@@ -253,57 +349,70 @@ private:
bool write_inheritance = false;
bool modified_inheritance = false;
// Calculate new buffer parameters
- for (auto& overlap : overlaps) {
- new_start = std::min(overlap->GetStart(), new_start);
- new_end = std::max(overlap->GetEnd(), new_end);
- write_inheritance |= overlap->IsWritten();
- modified_inheritance |= overlap->IsModified();
+ for (MapInterval* overlap : overlaps) {
+ new_start = std::min(overlap->start, new_start);
+ new_end = std::max(overlap->end, new_end);
+ write_inheritance |= overlap->is_written;
+ modified_inheritance |= overlap->is_modified;
}
GPUVAddr new_gpu_addr = gpu_addr + new_start - cpu_addr;
for (auto& overlap : overlaps) {
Unregister(overlap);
}
UpdateBlock(block, new_start, new_end, overlaps);
- MapInterval new_map = CreateMap(new_start, new_end, new_gpu_addr);
+
+ const MapInterval new_map{new_start, new_end, new_gpu_addr};
+ MapInterval* const map = Register(new_map, write_inheritance);
+ if (!map) {
+ return nullptr;
+ }
if (modified_inheritance) {
- new_map->MarkAsModified(true, GetModifiedTicks());
+ map->MarkAsModified(true, GetModifiedTicks());
+ if (Settings::IsGPULevelHigh() &&
+ Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ MarkForAsyncFlush(map);
+ }
}
- Register(new_map, write_inheritance);
- return new_map;
+ return map;
}
- void UpdateBlock(const TBuffer& block, VAddr start, VAddr end,
- std::vector<MapInterval>& overlaps) {
+ void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) {
const IntervalType base_interval{start, end};
IntervalSet interval_set{};
interval_set.add(base_interval);
for (auto& overlap : overlaps) {
- const IntervalType subtract{overlap->GetStart(), overlap->GetEnd()};
+ const IntervalType subtract{overlap->start, overlap->end};
interval_set.subtract(subtract);
}
for (auto& interval : interval_set) {
- std::size_t size = interval.upper() - interval.lower();
- if (size > 0) {
- staging_buffer.resize(size);
- system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
- UploadBlockData(block, block->GetOffset(interval.lower()), size,
- staging_buffer.data());
+ const std::size_t size = interval.upper() - interval.lower();
+ if (size == 0) {
+ continue;
}
+ staging_buffer.resize(size);
+ cpu_memory.ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
+ block->Upload(block->Offset(interval.lower()), size, staging_buffer.data());
}
}
- std::vector<MapInterval> GetMapsInRange(VAddr addr, std::size_t size) {
+ VectorMapInterval GetMapsInRange(VAddr addr, std::size_t size) {
+ VectorMapInterval result;
if (size == 0) {
- return {};
+ return result;
}
- std::vector<MapInterval> objects{};
- const IntervalType interval{addr, addr + size};
- for (auto& pair : boost::make_iterator_range(mapped_addresses.equal_range(interval))) {
- objects.push_back(pair.second);
+ const VAddr addr_end = addr + size;
+ auto it = mapped_addresses.lower_bound(addr);
+ if (it != mapped_addresses.begin()) {
+ --it;
}
-
- return objects;
+ while (it != mapped_addresses.end() && it->start < addr_end) {
+ if (it->Overlaps(addr, addr_end)) {
+ result.push_back(&*it);
+ }
+ ++it;
+ }
+ return result;
}
/// Returns a ticks counter used for tracking when cached objects were last modified
@@ -311,24 +420,28 @@ private:
return ++modified_ticks;
}
- void FlushMap(MapInterval map) {
- std::size_t size = map->GetEnd() - map->GetStart();
- TBuffer block = blocks[map->GetStart() >> block_page_bits];
+ void FlushMap(MapInterval* map) {
+ const auto it = blocks.find(map->start >> BLOCK_PAGE_BITS);
+ ASSERT_OR_EXECUTE(it != blocks.end(), return;);
+
+ std::shared_ptr<Buffer> block = it->second;
+
+ const std::size_t size = map->end - map->start;
staging_buffer.resize(size);
- DownloadBlockData(block, block->GetOffset(map->GetStart()), size, staging_buffer.data());
- system.Memory().WriteBlockUnsafe(map->GetStart(), staging_buffer.data(), size);
+ block->Download(block->Offset(map->start), size, staging_buffer.data());
+ cpu_memory.WriteBlockUnsafe(map->start, staging_buffer.data(), size);
map->MarkAsModified(false, 0);
}
- BufferInfo StreamBufferUpload(const void* raw_pointer, std::size_t size,
- std::size_t alignment) {
+ template <typename Callable>
+ BufferInfo StreamBufferUpload(std::size_t size, std::size_t alignment, Callable&& callable) {
AlignBuffer(alignment);
const std::size_t uploaded_offset = buffer_offset;
- std::memcpy(buffer_ptr, raw_pointer, size);
+ callable(buffer_ptr);
buffer_ptr += size;
buffer_offset += size;
- return {&stream_buffer_handle, uploaded_offset};
+ return BufferInfo{stream_buffer->Handle(), uploaded_offset, stream_buffer->Address()};
}
void AlignBuffer(std::size_t alignment) {
@@ -338,151 +451,148 @@ private:
buffer_offset = offset_aligned;
}
- TBuffer EnlargeBlock(TBuffer buffer) {
- const std::size_t old_size = buffer->GetSize();
- const std::size_t new_size = old_size + block_page_size;
- const VAddr cpu_addr = buffer->GetCpuAddr();
- TBuffer new_buffer = CreateBlock(cpu_addr, new_size);
- CopyBlock(buffer, new_buffer, 0, 0, old_size);
- buffer->SetEpoch(epoch);
- pending_destruction.push_back(buffer);
+ std::shared_ptr<Buffer> EnlargeBlock(std::shared_ptr<Buffer> buffer) {
+ const std::size_t old_size = buffer->Size();
+ const std::size_t new_size = old_size + BLOCK_PAGE_SIZE;
+ const VAddr cpu_addr = buffer->CpuAddr();
+ std::shared_ptr<Buffer> new_buffer = CreateBlock(cpu_addr, new_size);
+ new_buffer->CopyFrom(*buffer, 0, 0, old_size);
+ QueueDestruction(std::move(buffer));
+
const VAddr cpu_addr_end = cpu_addr + new_size - 1;
- u64 page_start = cpu_addr >> block_page_bits;
- const u64 page_end = cpu_addr_end >> block_page_bits;
- while (page_start <= page_end) {
- blocks[page_start] = new_buffer;
- ++page_start;
+ const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
+ for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
+ blocks.insert_or_assign(page_start, new_buffer);
}
+
return new_buffer;
}
- TBuffer MergeBlocks(TBuffer first, TBuffer second) {
- const std::size_t size_1 = first->GetSize();
- const std::size_t size_2 = second->GetSize();
- const VAddr first_addr = first->GetCpuAddr();
- const VAddr second_addr = second->GetCpuAddr();
+ std::shared_ptr<Buffer> MergeBlocks(std::shared_ptr<Buffer> first,
+ std::shared_ptr<Buffer> second) {
+ const std::size_t size_1 = first->Size();
+ const std::size_t size_2 = second->Size();
+ const VAddr first_addr = first->CpuAddr();
+ const VAddr second_addr = second->CpuAddr();
const VAddr new_addr = std::min(first_addr, second_addr);
const std::size_t new_size = size_1 + size_2;
- TBuffer new_buffer = CreateBlock(new_addr, new_size);
- CopyBlock(first, new_buffer, 0, new_buffer->GetOffset(first_addr), size_1);
- CopyBlock(second, new_buffer, 0, new_buffer->GetOffset(second_addr), size_2);
- first->SetEpoch(epoch);
- second->SetEpoch(epoch);
- pending_destruction.push_back(first);
- pending_destruction.push_back(second);
+
+ std::shared_ptr<Buffer> new_buffer = CreateBlock(new_addr, new_size);
+ new_buffer->CopyFrom(*first, 0, new_buffer->Offset(first_addr), size_1);
+ new_buffer->CopyFrom(*second, 0, new_buffer->Offset(second_addr), size_2);
+ QueueDestruction(std::move(first));
+ QueueDestruction(std::move(second));
+
const VAddr cpu_addr_end = new_addr + new_size - 1;
- u64 page_start = new_addr >> block_page_bits;
- const u64 page_end = cpu_addr_end >> block_page_bits;
- while (page_start <= page_end) {
- blocks[page_start] = new_buffer;
- ++page_start;
+ const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
+ for (u64 page_start = new_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
+ blocks.insert_or_assign(page_start, new_buffer);
}
return new_buffer;
}
- TBuffer GetBlock(const VAddr cpu_addr, const std::size_t size) {
- TBuffer found{};
+ Buffer* GetBlock(VAddr cpu_addr, std::size_t size) {
+ std::shared_ptr<Buffer> found;
+
const VAddr cpu_addr_end = cpu_addr + size - 1;
- u64 page_start = cpu_addr >> block_page_bits;
- const u64 page_end = cpu_addr_end >> block_page_bits;
- while (page_start <= page_end) {
+ const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
+ for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
auto it = blocks.find(page_start);
if (it == blocks.end()) {
if (found) {
found = EnlargeBlock(found);
- } else {
- const VAddr start_addr = (page_start << block_page_bits);
- found = CreateBlock(start_addr, block_page_size);
- blocks[page_start] = found;
- }
- } else {
- if (found) {
- if (found == it->second) {
- ++page_start;
- continue;
- }
- found = MergeBlocks(found, it->second);
- } else {
- found = it->second;
+ continue;
}
+ const VAddr start_addr = page_start << BLOCK_PAGE_BITS;
+ found = CreateBlock(start_addr, BLOCK_PAGE_SIZE);
+ blocks.insert_or_assign(page_start, found);
+ continue;
+ }
+ if (!found) {
+ found = it->second;
+ continue;
+ }
+ if (found != it->second) {
+ found = MergeBlocks(std::move(found), it->second);
}
- ++page_start;
}
- return found;
+ return found.get();
}
- void MarkRegionAsWritten(const VAddr start, const VAddr end) {
- u64 page_start = start >> write_page_bit;
- const u64 page_end = end >> write_page_bit;
- while (page_start <= page_end) {
- auto it = written_pages.find(page_start);
- if (it != written_pages.end()) {
- it->second = it->second + 1;
- } else {
- written_pages[page_start] = 1;
+ void MarkRegionAsWritten(VAddr start, VAddr end) {
+ const u64 page_end = end >> WRITE_PAGE_BIT;
+ for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
+ if (const auto [it, inserted] = written_pages.emplace(page_start, 1); !inserted) {
+ ++it->second;
}
- page_start++;
}
}
- void UnmarkRegionAsWritten(const VAddr start, const VAddr end) {
- u64 page_start = start >> write_page_bit;
- const u64 page_end = end >> write_page_bit;
- while (page_start <= page_end) {
+ void UnmarkRegionAsWritten(VAddr start, VAddr end) {
+ const u64 page_end = end >> WRITE_PAGE_BIT;
+ for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
auto it = written_pages.find(page_start);
if (it != written_pages.end()) {
if (it->second > 1) {
- it->second = it->second - 1;
+ --it->second;
} else {
written_pages.erase(it);
}
}
- page_start++;
}
}
- bool IsRegionWritten(const VAddr start, const VAddr end) const {
- u64 page_start = start >> write_page_bit;
- const u64 page_end = end >> write_page_bit;
- while (page_start <= page_end) {
+ bool IsRegionWritten(VAddr start, VAddr end) const {
+ const u64 page_end = end >> WRITE_PAGE_BIT;
+ for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
if (written_pages.count(page_start) > 0) {
return true;
}
- page_start++;
}
return false;
}
+ void QueueDestruction(std::shared_ptr<Buffer> buffer) {
+ buffer->SetEpoch(epoch);
+ pending_destruction.push(std::move(buffer));
+ }
+
+ void MarkForAsyncFlush(MapInterval* map) {
+ if (!uncommitted_flushes) {
+ uncommitted_flushes = std::make_shared<std::unordered_set<MapInterval*>>();
+ }
+ uncommitted_flushes->insert(map);
+ }
+
VideoCore::RasterizerInterface& rasterizer;
- Core::System& system;
+ Tegra::MemoryManager& gpu_memory;
+ Core::Memory::Memory& cpu_memory;
std::unique_ptr<StreamBuffer> stream_buffer;
- TBufferType stream_buffer_handle{};
-
- bool invalidated = false;
+ BufferType stream_buffer_handle;
u8* buffer_ptr = nullptr;
u64 buffer_offset = 0;
u64 buffer_offset_base = 0;
- using IntervalSet = boost::icl::interval_set<VAddr>;
- using IntervalCache = boost::icl::interval_map<VAddr, MapInterval>;
- using IntervalType = typename IntervalCache::interval_type;
- IntervalCache mapped_addresses;
+ MapIntervalAllocator mapped_addresses_allocator;
+ boost::intrusive::set<MapInterval, boost::intrusive::compare<MapIntervalCompare>>
+ mapped_addresses;
- static constexpr u64 write_page_bit = 11;
std::unordered_map<u64, u32> written_pages;
+ std::unordered_map<u64, std::shared_ptr<Buffer>> blocks;
- static constexpr u64 block_page_bits = 21;
- static constexpr u64 block_page_size = 1ULL << block_page_bits;
- std::unordered_map<u64, TBuffer> blocks;
-
- std::list<TBuffer> pending_destruction;
+ std::queue<std::shared_ptr<Buffer>> pending_destruction;
u64 epoch = 0;
u64 modified_ticks = 0;
std::vector<u8> staging_buffer;
+ std::list<MapInterval*> marked_for_unregister;
+
+ std::shared_ptr<std::unordered_set<MapInterval*>> uncommitted_flushes;
+ std::list<std::shared_ptr<std::list<MapInterval*>>> committed_flushes;
+
std::recursive_mutex mutex;
};
diff --git a/src/video_core/buffer_cache/map_interval.cpp b/src/video_core/buffer_cache/map_interval.cpp
new file mode 100644
index 000000000..62587e18a
--- /dev/null
+++ b/src/video_core/buffer_cache/map_interval.cpp
@@ -0,0 +1,33 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <memory>
+
+#include "video_core/buffer_cache/map_interval.h"
+
+namespace VideoCommon {
+
+MapIntervalAllocator::MapIntervalAllocator() {
+ FillFreeList(first_chunk);
+}
+
+MapIntervalAllocator::~MapIntervalAllocator() = default;
+
+void MapIntervalAllocator::AllocateNewChunk() {
+ *new_chunk = std::make_unique<Chunk>();
+ FillFreeList(**new_chunk);
+ new_chunk = &(*new_chunk)->next;
+}
+
+void MapIntervalAllocator::FillFreeList(Chunk& chunk) {
+ const std::size_t old_size = free_list.size();
+ free_list.resize(old_size + chunk.data.size());
+ std::transform(chunk.data.rbegin(), chunk.data.rend(), free_list.begin() + old_size,
+ [](MapInterval& interval) { return &interval; });
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/map_interval.h b/src/video_core/buffer_cache/map_interval.h
index b0956029d..fe0bcd1d8 100644
--- a/src/video_core/buffer_cache/map_interval.h
+++ b/src/video_core/buffer_cache/map_interval.h
@@ -4,86 +4,89 @@
#pragma once
+#include <array>
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include <boost/intrusive/set_hook.hpp>
+
#include "common/common_types.h"
#include "video_core/gpu.h"
namespace VideoCommon {
-class MapIntervalBase {
-public:
- MapIntervalBase(const VAddr start, const VAddr end, const GPUVAddr gpu_addr)
- : start{start}, end{end}, gpu_addr{gpu_addr} {}
+struct MapInterval : public boost::intrusive::set_base_hook<boost::intrusive::optimize_size<true>> {
+ MapInterval() = default;
- void SetCpuAddress(VAddr new_cpu_addr) {
- cpu_addr = new_cpu_addr;
- }
+ /*implicit*/ MapInterval(VAddr start_) noexcept : start{start_} {}
- VAddr GetCpuAddress() const {
- return cpu_addr;
- }
+ explicit MapInterval(VAddr start_, VAddr end_, GPUVAddr gpu_addr_) noexcept
+ : start{start_}, end{end_}, gpu_addr{gpu_addr_} {}
- GPUVAddr GetGpuAddress() const {
- return gpu_addr;
+ bool IsInside(VAddr other_start, VAddr other_end) const noexcept {
+ return start <= other_start && other_end <= end;
}
- bool IsInside(const VAddr other_start, const VAddr other_end) const {
- return (start <= other_start && other_end <= end);
+ bool Overlaps(VAddr other_start, VAddr other_end) const noexcept {
+ return start < other_end && other_start < end;
}
- bool operator==(const MapIntervalBase& rhs) const {
- return std::tie(start, end) == std::tie(rhs.start, rhs.end);
- }
-
- bool operator!=(const MapIntervalBase& rhs) const {
- return !operator==(rhs);
- }
-
- void MarkAsRegistered(const bool registered) {
- is_registered = registered;
+ void MarkAsModified(bool is_modified_, u64 ticks_) noexcept {
+ is_modified = is_modified_;
+ ticks = ticks_;
}
- bool IsRegistered() const {
- return is_registered;
- }
+ boost::intrusive::set_member_hook<> member_hook_;
+ VAddr start = 0;
+ VAddr end = 0;
+ GPUVAddr gpu_addr = 0;
+ u64 ticks = 0;
+ bool is_written = false;
+ bool is_modified = false;
+ bool is_registered = false;
+ bool is_memory_marked = false;
+ bool is_sync_pending = false;
+};
- VAddr GetStart() const {
- return start;
+struct MapIntervalCompare {
+ constexpr bool operator()(const MapInterval& lhs, const MapInterval& rhs) const noexcept {
+ return lhs.start < rhs.start;
}
+};
- VAddr GetEnd() const {
- return end;
+class MapIntervalAllocator {
+public:
+ MapIntervalAllocator();
+ ~MapIntervalAllocator();
+
+ MapInterval* Allocate() {
+ if (free_list.empty()) {
+ AllocateNewChunk();
+ }
+ MapInterval* const interval = free_list.back();
+ free_list.pop_back();
+ return interval;
}
- void MarkAsModified(const bool is_modified_, const u64 tick) {
- is_modified = is_modified_;
- ticks = tick;
+ void Release(MapInterval* interval) {
+ free_list.push_back(interval);
}
- bool IsModified() const {
- return is_modified;
- }
+private:
+ struct Chunk {
+ std::unique_ptr<Chunk> next;
+ std::array<MapInterval, 0x8000> data;
+ };
- u64 GetModificationTick() const {
- return ticks;
- }
+ void AllocateNewChunk();
- void MarkAsWritten(const bool is_written_) {
- is_written = is_written_;
- }
+ void FillFreeList(Chunk& chunk);
- bool IsWritten() const {
- return is_written;
- }
+ std::vector<MapInterval*> free_list;
+ std::unique_ptr<Chunk>* new_chunk = &first_chunk.next;
-private:
- VAddr start;
- VAddr end;
- GPUVAddr gpu_addr;
- VAddr cpu_addr{};
- bool is_written{};
- bool is_modified{};
- bool is_registered{};
- u64 ticks{};
+ Chunk first_chunk;
};
} // namespace VideoCommon
diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp
new file mode 100644
index 000000000..b60f86260
--- /dev/null
+++ b/src/video_core/cdma_pusher.cpp
@@ -0,0 +1,171 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include "command_classes/host1x.h"
+#include "command_classes/nvdec.h"
+#include "command_classes/vic.h"
+#include "common/bit_util.h"
+#include "video_core/cdma_pusher.h"
+#include "video_core/command_classes/nvdec_common.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+CDmaPusher::CDmaPusher(GPU& gpu)
+ : gpu(gpu), nvdec_processor(std::make_shared<Nvdec>(gpu)),
+ vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)),
+ host1x_processor(std::make_unique<Host1x>(gpu)),
+ nvdec_sync(std::make_unique<SyncptIncrManager>(gpu)),
+ vic_sync(std::make_unique<SyncptIncrManager>(gpu)) {}
+
+CDmaPusher::~CDmaPusher() = default;
+
+void CDmaPusher::Push(ChCommandHeaderList&& entries) {
+ cdma_queue.push(std::move(entries));
+}
+
+void CDmaPusher::DispatchCalls() {
+ while (!cdma_queue.empty()) {
+ Step();
+ }
+}
+
+void CDmaPusher::Step() {
+ const auto entries{cdma_queue.front()};
+ cdma_queue.pop();
+
+ std::vector<u32> values(entries.size());
+ std::memcpy(values.data(), entries.data(), entries.size() * sizeof(u32));
+
+ for (const u32 value : values) {
+ if (mask != 0) {
+ const u32 lbs = Common::CountTrailingZeroes32(mask);
+ mask &= ~(1U << lbs);
+ ExecuteCommand(static_cast<u32>(offset + lbs), value);
+ continue;
+ } else if (count != 0) {
+ --count;
+ ExecuteCommand(static_cast<u32>(offset), value);
+ if (incrementing) {
+ ++offset;
+ }
+ continue;
+ }
+ const auto mode = static_cast<ChSubmissionMode>((value >> 28) & 0xf);
+ switch (mode) {
+ case ChSubmissionMode::SetClass: {
+ mask = value & 0x3f;
+ offset = (value >> 16) & 0xfff;
+ current_class = static_cast<ChClassId>((value >> 6) & 0x3ff);
+ break;
+ }
+ case ChSubmissionMode::Incrementing:
+ case ChSubmissionMode::NonIncrementing:
+ count = value & 0xffff;
+ offset = (value >> 16) & 0xfff;
+ incrementing = mode == ChSubmissionMode::Incrementing;
+ break;
+ case ChSubmissionMode::Mask:
+ mask = value & 0xffff;
+ offset = (value >> 16) & 0xfff;
+ break;
+ case ChSubmissionMode::Immediate: {
+ const u32 data = value & 0xfff;
+ offset = (value >> 16) & 0xfff;
+ ExecuteCommand(static_cast<u32>(offset), data);
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", static_cast<u32>(mode));
+ break;
+ }
+ }
+}
+
+void CDmaPusher::ExecuteCommand(u32 offset, u32 data) {
+ switch (current_class) {
+ case ChClassId::NvDec:
+ ThiStateWrite(nvdec_thi_state, offset, {data});
+ switch (static_cast<ThiMethod>(offset)) {
+ case ThiMethod::IncSyncpt: {
+ LOG_DEBUG(Service_NVDRV, "NVDEC Class IncSyncpt Method");
+ const auto syncpoint_id = static_cast<u32>(data & 0xFF);
+ const auto cond = static_cast<u32>((data >> 8) & 0xFF);
+ if (cond == 0) {
+ nvdec_sync->Increment(syncpoint_id);
+ } else {
+ nvdec_sync->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id);
+ nvdec_sync->SignalDone(syncpoint_id);
+ }
+ break;
+ }
+ case ThiMethod::SetMethod1:
+ LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}",
+ static_cast<u32>(nvdec_thi_state.method_0));
+ nvdec_processor->ProcessMethod(
+ static_cast<Tegra::Nvdec::Method>(nvdec_thi_state.method_0), {data});
+ break;
+ default:
+ break;
+ }
+ break;
+ case ChClassId::GraphicsVic:
+ ThiStateWrite(vic_thi_state, static_cast<u32>(offset), {data});
+ switch (static_cast<ThiMethod>(offset)) {
+ case ThiMethod::IncSyncpt: {
+ LOG_DEBUG(Service_NVDRV, "VIC Class IncSyncpt Method");
+ const auto syncpoint_id = static_cast<u32>(data & 0xFF);
+ const auto cond = static_cast<u32>((data >> 8) & 0xFF);
+ if (cond == 0) {
+ vic_sync->Increment(syncpoint_id);
+ } else {
+ vic_sync->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id);
+ vic_sync->SignalDone(syncpoint_id);
+ }
+ break;
+ }
+ case ThiMethod::SetMethod1:
+ LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
+ static_cast<u32>(vic_thi_state.method_0), data);
+ vic_processor->ProcessMethod(static_cast<Tegra::Vic::Method>(vic_thi_state.method_0),
+ {data});
+ break;
+ default:
+ break;
+ }
+ break;
+ case ChClassId::Host1x:
+ // This device is mainly for syncpoint synchronization
+ LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
+ host1x_processor->ProcessMethod(static_cast<Tegra::Host1x::Method>(offset), {data});
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
+ break;
+ }
+}
+
+void CDmaPusher::ThiStateWrite(ThiRegisters& state, u32 offset, const std::vector<u32>& arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&state) + sizeof(u32) * offset;
+ std::memcpy(state_offset, arguments.data(), sizeof(u32) * arguments.size());
+}
+
+} // namespace Tegra
diff --git a/src/video_core/cdma_pusher.h b/src/video_core/cdma_pusher.h
new file mode 100644
index 000000000..982f309c5
--- /dev/null
+++ b/src/video_core/cdma_pusher.h
@@ -0,0 +1,138 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+#include <queue>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "video_core/command_classes/sync_manager.h"
+
+namespace Tegra {
+
+class GPU;
+class Nvdec;
+class Vic;
+class Host1x;
+
+enum class ChSubmissionMode : u32 {
+ SetClass = 0,
+ Incrementing = 1,
+ NonIncrementing = 2,
+ Mask = 3,
+ Immediate = 4,
+ Restart = 5,
+ Gather = 6,
+};
+
+enum class ChClassId : u32 {
+ NoClass = 0x0,
+ Host1x = 0x1,
+ VideoEncodeMpeg = 0x20,
+ VideoEncodeNvEnc = 0x21,
+ VideoStreamingVi = 0x30,
+ VideoStreamingIsp = 0x32,
+ VideoStreamingIspB = 0x34,
+ VideoStreamingViI2c = 0x36,
+ GraphicsVic = 0x5d,
+ Graphics3D = 0x60,
+ GraphicsGpu = 0x61,
+ Tsec = 0xe0,
+ TsecB = 0xe1,
+ NvJpg = 0xc0,
+ NvDec = 0xf0
+};
+
+enum class ChMethod : u32 {
+ Empty = 0,
+ SetMethod = 0x10,
+ SetData = 0x11,
+};
+
+union ChCommandHeader {
+ u32 raw;
+ BitField<0, 16, u32> value;
+ BitField<16, 12, ChMethod> method_offset;
+ BitField<28, 4, ChSubmissionMode> submission_mode;
+};
+static_assert(sizeof(ChCommandHeader) == sizeof(u32), "ChCommand header is an invalid size");
+
+struct ChCommand {
+ ChClassId class_id{};
+ int method_offset{};
+ std::vector<u32> arguments;
+};
+
+using ChCommandHeaderList = std::vector<Tegra::ChCommandHeader>;
+using ChCommandList = std::vector<Tegra::ChCommand>;
+
+struct ThiRegisters {
+ u32_le increment_syncpt{};
+ INSERT_PADDING_WORDS(1);
+ u32_le increment_syncpt_error{};
+ u32_le ctx_switch_incremement_syncpt{};
+ INSERT_PADDING_WORDS(4);
+ u32_le ctx_switch{};
+ INSERT_PADDING_WORDS(1);
+ u32_le ctx_syncpt_eof{};
+ INSERT_PADDING_WORDS(5);
+ u32_le method_0{};
+ u32_le method_1{};
+ INSERT_PADDING_WORDS(12);
+ u32_le int_status{};
+ u32_le int_mask{};
+};
+
+enum class ThiMethod : u32 {
+ IncSyncpt = offsetof(ThiRegisters, increment_syncpt) / sizeof(u32),
+ SetMethod0 = offsetof(ThiRegisters, method_0) / sizeof(u32),
+ SetMethod1 = offsetof(ThiRegisters, method_1) / sizeof(u32),
+};
+
+class CDmaPusher {
+public:
+ explicit CDmaPusher(GPU& gpu);
+ ~CDmaPusher();
+
+ /// Push NVDEC command buffer entries into queue
+ void Push(ChCommandHeaderList&& entries);
+
+ /// Process queued command buffer entries
+ void DispatchCalls();
+
+ /// Process one queue element
+ void Step();
+
+ /// Invoke command class devices to execute the command based on the current state
+ void ExecuteCommand(u32 offset, u32 data);
+
+private:
+ /// Write arguments value to the ThiRegisters member at the specified offset
+ void ThiStateWrite(ThiRegisters& state, u32 offset, const std::vector<u32>& arguments);
+
+ GPU& gpu;
+
+ std::shared_ptr<Tegra::Nvdec> nvdec_processor;
+ std::unique_ptr<Tegra::Vic> vic_processor;
+ std::unique_ptr<Tegra::Host1x> host1x_processor;
+ std::unique_ptr<SyncptIncrManager> nvdec_sync;
+ std::unique_ptr<SyncptIncrManager> vic_sync;
+ ChClassId current_class{};
+ ThiRegisters vic_thi_state{};
+ ThiRegisters nvdec_thi_state{};
+
+ s32 count{};
+ s32 offset{};
+ s32 mask{};
+ bool incrementing{};
+
+ // Queue of command lists to be processed
+ std::queue<ChCommandHeaderList> cdma_queue;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp
new file mode 100644
index 000000000..1adf3cd13
--- /dev/null
+++ b/src/video_core/command_classes/codecs/codec.cpp
@@ -0,0 +1,115 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <fstream>
+#include <vector>
+#include "common/assert.h"
+#include "video_core/command_classes/codecs/codec.h"
+#include "video_core/command_classes/codecs/h264.h"
+#include "video_core/command_classes/codecs/vp9.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+extern "C" {
+#include <libavutil/opt.h>
+}
+
+namespace Tegra {
+
+Codec::Codec(GPU& gpu_)
+ : gpu(gpu_), h264_decoder(std::make_unique<Decoder::H264>(gpu)),
+ vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
+
+Codec::~Codec() {
+ if (!initialized) {
+ return;
+ }
+ // Free libav memory
+ avcodec_send_packet(av_codec_ctx, nullptr);
+ avcodec_receive_frame(av_codec_ctx, av_frame);
+ avcodec_flush_buffers(av_codec_ctx);
+
+ av_frame_unref(av_frame);
+ av_free(av_frame);
+ avcodec_close(av_codec_ctx);
+}
+
+void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
+ LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", static_cast<u32>(codec));
+ current_codec = codec;
+}
+
+void Codec::StateWrite(u32 offset, u64 arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u64);
+ std::memcpy(state_offset, &arguments, sizeof(u64));
+}
+
+void Codec::Decode() {
+ bool is_first_frame = false;
+
+ if (!initialized) {
+ if (current_codec == NvdecCommon::VideoCodec::H264) {
+ av_codec = avcodec_find_decoder(AV_CODEC_ID_H264);
+ } else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
+ av_codec = avcodec_find_decoder(AV_CODEC_ID_VP9);
+ } else {
+ LOG_ERROR(Service_NVDRV, "Unknown video codec {}", static_cast<u32>(current_codec));
+ return;
+ }
+
+ av_codec_ctx = avcodec_alloc_context3(av_codec);
+ av_frame = av_frame_alloc();
+ av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
+
+ // TODO(ameerj): libavcodec gpu hw acceleration
+
+ const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
+ if (av_error < 0) {
+ LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
+ av_frame_unref(av_frame);
+ av_free(av_frame);
+ avcodec_close(av_codec_ctx);
+ return;
+ }
+ initialized = true;
+ is_first_frame = true;
+ }
+ bool vp9_hidden_frame = false;
+
+ AVPacket packet{};
+ av_init_packet(&packet);
+ std::vector<u8> frame_data;
+
+ if (current_codec == NvdecCommon::VideoCodec::H264) {
+ frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
+ } else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
+ frame_data = vp9_decoder->ComposeFrameHeader(state);
+ vp9_hidden_frame = vp9_decoder->WasFrameHidden();
+ }
+
+ packet.data = frame_data.data();
+ packet.size = static_cast<int>(frame_data.size());
+
+ avcodec_send_packet(av_codec_ctx, &packet);
+
+ if (!vp9_hidden_frame) {
+ // Only receive/store visible frames
+ avcodec_receive_frame(av_codec_ctx, av_frame);
+ }
+}
+
+AVFrame* Codec::GetCurrentFrame() {
+ return av_frame;
+}
+
+const AVFrame* Codec::GetCurrentFrame() const {
+ return av_frame;
+}
+
+NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
+ return current_codec;
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h
new file mode 100644
index 000000000..5bbe6a332
--- /dev/null
+++ b/src/video_core/command_classes/codecs/codec.h
@@ -0,0 +1,66 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "common/common_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+extern "C" {
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <libavcodec/avcodec.h>
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+namespace Tegra {
+class GPU;
+struct VicRegisters;
+
+namespace Decoder {
+class H264;
+class VP9;
+} // namespace Decoder
+
+class Codec {
+public:
+ explicit Codec(GPU& gpu);
+ ~Codec();
+
+ /// Sets NVDEC video stream codec
+ void SetTargetCodec(NvdecCommon::VideoCodec codec);
+
+ /// Populate NvdecRegisters state with argument value at the provided offset
+ void StateWrite(u32 offset, u64 arguments);
+
+ /// Call decoders to construct headers, decode AVFrame with ffmpeg
+ void Decode();
+
+ /// Returns most recently decoded frame
+ [[nodiscard]] AVFrame* GetCurrentFrame();
+ [[nodiscard]] const AVFrame* GetCurrentFrame() const;
+
+ /// Returns the value of current_codec
+ [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
+
+private:
+ bool initialized{};
+ NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
+
+ AVCodec* av_codec{nullptr};
+ AVCodecContext* av_codec_ctx{nullptr};
+ AVFrame* av_frame{nullptr};
+
+ GPU& gpu;
+ std::unique_ptr<Decoder::H264> h264_decoder;
+ std::unique_ptr<Decoder::VP9> vp9_decoder;
+
+ NvdecCommon::NvdecRegisters state{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/h264.cpp b/src/video_core/command_classes/codecs/h264.cpp
new file mode 100644
index 000000000..33e063e20
--- /dev/null
+++ b/src/video_core/command_classes/codecs/h264.cpp
@@ -0,0 +1,293 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <array>
+#include "common/bit_util.h"
+#include "video_core/command_classes/codecs/h264.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+// ZigZag LUTs from libavcodec.
+constexpr std::array<u8, 64> zig_zag_direct{
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
+ 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
+ 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
+};
+
+constexpr std::array<u8, 16> zig_zag_scan{
+ 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
+ 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
+};
+} // Anonymous namespace
+
+H264::H264(GPU& gpu_) : gpu(gpu_) {}
+
+H264::~H264() = default;
+
+const std::vector<u8>& H264::ComposeFrameHeader(NvdecCommon::NvdecRegisters& state,
+ bool is_first_frame) {
+ H264DecoderContext context{};
+ gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
+
+ const s32 frame_number = static_cast<s32>((context.h264_parameter_set.flags >> 46) & 0x1ffff);
+ if (!is_first_frame && frame_number != 0) {
+ frame.resize(context.frame_data_size);
+
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
+ } else {
+ /// Encode header
+ H264BitWriter writer{};
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(7, 5);
+ writer.WriteU(100, 8);
+ writer.WriteU(0, 8);
+ writer.WriteU(31, 8);
+ writer.WriteUe(0);
+ const auto chroma_format_idc =
+ static_cast<u32>((context.h264_parameter_set.flags >> 12) & 3);
+ writer.WriteUe(chroma_format_idc);
+ if (chroma_format_idc == 3) {
+ writer.WriteBit(false);
+ }
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+ writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
+ writer.WriteBit(false); // Scaling matrix present flag
+
+ const auto order_cnt_type = static_cast<u32>((context.h264_parameter_set.flags >> 14) & 3);
+ writer.WriteUe(static_cast<u32>((context.h264_parameter_set.flags >> 8) & 0xf));
+ writer.WriteUe(order_cnt_type);
+ if (order_cnt_type == 0) {
+ writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt);
+ } else if (order_cnt_type == 1) {
+ writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
+
+ writer.WriteSe(0);
+ writer.WriteSe(0);
+ writer.WriteUe(0);
+ }
+
+ const s32 pic_height = context.h264_parameter_set.pic_height_in_map_units /
+ (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
+
+ writer.WriteUe(16);
+ writer.WriteBit(false);
+ writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
+ writer.WriteUe(pic_height - 1);
+ writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
+
+ if (!context.h264_parameter_set.frame_mbs_only_flag) {
+ writer.WriteBit(((context.h264_parameter_set.flags >> 0) & 1) != 0);
+ }
+
+ writer.WriteBit(((context.h264_parameter_set.flags >> 1) & 1) != 0);
+ writer.WriteBit(false); // Frame cropping flag
+ writer.WriteBit(false); // VUI parameter present flag
+
+ writer.End();
+
+ // H264 PPS
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(8, 5);
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+
+ writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
+ writer.WriteBit(false);
+ writer.WriteUe(0);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
+ writer.WriteBit(((context.h264_parameter_set.flags >> 2) & 1) != 0);
+ writer.WriteU(static_cast<s32>((context.h264_parameter_set.flags >> 32) & 0x3), 2);
+ s32 pic_init_qp = static_cast<s32>((context.h264_parameter_set.flags >> 16) & 0x3f);
+ pic_init_qp = (pic_init_qp << 26) >> 26;
+ writer.WriteSe(pic_init_qp);
+ writer.WriteSe(0);
+ s32 chroma_qp_index_offset =
+ static_cast<s32>((context.h264_parameter_set.flags >> 22) & 0x1f);
+ chroma_qp_index_offset = (chroma_qp_index_offset << 27) >> 27;
+
+ writer.WriteSe(chroma_qp_index_offset);
+ writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_flag != 0);
+ writer.WriteBit(((context.h264_parameter_set.flags >> 3) & 1) != 0);
+ writer.WriteBit(context.h264_parameter_set.redundant_pic_count_flag != 0);
+ writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
+
+ writer.WriteBit(true);
+
+ for (s32 index = 0; index < 6; index++) {
+ writer.WriteBit(true);
+ const auto matrix_x4 =
+ std::vector<u8>(context.scaling_matrix_4.begin(), context.scaling_matrix_4.end());
+ writer.WriteScalingList(matrix_x4, index * 16, 16);
+ }
+
+ if (context.h264_parameter_set.transform_8x8_mode_flag) {
+ for (s32 index = 0; index < 2; index++) {
+ writer.WriteBit(true);
+ const auto matrix_x8 = std::vector<u8>(context.scaling_matrix_8.begin(),
+ context.scaling_matrix_8.end());
+
+ writer.WriteScalingList(matrix_x8, index * 64, 64);
+ }
+ }
+
+ s32 chroma_qp_index_offset2 =
+ static_cast<s32>((context.h264_parameter_set.flags >> 27) & 0x1f);
+ chroma_qp_index_offset2 = (chroma_qp_index_offset2 << 27) >> 27;
+
+ writer.WriteSe(chroma_qp_index_offset2);
+
+ writer.End();
+
+ const auto& encoded_header = writer.GetByteArray();
+ frame.resize(encoded_header.size() + context.frame_data_size);
+ std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
+
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+ frame.data() + encoded_header.size(),
+ context.frame_data_size);
+ }
+
+ return frame;
+}
+
+H264BitWriter::H264BitWriter() = default;
+
+H264BitWriter::~H264BitWriter() = default;
+
+void H264BitWriter::WriteU(s32 value, s32 value_sz) {
+ WriteBits(value, value_sz);
+}
+
+void H264BitWriter::WriteSe(s32 value) {
+ WriteExpGolombCodedInt(value);
+}
+
+void H264BitWriter::WriteUe(u32 value) {
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::End() {
+ WriteBit(true);
+ Flush();
+}
+
+void H264BitWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+void H264BitWriter::WriteScalingList(const std::vector<u8>& list, s32 start, s32 count) {
+ std::vector<u8> scan(count);
+ if (count == 16) {
+ std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
+ } else {
+ std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
+ }
+ u8 last_scale = 8;
+
+ for (s32 index = 0; index < count; index++) {
+ const u8 value = list[start + scan[index]];
+ const s32 delta_scale = static_cast<s32>(value - last_scale);
+
+ WriteSe(delta_scale);
+
+ last_scale = value;
+ }
+}
+
+std::vector<u8>& H264BitWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& H264BitWriter::GetByteArray() const {
+ return byte_array;
+}
+
+void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
+ s32 value_pos = 0;
+
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free_bits = GetFreeBufferBits();
+
+ if (copy_size > free_bits) {
+ copy_size = free_bits;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
+ const s32 sign = value <= 0 ? 0 : 1;
+ if (value < 0) {
+ value = -value;
+ }
+ value = (value << 1) - sign;
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
+ const s32 size = 32 - Common::CountLeadingZeroes32(static_cast<s32>(value + 1));
+ WriteBits(1, size);
+
+ value -= (1U << (size - 1)) - 1;
+ WriteBits(static_cast<s32>(value), size - 1);
+}
+
+s32 H264BitWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void H264BitWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+
+ buffer = 0;
+ buffer_pos = 0;
+}
+} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/h264.h b/src/video_core/command_classes/codecs/h264.h
new file mode 100644
index 000000000..273449495
--- /dev/null
+++ b/src/video_core/command_classes/codecs/h264.h
@@ -0,0 +1,118 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#pragma once
+
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+namespace Tegra {
+class GPU;
+namespace Decoder {
+
+class H264BitWriter {
+public:
+ H264BitWriter();
+ ~H264BitWriter();
+
+ /// The following Write methods are based on clause 9.1 in the H.264 specification.
+ /// WriteSe and WriteUe write in the Exp-Golomb-coded syntax
+ void WriteU(s32 value, s32 value_sz);
+ void WriteSe(s32 value);
+ void WriteUe(u32 value);
+
+ /// Finalize the bitstream
+ void End();
+
+ /// append a bit to the stream, equivalent value to the state parameter
+ void WriteBit(bool state);
+
+ /// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
+ /// Writes the scaling matrices of the sream
+ void WriteScalingList(const std::vector<u8>& list, s32 start, s32 count);
+
+ /// Return the bitstream as a vector.
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ void WriteBits(s32 value, s32 bit_count);
+ void WriteExpGolombCodedInt(s32 value);
+ void WriteExpGolombCodedUInt(u32 value);
+ [[nodiscard]] s32 GetFreeBufferBits();
+ void Flush();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class H264 {
+public:
+ explicit H264(GPU& gpu);
+ ~H264();
+
+ /// Compose the H264 header of the frame for FFmpeg decoding
+ [[nodiscard]] const std::vector<u8>& ComposeFrameHeader(NvdecCommon::NvdecRegisters& state,
+ bool is_first_frame = false);
+
+private:
+ struct H264ParameterSet {
+ u32 log2_max_pic_order_cnt{};
+ u32 delta_pic_order_always_zero_flag{};
+ u32 frame_mbs_only_flag{};
+ u32 pic_width_in_mbs{};
+ u32 pic_height_in_map_units{};
+ INSERT_PADDING_WORDS(1);
+ u32 entropy_coding_mode_flag{};
+ u32 bottom_field_pic_order_flag{};
+ u32 num_refidx_l0_default_active{};
+ u32 num_refidx_l1_default_active{};
+ u32 deblocking_filter_control_flag{};
+ u32 redundant_pic_count_flag{};
+ u32 transform_8x8_mode_flag{};
+ INSERT_PADDING_WORDS(9);
+ u64 flags{};
+ u32 frame_number{};
+ u32 frame_number2{};
+ };
+ static_assert(sizeof(H264ParameterSet) == 0x68, "H264ParameterSet is an invalid size");
+
+ struct H264DecoderContext {
+ INSERT_PADDING_BYTES(0x48);
+ u32 frame_data_size{};
+ INSERT_PADDING_BYTES(0xc);
+ H264ParameterSet h264_parameter_set{};
+ INSERT_PADDING_BYTES(0x100);
+ std::array<u8, 0x60> scaling_matrix_4;
+ std::array<u8, 0x80> scaling_matrix_8;
+ };
+ static_assert(sizeof(H264DecoderContext) == 0x2a0, "H264DecoderContext is an invalid size");
+
+ std::vector<u8> frame;
+ GPU& gpu;
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp
new file mode 100644
index 000000000..ab44fdc9e
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9.cpp
@@ -0,0 +1,1040 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring> // for std::memcpy
+#include <numeric>
+#include "video_core/command_classes/codecs/vp9.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+// Default compressed header probabilities once frame context resets
+constexpr Vp9EntropyProbs default_probs{
+ .y_mode_prob{
+ 65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
+ 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
+ },
+ .partition_prob{
+ 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0,
+ 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0,
+ 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0,
+ 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0,
+ },
+ .coef_probs{
+ 195, 29, 183, 0, 84, 49, 136, 0, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 31, 107, 169, 0, 35, 99, 159, 0, 17, 82, 140, 0, 8, 66, 114, 0,
+ 2, 44, 76, 0, 1, 19, 32, 0, 40, 132, 201, 0, 29, 114, 187, 0, 13, 91, 157, 0,
+ 7, 75, 127, 0, 3, 58, 95, 0, 1, 28, 47, 0, 69, 142, 221, 0, 42, 122, 201, 0,
+ 15, 91, 159, 0, 6, 67, 121, 0, 1, 42, 77, 0, 1, 17, 31, 0, 102, 148, 228, 0,
+ 67, 117, 204, 0, 17, 82, 154, 0, 6, 59, 114, 0, 2, 39, 75, 0, 1, 15, 29, 0,
+ 156, 57, 233, 0, 119, 57, 212, 0, 58, 48, 163, 0, 29, 40, 124, 0, 12, 30, 81, 0,
+ 3, 12, 31, 0, 191, 107, 226, 0, 124, 117, 204, 0, 25, 99, 155, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 29, 148, 210, 0, 37, 126, 194, 0, 8, 93, 157, 0,
+ 2, 68, 118, 0, 1, 39, 69, 0, 1, 17, 33, 0, 41, 151, 213, 0, 27, 123, 193, 0,
+ 3, 82, 144, 0, 1, 58, 105, 0, 1, 32, 60, 0, 1, 13, 26, 0, 59, 159, 220, 0,
+ 23, 126, 198, 0, 4, 88, 151, 0, 1, 66, 114, 0, 1, 38, 71, 0, 1, 18, 34, 0,
+ 114, 136, 232, 0, 51, 114, 207, 0, 11, 83, 155, 0, 3, 56, 105, 0, 1, 33, 65, 0,
+ 1, 17, 34, 0, 149, 65, 234, 0, 121, 57, 215, 0, 61, 49, 166, 0, 28, 36, 114, 0,
+ 12, 25, 76, 0, 3, 16, 42, 0, 214, 49, 220, 0, 132, 63, 188, 0, 42, 65, 137, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 137, 221, 0, 104, 131, 216, 0,
+ 49, 111, 192, 0, 21, 87, 155, 0, 2, 49, 87, 0, 1, 16, 28, 0, 89, 163, 230, 0,
+ 90, 137, 220, 0, 29, 100, 183, 0, 10, 70, 135, 0, 2, 42, 81, 0, 1, 17, 33, 0,
+ 108, 167, 237, 0, 55, 133, 222, 0, 15, 97, 179, 0, 4, 72, 135, 0, 1, 45, 85, 0,
+ 1, 19, 38, 0, 124, 146, 240, 0, 66, 124, 224, 0, 17, 88, 175, 0, 4, 58, 122, 0,
+ 1, 36, 75, 0, 1, 18, 37, 0, 141, 79, 241, 0, 126, 70, 227, 0, 66, 58, 182, 0,
+ 30, 44, 136, 0, 12, 34, 96, 0, 2, 20, 47, 0, 229, 99, 249, 0, 143, 111, 235, 0,
+ 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 158, 236, 0,
+ 94, 146, 224, 0, 25, 117, 191, 0, 9, 87, 149, 0, 3, 56, 99, 0, 1, 33, 57, 0,
+ 83, 167, 237, 0, 68, 145, 222, 0, 10, 103, 177, 0, 2, 72, 131, 0, 1, 41, 79, 0,
+ 1, 20, 39, 0, 99, 167, 239, 0, 47, 141, 224, 0, 10, 104, 178, 0, 2, 73, 133, 0,
+ 1, 44, 85, 0, 1, 22, 47, 0, 127, 145, 243, 0, 71, 129, 228, 0, 17, 93, 177, 0,
+ 3, 61, 124, 0, 1, 41, 84, 0, 1, 21, 52, 0, 157, 78, 244, 0, 140, 72, 231, 0,
+ 69, 58, 184, 0, 31, 44, 137, 0, 14, 38, 105, 0, 8, 23, 61, 0, 125, 34, 187, 0,
+ 52, 41, 133, 0, 6, 31, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 37, 109, 153, 0, 51, 102, 147, 0, 23, 87, 128, 0, 8, 67, 101, 0, 1, 41, 63, 0,
+ 1, 19, 29, 0, 31, 154, 185, 0, 17, 127, 175, 0, 6, 96, 145, 0, 2, 73, 114, 0,
+ 1, 51, 82, 0, 1, 28, 45, 0, 23, 163, 200, 0, 10, 131, 185, 0, 2, 93, 148, 0,
+ 1, 67, 111, 0, 1, 41, 69, 0, 1, 14, 24, 0, 29, 176, 217, 0, 12, 145, 201, 0,
+ 3, 101, 156, 0, 1, 69, 111, 0, 1, 39, 63, 0, 1, 14, 23, 0, 57, 192, 233, 0,
+ 25, 154, 215, 0, 6, 109, 167, 0, 3, 78, 118, 0, 1, 48, 69, 0, 1, 21, 29, 0,
+ 202, 105, 245, 0, 108, 106, 216, 0, 18, 90, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 33, 172, 219, 0, 64, 149, 206, 0, 14, 117, 177, 0, 5, 90, 141, 0,
+ 2, 61, 95, 0, 1, 37, 57, 0, 33, 179, 220, 0, 11, 140, 198, 0, 1, 89, 148, 0,
+ 1, 60, 104, 0, 1, 33, 57, 0, 1, 12, 21, 0, 30, 181, 221, 0, 8, 141, 198, 0,
+ 1, 87, 145, 0, 1, 58, 100, 0, 1, 31, 55, 0, 1, 12, 20, 0, 32, 186, 224, 0,
+ 7, 142, 198, 0, 1, 86, 143, 0, 1, 58, 100, 0, 1, 31, 55, 0, 1, 12, 22, 0,
+ 57, 192, 227, 0, 20, 143, 204, 0, 3, 96, 154, 0, 1, 68, 112, 0, 1, 42, 69, 0,
+ 1, 19, 32, 0, 212, 35, 215, 0, 113, 47, 169, 0, 29, 48, 105, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 74, 129, 203, 0, 106, 120, 203, 0, 49, 107, 178, 0,
+ 19, 84, 144, 0, 4, 50, 84, 0, 1, 15, 25, 0, 71, 172, 217, 0, 44, 141, 209, 0,
+ 15, 102, 173, 0, 6, 76, 133, 0, 2, 51, 89, 0, 1, 24, 42, 0, 64, 185, 231, 0,
+ 31, 148, 216, 0, 8, 103, 175, 0, 3, 74, 131, 0, 1, 46, 81, 0, 1, 18, 30, 0,
+ 65, 196, 235, 0, 25, 157, 221, 0, 5, 105, 174, 0, 1, 67, 120, 0, 1, 38, 69, 0,
+ 1, 15, 30, 0, 65, 204, 238, 0, 30, 156, 224, 0, 7, 107, 177, 0, 2, 70, 124, 0,
+ 1, 42, 73, 0, 1, 18, 34, 0, 225, 86, 251, 0, 144, 104, 235, 0, 42, 99, 181, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 175, 239, 0, 112, 165, 229, 0,
+ 29, 136, 200, 0, 12, 103, 162, 0, 6, 77, 123, 0, 2, 53, 84, 0, 75, 183, 239, 0,
+ 30, 155, 221, 0, 3, 106, 171, 0, 1, 74, 128, 0, 1, 44, 76, 0, 1, 17, 28, 0,
+ 73, 185, 240, 0, 27, 159, 222, 0, 2, 107, 172, 0, 1, 75, 127, 0, 1, 42, 73, 0,
+ 1, 17, 29, 0, 62, 190, 238, 0, 21, 159, 222, 0, 2, 107, 172, 0, 1, 72, 122, 0,
+ 1, 40, 71, 0, 1, 18, 32, 0, 61, 199, 240, 0, 27, 161, 226, 0, 4, 113, 180, 0,
+ 1, 76, 129, 0, 1, 46, 80, 0, 1, 23, 41, 0, 7, 27, 153, 0, 5, 30, 95, 0,
+ 1, 16, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 75, 127, 0,
+ 57, 75, 124, 0, 27, 67, 108, 0, 10, 54, 86, 0, 1, 33, 52, 0, 1, 12, 18, 0,
+ 43, 125, 151, 0, 26, 108, 148, 0, 7, 83, 122, 0, 2, 59, 89, 0, 1, 38, 60, 0,
+ 1, 17, 27, 0, 23, 144, 163, 0, 13, 112, 154, 0, 2, 75, 117, 0, 1, 50, 81, 0,
+ 1, 31, 51, 0, 1, 14, 23, 0, 18, 162, 185, 0, 6, 123, 171, 0, 1, 78, 125, 0,
+ 1, 51, 86, 0, 1, 31, 54, 0, 1, 14, 23, 0, 15, 199, 227, 0, 3, 150, 204, 0,
+ 1, 91, 146, 0, 1, 55, 95, 0, 1, 30, 53, 0, 1, 11, 20, 0, 19, 55, 240, 0,
+ 19, 59, 196, 0, 3, 52, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 41, 166, 207, 0, 104, 153, 199, 0, 31, 123, 181, 0, 14, 101, 152, 0, 5, 72, 106, 0,
+ 1, 36, 52, 0, 35, 176, 211, 0, 12, 131, 190, 0, 2, 88, 144, 0, 1, 60, 101, 0,
+ 1, 36, 60, 0, 1, 16, 28, 0, 28, 183, 213, 0, 8, 134, 191, 0, 1, 86, 142, 0,
+ 1, 56, 96, 0, 1, 30, 53, 0, 1, 12, 20, 0, 20, 190, 215, 0, 4, 135, 192, 0,
+ 1, 84, 139, 0, 1, 53, 91, 0, 1, 28, 49, 0, 1, 11, 20, 0, 13, 196, 216, 0,
+ 2, 137, 192, 0, 1, 86, 143, 0, 1, 57, 99, 0, 1, 32, 56, 0, 1, 13, 24, 0,
+ 211, 29, 217, 0, 96, 47, 156, 0, 22, 43, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 78, 120, 193, 0, 111, 116, 186, 0, 46, 102, 164, 0, 15, 80, 128, 0,
+ 2, 49, 76, 0, 1, 18, 28, 0, 71, 161, 203, 0, 42, 132, 192, 0, 10, 98, 150, 0,
+ 3, 69, 109, 0, 1, 44, 70, 0, 1, 18, 29, 0, 57, 186, 211, 0, 30, 140, 196, 0,
+ 4, 93, 146, 0, 1, 62, 102, 0, 1, 38, 65, 0, 1, 16, 27, 0, 47, 199, 217, 0,
+ 14, 145, 196, 0, 1, 88, 142, 0, 1, 57, 98, 0, 1, 36, 62, 0, 1, 15, 26, 0,
+ 26, 219, 229, 0, 5, 155, 207, 0, 1, 94, 151, 0, 1, 60, 104, 0, 1, 36, 62, 0,
+ 1, 16, 28, 0, 233, 29, 248, 0, 146, 47, 220, 0, 43, 52, 140, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 100, 163, 232, 0, 179, 161, 222, 0, 63, 142, 204, 0,
+ 37, 113, 174, 0, 26, 89, 137, 0, 18, 68, 97, 0, 85, 181, 230, 0, 32, 146, 209, 0,
+ 7, 100, 164, 0, 3, 71, 121, 0, 1, 45, 77, 0, 1, 18, 30, 0, 65, 187, 230, 0,
+ 20, 148, 207, 0, 2, 97, 159, 0, 1, 68, 116, 0, 1, 40, 70, 0, 1, 14, 29, 0,
+ 40, 194, 227, 0, 8, 147, 204, 0, 1, 94, 155, 0, 1, 65, 112, 0, 1, 39, 66, 0,
+ 1, 14, 26, 0, 16, 208, 228, 0, 3, 151, 207, 0, 1, 98, 160, 0, 1, 67, 117, 0,
+ 1, 41, 74, 0, 1, 17, 31, 0, 17, 38, 140, 0, 7, 34, 80, 0, 1, 17, 29, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 75, 128, 0, 41, 76, 128, 0,
+ 26, 66, 116, 0, 12, 52, 94, 0, 2, 32, 55, 0, 1, 10, 16, 0, 50, 127, 154, 0,
+ 37, 109, 152, 0, 16, 82, 121, 0, 5, 59, 85, 0, 1, 35, 54, 0, 1, 13, 20, 0,
+ 40, 142, 167, 0, 17, 110, 157, 0, 2, 71, 112, 0, 1, 44, 72, 0, 1, 27, 45, 0,
+ 1, 11, 17, 0, 30, 175, 188, 0, 9, 124, 169, 0, 1, 74, 116, 0, 1, 48, 78, 0,
+ 1, 30, 49, 0, 1, 11, 18, 0, 10, 222, 223, 0, 2, 150, 194, 0, 1, 83, 128, 0,
+ 1, 48, 79, 0, 1, 27, 45, 0, 1, 11, 17, 0, 36, 41, 235, 0, 29, 36, 193, 0,
+ 10, 27, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 165, 222, 0,
+ 177, 162, 215, 0, 110, 135, 195, 0, 57, 113, 168, 0, 23, 83, 120, 0, 10, 49, 61, 0,
+ 85, 190, 223, 0, 36, 139, 200, 0, 5, 90, 146, 0, 1, 60, 103, 0, 1, 38, 65, 0,
+ 1, 18, 30, 0, 72, 202, 223, 0, 23, 141, 199, 0, 2, 86, 140, 0, 1, 56, 97, 0,
+ 1, 36, 61, 0, 1, 16, 27, 0, 55, 218, 225, 0, 13, 145, 200, 0, 1, 86, 141, 0,
+ 1, 57, 99, 0, 1, 35, 61, 0, 1, 13, 22, 0, 15, 235, 212, 0, 1, 132, 184, 0,
+ 1, 84, 139, 0, 1, 57, 97, 0, 1, 34, 56, 0, 1, 14, 23, 0, 181, 21, 201, 0,
+ 61, 37, 123, 0, 10, 38, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 47, 106, 172, 0, 95, 104, 173, 0, 42, 93, 159, 0, 18, 77, 131, 0, 4, 50, 81, 0,
+ 1, 17, 23, 0, 62, 147, 199, 0, 44, 130, 189, 0, 28, 102, 154, 0, 18, 75, 115, 0,
+ 2, 44, 65, 0, 1, 12, 19, 0, 55, 153, 210, 0, 24, 130, 194, 0, 3, 93, 146, 0,
+ 1, 61, 97, 0, 1, 31, 50, 0, 1, 10, 16, 0, 49, 186, 223, 0, 17, 148, 204, 0,
+ 1, 96, 142, 0, 1, 53, 83, 0, 1, 26, 44, 0, 1, 11, 17, 0, 13, 217, 212, 0,
+ 2, 136, 180, 0, 1, 78, 124, 0, 1, 50, 83, 0, 1, 29, 49, 0, 1, 14, 23, 0,
+ 197, 13, 247, 0, 82, 17, 222, 0, 25, 17, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 126, 186, 247, 0, 234, 191, 243, 0, 176, 177, 234, 0, 104, 158, 220, 0,
+ 66, 128, 186, 0, 55, 90, 137, 0, 111, 197, 242, 0, 46, 158, 219, 0, 9, 104, 171, 0,
+ 2, 65, 125, 0, 1, 44, 80, 0, 1, 17, 91, 0, 104, 208, 245, 0, 39, 168, 224, 0,
+ 3, 109, 162, 0, 1, 79, 124, 0, 1, 50, 102, 0, 1, 43, 102, 0, 84, 220, 246, 0,
+ 31, 177, 231, 0, 2, 115, 180, 0, 1, 79, 134, 0, 1, 55, 77, 0, 1, 60, 79, 0,
+ 43, 243, 240, 0, 8, 180, 217, 0, 1, 115, 166, 0, 1, 84, 121, 0, 1, 51, 67, 0,
+ 1, 16, 6, 0,
+ },
+ .switchable_interp_prob{235, 162, 36, 255, 34, 3, 149, 144},
+ .inter_mode_prob{
+ 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94,
+ 66, 0, 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0,
+ },
+ .intra_inter_prob{9, 102, 187, 225},
+ .comp_inter_prob{9, 102, 187, 225, 0},
+ .single_ref_prob{33, 16, 77, 74, 142, 142, 172, 170, 238, 247},
+ .comp_ref_prob{50, 126, 123, 221, 226},
+ .tx_32x32_prob{3, 136, 37, 5, 52, 13},
+ .tx_16x16_prob{20, 152, 15, 101},
+ .tx_8x8_prob{100, 66},
+ .skip_probs{192, 128, 64},
+ .joints{32, 64, 96},
+ .sign{128, 128},
+ .classes{
+ 224, 144, 192, 168, 192, 176, 192, 198, 198, 245,
+ 216, 128, 176, 160, 176, 176, 192, 198, 198, 208,
+ },
+ .class_0{216, 208},
+ .prob_bits{
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ },
+ .class_0_fr{128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64},
+ .fr{64, 96, 64, 64, 96, 64},
+ .class_0_hp{160, 160},
+ .high_precision{128, 128},
+};
+
+constexpr std::array<s32, 256> norm_lut{
+ 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+constexpr std::array<s32, 254> map_lut{
+ 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
+ 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
+ 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
+ 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
+ 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
+ 248, 249, 250, 251, 252, 253, 19,
+};
+
+// 6.2.14 Tile size calculation
+
+[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 min_log2 = 0;
+
+ while ((64 << min_log2) < sb64_cols) {
+ min_log2++;
+ }
+
+ return min_log2;
+}
+
+[[nodiscard]] s32 CalcMaxLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 max_log2 = 1;
+
+ while ((sb64_cols >> max_log2) >= 4) {
+ max_log2++;
+ }
+
+ return max_log2 - 1;
+}
+
+// Recenters probability. Based on section 6.3.6 of VP9 Specification
+[[nodiscard]] s32 RecenterNonNeg(s32 new_prob, s32 old_prob) {
+ if (new_prob > old_prob * 2) {
+ return new_prob;
+ }
+
+ if (new_prob >= old_prob) {
+ return (new_prob - old_prob) * 2;
+ }
+
+ return (old_prob - new_prob) * 2 - 1;
+}
+
+// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification
+[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
+ new_prob--;
+ old_prob--;
+
+ std::size_t index{};
+
+ if (old_prob * 2 <= 0xff) {
+ index = static_cast<std::size_t>(std::max(0, RecenterNonNeg(new_prob, old_prob) - 1));
+ } else {
+ index = static_cast<std::size_t>(
+ std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
+ }
+
+ return map_lut[index];
+}
+} // Anonymous namespace
+
+VP9::VP9(GPU& gpu) : gpu(gpu) {}
+
+VP9::~VP9() = default;
+
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ WriteProbabilityDelta(writer, new_prob, old_prob);
+ }
+}
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); ++offset) {
+ WriteProbabilityUpdate(writer, new_prob[offset], old_prob[offset]);
+ }
+}
+
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); offset += 4) {
+ WriteProbabilityUpdate(writer, new_prob[offset + 0], old_prob[offset + 0]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 1], old_prob[offset + 1]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 2], old_prob[offset + 2]);
+ }
+}
+
+void VP9::WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const int delta = RemapProbability(new_prob, old_prob);
+
+ EncodeTermSubExp(writer, delta);
+}
+
+void VP9::EncodeTermSubExp(VpxRangeEncoder& writer, s32 value) {
+ if (WriteLessThan(writer, value, 16)) {
+ writer.Write(value, 4);
+ } else if (WriteLessThan(writer, value, 32)) {
+ writer.Write(value - 16, 4);
+ } else if (WriteLessThan(writer, value, 64)) {
+ writer.Write(value - 32, 5);
+ } else {
+ value -= 64;
+
+ constexpr s32 size = 8;
+
+ const s32 mask = (1 << size) - 191;
+
+ const s32 delta = value - mask;
+
+ if (delta < 0) {
+ writer.Write(value, size - 1);
+ } else {
+ writer.Write(delta / 2 + mask, size - 1);
+ writer.Write(delta & 1, 1);
+ }
+ }
+}
+
+bool VP9::WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test) {
+ const bool is_lt = value < test;
+ writer.Write(!is_lt);
+ return is_lt;
+}
+
+void VP9::WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 2304>& new_prob,
+ const std::array<u8, 2304>& old_prob) {
+ // Note: There's 1 byte added on each packet for alignment,
+ // this byte is ignored when doing updates.
+ constexpr s32 block_bytes = 2 * 2 * 6 * 6 * 4;
+
+ const auto needs_update = [&](s32 base_index) -> bool {
+ s32 index = base_index;
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 6; k++) {
+ for (s32 l = 0; l < 6; l++) {
+ if (new_prob[index + 0] != old_prob[index + 0] ||
+ new_prob[index + 1] != old_prob[index + 1] ||
+ new_prob[index + 2] != old_prob[index + 2]) {
+ return true;
+ }
+
+ index += 4;
+ }
+ }
+ }
+ }
+ return false;
+ };
+
+ for (s32 block_index = 0; block_index < 4; block_index++) {
+ const s32 base_index = block_index * block_bytes;
+ const bool update = needs_update(base_index);
+ writer.Write(update);
+
+ if (update) {
+ s32 index = base_index;
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 6; k++) {
+ for (s32 l = 0; l < 6; l++) {
+ if (k != 0 || l < 3) {
+ WriteProbabilityUpdate(writer, new_prob[index + 0],
+ old_prob[index + 0]);
+ WriteProbabilityUpdate(writer, new_prob[index + 1],
+ old_prob[index + 1]);
+ WriteProbabilityUpdate(writer, new_prob[index + 2],
+ old_prob[index + 2]);
+ }
+ index += 4;
+ }
+ }
+ }
+ }
+ }
+
+ if (block_index == tx_mode) {
+ break;
+ }
+ }
+}
+
+void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ writer.Write(new_prob >> 1, 7);
+ }
+}
+
+Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
+ PictureInfo picture_info{};
+ gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
+ Vp9PictureInfo vp9_info = picture_info.Convert();
+
+ InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
+
+ // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
+ // order: last, golden, altref, current. It may be worthwhile to track the updates done here
+ // to avoid buffering frame data needed for reference frame updating in the header composition.
+ std::memcpy(vp9_info.frame_offsets.data(), state.surface_luma_offset.data(), 4 * sizeof(u64));
+
+ return vp9_info;
+}
+
+void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
+ EntropyProbs entropy{};
+ gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
+ entropy.Convert(dst);
+}
+
+Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) {
+ Vp9FrameContainer frame{};
+ {
+ gpu.SyncGuestHost();
+ frame.info = GetVp9PictureInfo(state);
+
+ frame.bit_stream.resize(frame.info.bitstream_size);
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.bit_stream.data(),
+ frame.info.bitstream_size);
+ }
+ // Buffer two frames, saving the last show frame info
+ if (!next_next_frame.bit_stream.empty()) {
+ Vp9FrameContainer temp{
+ .info = frame.info,
+ .bit_stream = frame.bit_stream,
+ };
+ next_next_frame.info.show_frame = frame.info.last_frame_shown;
+ frame.info = next_next_frame.info;
+ frame.bit_stream = next_next_frame.bit_stream;
+ next_next_frame = std::move(temp);
+
+ if (!next_frame.bit_stream.empty()) {
+ Vp9FrameContainer temp2{
+ .info = frame.info,
+ .bit_stream = frame.bit_stream,
+ };
+ next_frame.info.show_frame = frame.info.last_frame_shown;
+ frame.info = next_frame.info;
+ frame.bit_stream = next_frame.bit_stream;
+ next_frame = std::move(temp2);
+ } else {
+ next_frame.info = frame.info;
+ next_frame.bit_stream = frame.bit_stream;
+ }
+ } else {
+ next_next_frame.info = frame.info;
+ next_next_frame.bit_stream = frame.bit_stream;
+ }
+ return frame;
+}
+
+std::vector<u8> VP9::ComposeCompressedHeader() {
+ VpxRangeEncoder writer{};
+
+ if (!current_frame_info.lossless) {
+ if (static_cast<u32>(current_frame_info.transform_mode) >= 3) {
+ writer.Write(3, 2);
+ writer.Write(current_frame_info.transform_mode == 4);
+ } else {
+ writer.Write(current_frame_info.transform_mode, 2);
+ }
+ }
+
+ if (current_frame_info.transform_mode == 4) {
+ // tx_mode_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_8x8_prob,
+ prev_frame_probs.tx_8x8_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_16x16_prob,
+ prev_frame_probs.tx_16x16_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_32x32_prob,
+ prev_frame_probs.tx_32x32_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.tx_8x8_prob = current_frame_info.entropy.tx_8x8_prob;
+ prev_frame_probs.tx_16x16_prob = current_frame_info.entropy.tx_16x16_prob;
+ prev_frame_probs.tx_32x32_prob = current_frame_info.entropy.tx_32x32_prob;
+ }
+ }
+ // read_coef_probs() in the spec
+ WriteCoefProbabilityUpdate(writer, current_frame_info.transform_mode,
+ current_frame_info.entropy.coef_probs, prev_frame_probs.coef_probs);
+ // read_skip_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.skip_probs,
+ prev_frame_probs.skip_probs);
+
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.coef_probs = current_frame_info.entropy.coef_probs;
+ prev_frame_probs.skip_probs = current_frame_info.entropy.skip_probs;
+ }
+
+ if (!current_frame_info.intra_only) {
+ // read_inter_probs() in the spec
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.inter_mode_prob,
+ prev_frame_probs.inter_mode_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.inter_mode_prob = current_frame_info.entropy.inter_mode_prob;
+ }
+
+ if (current_frame_info.interp_filter == 4) {
+ // read_interp_filter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.switchable_interp_prob,
+ prev_frame_probs.switchable_interp_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.switchable_interp_prob =
+ current_frame_info.entropy.switchable_interp_prob;
+ }
+ }
+
+ // read_is_inter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.intra_inter_prob,
+ prev_frame_probs.intra_inter_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.intra_inter_prob = current_frame_info.entropy.intra_inter_prob;
+ }
+ // frame_reference_mode() in the spec
+ if ((current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[2] & 1) ||
+ (current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[3] & 1)) {
+ if (current_frame_info.reference_mode >= 1) {
+ writer.Write(1, 1);
+ writer.Write(current_frame_info.reference_mode == 2);
+ } else {
+ writer.Write(0, 1);
+ }
+ }
+
+ // frame_reference_mode_probs() in the spec
+ if (current_frame_info.reference_mode == 2) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_inter_prob,
+ prev_frame_probs.comp_inter_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.comp_inter_prob = current_frame_info.entropy.comp_inter_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 1) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.single_ref_prob,
+ prev_frame_probs.single_ref_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.single_ref_prob = current_frame_info.entropy.single_ref_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 0) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_ref_prob,
+ prev_frame_probs.comp_ref_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.comp_ref_prob = current_frame_info.entropy.comp_ref_prob;
+ }
+ }
+
+ // read_y_mode_probs
+ for (std::size_t index = 0; index < current_frame_info.entropy.y_mode_prob.size();
+ ++index) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.y_mode_prob[index],
+ prev_frame_probs.y_mode_prob[index]);
+ }
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.y_mode_prob = current_frame_info.entropy.y_mode_prob;
+ }
+ // read_partition_probs
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.partition_prob,
+ prev_frame_probs.partition_prob);
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.partition_prob = current_frame_info.entropy.partition_prob;
+ }
+
+ // mv_probs
+ for (s32 i = 0; i < 3; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.joints[i],
+ prev_frame_probs.joints[i]);
+ }
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.joints = current_frame_info.entropy.joints;
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.sign[i],
+ prev_frame_probs.sign[i]);
+
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.classes[index],
+ prev_frame_probs.classes[index]);
+ }
+
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0[i],
+ prev_frame_probs.class_0[i]);
+
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.prob_bits[index],
+ prev_frame_probs.prob_bits[index]);
+ }
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 3; k++) {
+ const int index = i * 2 * 3 + j * 3 + k;
+
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_fr[index],
+ prev_frame_probs.class_0_fr[index]);
+ }
+ }
+
+ for (s32 j = 0; j < 3; j++) {
+ const int index = i * 3 + j;
+
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.fr[index],
+ prev_frame_probs.fr[index]);
+ }
+ }
+
+ if (current_frame_info.allow_high_precision_mv) {
+ for (s32 index = 0; index < 2; index++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_hp[index],
+ prev_frame_probs.class_0_hp[index]);
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.high_precision[index],
+ prev_frame_probs.high_precision[index]);
+ }
+ }
+
+ // save previous probs
+ if (current_frame_info.show_frame && !current_frame_info.is_key_frame) {
+ prev_frame_probs.sign = current_frame_info.entropy.sign;
+ prev_frame_probs.classes = current_frame_info.entropy.classes;
+ prev_frame_probs.class_0 = current_frame_info.entropy.class_0;
+ prev_frame_probs.prob_bits = current_frame_info.entropy.prob_bits;
+ prev_frame_probs.class_0_fr = current_frame_info.entropy.class_0_fr;
+ prev_frame_probs.fr = current_frame_info.entropy.fr;
+ prev_frame_probs.class_0_hp = current_frame_info.entropy.class_0_hp;
+ prev_frame_probs.high_precision = current_frame_info.entropy.high_precision;
+ }
+ }
+
+ writer.End();
+ return writer.GetBuffer();
+}
+
+VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
+ VpxBitStreamWriter uncomp_writer{};
+
+ uncomp_writer.WriteU(2, 2); // Frame marker.
+ uncomp_writer.WriteU(0, 2); // Profile.
+ uncomp_writer.WriteBit(false); // Show existing frame.
+ uncomp_writer.WriteBit(!current_frame_info.is_key_frame); // is key frame?
+ uncomp_writer.WriteBit(current_frame_info.show_frame); // show frame?
+ uncomp_writer.WriteBit(current_frame_info.error_resilient_mode); // error reslience
+
+ if (current_frame_info.is_key_frame) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(0, 3); // Color space.
+ uncomp_writer.WriteU(0, 1); // Color range.
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+
+ // Reset context
+ prev_frame_probs = default_probs;
+ swap_next_golden = false;
+ loop_filter_ref_deltas.fill(0);
+ loop_filter_mode_deltas.fill(0);
+
+ // allow frames offsets to stabilize before checking for golden frames
+ grace_period = 4;
+
+ // On key frames, all frame slots are set to the current frame,
+ // so the value of the selected slot doesn't really matter.
+ frame_ctxs.fill({current_frame_number, false, default_probs});
+
+ // intra only, meaning the frame can be recreated with no other references
+ current_frame_info.intra_only = true;
+
+ } else {
+
+ if (!current_frame_info.show_frame) {
+ uncomp_writer.WriteBit(current_frame_info.intra_only);
+ if (!current_frame_info.last_frame_was_key) {
+ swap_next_golden = !swap_next_golden;
+ }
+ } else {
+ current_frame_info.intra_only = false;
+ }
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteU(0, 2); // Reset frame context.
+ }
+
+ // Last, Golden, Altref frames
+ std::array<s32, 3> ref_frame_index{0, 1, 2};
+
+ // Set when next frame is hidden
+ // altref and golden references are swapped
+ if (swap_next_golden) {
+ ref_frame_index = std::array<s32, 3>{0, 2, 1};
+ }
+
+ // update Last Frame
+ u64 refresh_frame_flags = 1;
+
+ // golden frame may refresh, determined if the next golden frame offset is changed
+ bool golden_refresh = false;
+ if (grace_period <= 0) {
+ for (s32 index = 1; index < 3; ++index) {
+ if (current_frame_info.frame_offsets[index] !=
+ next_frame.info.frame_offsets[index]) {
+ current_frame_info.refresh_frame[index] = true;
+ golden_refresh = true;
+ grace_period = 3;
+ }
+ }
+ }
+
+ if (current_frame_info.show_frame &&
+ (!next_frame.info.show_frame || next_frame.info.is_key_frame)) {
+ // Update golden frame
+ refresh_frame_flags = swap_next_golden ? 2 : 4;
+ }
+
+ if (!current_frame_info.show_frame) {
+ // Update altref
+ refresh_frame_flags = swap_next_golden ? 2 : 4;
+ } else if (golden_refresh) {
+ refresh_frame_flags = 3;
+ }
+
+ if (current_frame_info.intra_only) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ } else {
+ uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
+
+ for (s32 index = 1; index < 4; index++) {
+ uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
+ uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
+ }
+
+ uncomp_writer.WriteBit(true); // Frame size with refs.
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
+ uncomp_writer.WriteBit(current_frame_info.interp_filter == 4);
+
+ if (current_frame_info.interp_filter != 4) {
+ uncomp_writer.WriteU(current_frame_info.interp_filter, 2);
+ }
+ }
+ }
+
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteBit(true); // Refresh frame context. where do i get this info from?
+ uncomp_writer.WriteBit(true); // Frame parallel decoding mode.
+ }
+
+ int frame_ctx_idx = 0;
+ if (!current_frame_info.show_frame) {
+ frame_ctx_idx = 1;
+ }
+
+ uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
+ prev_frame_probs =
+ frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header
+ frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy};
+
+ uncomp_writer.WriteU(current_frame_info.first_level, 6);
+ uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
+ uncomp_writer.WriteBit(current_frame_info.mode_ref_delta_enabled);
+
+ if (current_frame_info.mode_ref_delta_enabled) {
+ // check if ref deltas are different, update accordingly
+ std::array<bool, 4> update_loop_filter_ref_deltas;
+ std::array<bool, 2> update_loop_filter_mode_deltas;
+
+ bool loop_filter_delta_update = false;
+
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_ref_deltas[index];
+ const s8 new_deltas = current_frame_info.ref_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_ref_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_mode_deltas[index];
+ const s8 new_deltas = current_frame_info.mode_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_mode_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ uncomp_writer.WriteBit(loop_filter_delta_update);
+
+ if (loop_filter_delta_update) {
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_ref_deltas[index]);
+
+ if (update_loop_filter_ref_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.ref_deltas[index], 6);
+ }
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_mode_deltas[index]);
+
+ if (update_loop_filter_mode_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.mode_deltas[index], 6);
+ }
+ }
+ // save new deltas
+ loop_filter_ref_deltas = current_frame_info.ref_deltas;
+ loop_filter_mode_deltas = current_frame_info.mode_deltas;
+ }
+ }
+
+ uncomp_writer.WriteU(current_frame_info.base_q_index, 8);
+
+ uncomp_writer.WriteDeltaQ(current_frame_info.y_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_ac_delta_q);
+
+ uncomp_writer.WriteBit(false); // Segmentation enabled (TODO).
+
+ const s32 min_tile_cols_log2 = CalcMinLog2TileCols(current_frame_info.frame_size.width);
+ const s32 max_tile_cols_log2 = CalcMaxLog2TileCols(current_frame_info.frame_size.width);
+
+ const s32 tile_cols_log2_diff = current_frame_info.log2_tile_cols - min_tile_cols_log2;
+ const s32 tile_cols_log2_inc_mask = (1 << tile_cols_log2_diff) - 1;
+
+ // If it's less than the maximum, we need to add an extra 0 on the bitstream
+ // to indicate that it should stop reading.
+ if (current_frame_info.log2_tile_cols < max_tile_cols_log2) {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask << 1, tile_cols_log2_diff + 1);
+ } else {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask, tile_cols_log2_diff);
+ }
+
+ const bool tile_rows_log2_is_nonzero = current_frame_info.log2_tile_rows != 0;
+
+ uncomp_writer.WriteBit(tile_rows_log2_is_nonzero);
+
+ if (tile_rows_log2_is_nonzero) {
+ uncomp_writer.WriteBit(current_frame_info.log2_tile_rows > 1);
+ }
+
+ return uncomp_writer;
+}
+
+const std::vector<u8>& VP9::ComposeFrameHeader(NvdecCommon::NvdecRegisters& state) {
+ std::vector<u8> bitstream;
+ {
+ Vp9FrameContainer curr_frame = GetCurrentFrame(state);
+ current_frame_info = curr_frame.info;
+ bitstream = std::move(curr_frame.bit_stream);
+ }
+
+ // The uncompressed header routine sets PrevProb parameters needed for the compressed header
+ auto uncomp_writer = ComposeUncompressedHeader();
+ std::vector<u8> compressed_header = ComposeCompressedHeader();
+
+ uncomp_writer.WriteU(static_cast<s32>(compressed_header.size()), 16);
+ uncomp_writer.Flush();
+ std::vector<u8> uncompressed_header = uncomp_writer.GetByteArray();
+
+ // Write headers and frame to buffer
+ frame.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
+ std::memcpy(frame.data(), uncompressed_header.data(), uncompressed_header.size());
+ std::memcpy(frame.data() + uncompressed_header.size(), compressed_header.data(),
+ compressed_header.size());
+ std::memcpy(frame.data() + uncompressed_header.size() + compressed_header.size(),
+ bitstream.data(), bitstream.size());
+
+ // keep track of frame number
+ current_frame_number++;
+ grace_period--;
+
+ // don't display hidden frames
+ hidden = !current_frame_info.show_frame;
+ return frame;
+}
+
+VpxRangeEncoder::VpxRangeEncoder() {
+ Write(false);
+}
+
+VpxRangeEncoder::~VpxRangeEncoder() = default;
+
+void VpxRangeEncoder::Write(s32 value, s32 value_size) {
+ for (s32 bit = value_size - 1; bit >= 0; bit--) {
+ Write(((value >> bit) & 1) != 0);
+ }
+}
+
+void VpxRangeEncoder::Write(bool bit) {
+ Write(bit, half_probability);
+}
+
+void VpxRangeEncoder::Write(bool bit, s32 probability) {
+ u32 local_range = range;
+ const u32 split = 1 + (((local_range - 1) * static_cast<u32>(probability)) >> 8);
+ local_range = split;
+
+ if (bit) {
+ low_value += split;
+ local_range = range - split;
+ }
+
+ s32 shift = norm_lut[local_range];
+ local_range <<= shift;
+ count += shift;
+
+ if (count >= 0) {
+ const s32 offset = shift - count;
+
+ if (((low_value << (offset - 1)) >> 31) != 0) {
+ const s32 current_pos = static_cast<s32>(base_stream.GetPosition());
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+ while (PeekByte() == 0xff) {
+ base_stream.WriteByte(0);
+
+ base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos);
+ }
+ base_stream.WriteByte(static_cast<u8>((PeekByte() + 1)));
+ base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin);
+ }
+ base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset))));
+
+ low_value <<= offset;
+ shift = count;
+ low_value &= 0xffffff;
+ count -= 8;
+ }
+
+ low_value <<= shift;
+ range = local_range;
+}
+
+void VpxRangeEncoder::End() {
+ for (std::size_t index = 0; index < 32; ++index) {
+ Write(false);
+ }
+}
+
+u8 VpxRangeEncoder::PeekByte() {
+ const u8 value = base_stream.ReadByte();
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+
+ return value;
+}
+
+VpxBitStreamWriter::VpxBitStreamWriter() = default;
+
+VpxBitStreamWriter::~VpxBitStreamWriter() = default;
+
+void VpxBitStreamWriter::WriteU(u32 value, u32 value_size) {
+ WriteBits(value, value_size);
+}
+
+void VpxBitStreamWriter::WriteS(s32 value, u32 value_size) {
+ const bool sign = value < 0;
+ if (sign) {
+ value = -value;
+ }
+
+ WriteBits(static_cast<u32>(value << 1) | (sign ? 1 : 0), value_size + 1);
+}
+
+void VpxBitStreamWriter::WriteDeltaQ(u32 value) {
+ const bool delta_coded = value != 0;
+ WriteBit(delta_coded);
+
+ if (delta_coded) {
+ WriteBits(value, 4);
+ }
+}
+
+void VpxBitStreamWriter::WriteBits(u32 value, u32 bit_count) {
+ s32 value_pos = 0;
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free = GetFreeBufferBits();
+
+ if (copy_size > free) {
+ copy_size = free;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void VpxBitStreamWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+s32 VpxBitStreamWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void VpxBitStreamWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+ buffer = 0;
+ buffer_pos = 0;
+}
+
+std::vector<u8>& VpxBitStreamWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& VpxBitStreamWriter::GetByteArray() const {
+ return byte_array;
+}
+
+} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h
new file mode 100644
index 000000000..e2504512c
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9.h
@@ -0,0 +1,196 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/stream.h"
+#include "video_core/command_classes/codecs/vp9_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+namespace Tegra {
+class GPU;
+enum class FrameType { KeyFrame = 0, InterFrame = 1 };
+namespace Decoder {
+
+/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
+/// VP9 header bitstreams.
+
+class VpxRangeEncoder {
+public:
+ VpxRangeEncoder();
+ ~VpxRangeEncoder();
+
+ VpxRangeEncoder(const VpxRangeEncoder&) = delete;
+ VpxRangeEncoder& operator=(const VpxRangeEncoder&) = delete;
+
+ VpxRangeEncoder(VpxRangeEncoder&&) = default;
+ VpxRangeEncoder& operator=(VpxRangeEncoder&&) = default;
+
+ /// Writes the rightmost value_size bits from value into the stream
+ void Write(s32 value, s32 value_size);
+
+ /// Writes a single bit with half probability
+ void Write(bool bit);
+
+ /// Writes a bit to the base_stream encoded with probability
+ void Write(bool bit, s32 probability);
+
+ /// Signal the end of the bitstream
+ void End();
+
+ [[nodiscard]] std::vector<u8>& GetBuffer() {
+ return base_stream.GetBuffer();
+ }
+
+ [[nodiscard]] const std::vector<u8>& GetBuffer() const {
+ return base_stream.GetBuffer();
+ }
+
+private:
+ u8 PeekByte();
+ Common::Stream base_stream{};
+ u32 low_value{};
+ u32 range{0xff};
+ s32 count{-24};
+ s32 half_probability{128};
+};
+
+class VpxBitStreamWriter {
+public:
+ VpxBitStreamWriter();
+ ~VpxBitStreamWriter();
+
+ VpxBitStreamWriter(const VpxBitStreamWriter&) = delete;
+ VpxBitStreamWriter& operator=(const VpxBitStreamWriter&) = delete;
+
+ VpxBitStreamWriter(VpxBitStreamWriter&&) = default;
+ VpxBitStreamWriter& operator=(VpxBitStreamWriter&&) = default;
+
+ /// Write an unsigned integer value
+ void WriteU(u32 value, u32 value_size);
+
+ /// Write a signed integer value
+ void WriteS(s32 value, u32 value_size);
+
+ /// Based on 6.2.10 of VP9 Spec, writes a delta coded value
+ void WriteDeltaQ(u32 value);
+
+ /// Write a single bit.
+ void WriteBit(bool state);
+
+ /// Pushes current buffer into buffer_array, resets buffer
+ void Flush();
+
+ /// Returns byte_array
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+
+ /// Returns const byte_array
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ /// Write bit_count bits from value into buffer
+ void WriteBits(u32 value, u32 bit_count);
+
+ /// Gets next available position in buffer, invokes Flush() if buffer is full
+ s32 GetFreeBufferBits();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class VP9 {
+public:
+ explicit VP9(GPU& gpu);
+ ~VP9();
+
+ VP9(const VP9&) = delete;
+ VP9& operator=(const VP9&) = delete;
+
+ VP9(VP9&&) = default;
+ VP9& operator=(VP9&&) = delete;
+
+ /// Composes the VP9 frame from the GPU state information. Based on the official VP9 spec
+ /// documentation
+ [[nodiscard]] const std::vector<u8>& ComposeFrameHeader(NvdecCommon::NvdecRegisters& state);
+
+ /// Returns true if the most recent frame was a hidden frame.
+ [[nodiscard]] bool WasFrameHidden() const {
+ return hidden;
+ }
+
+private:
+ /// Generates compressed header probability updates in the bitstream writer
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Generates compressed header probability updates in the bitstream writer
+ /// If probs are not equal, WriteProbabilityDelta is invoked
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Generates compressed header probability deltas in the bitstream writer
+ void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Inverse of 6.3.4 Decode term subexp
+ void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value);
+
+ /// Writes if the value is less than the test value
+ bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test);
+
+ /// Writes probability updates for the Coef probabilities
+ void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 2304>& new_prob,
+ const std::array<u8, 2304>& old_prob);
+
+ /// Write probabilities for 4-byte aligned structures
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Write motion vector probability updates. 6.3.17 in the spec
+ void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Returns VP9 information from NVDEC provided offset and size
+ [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state);
+
+ /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
+ void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
+
+ /// Returns frame to be decoded after buffering
+ [[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state);
+
+ /// Use NVDEC providied information to compose the headers for the current frame
+ [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
+ [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
+
+ GPU& gpu;
+ std::vector<u8> frame;
+
+ std::array<s8, 4> loop_filter_ref_deltas{};
+ std::array<s8, 2> loop_filter_mode_deltas{};
+
+ bool hidden = false;
+ s64 current_frame_number = -2; // since we buffer 2 frames
+ s32 grace_period = 6; // frame offsets need to stabilize
+ std::array<FrameContexts, 4> frame_ctxs{};
+ Vp9FrameContainer next_frame{};
+ Vp9FrameContainer next_next_frame{};
+ bool swap_next_golden{};
+
+ Vp9PictureInfo current_frame_info{};
+ Vp9EntropyProbs prev_frame_probs{};
+
+ s32 diff_update_probability = 252;
+ s32 frame_sync_code = 0x498342;
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h
new file mode 100644
index 000000000..4f0b05d22
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9_types.h
@@ -0,0 +1,366 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+
+namespace Decoder {
+struct Vp9FrameDimensions {
+ s16 width{};
+ s16 height{};
+ s16 luma_pitch{};
+ s16 chroma_pitch{};
+};
+static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size");
+
+enum FrameFlags : u32 {
+ IsKeyFrame = 1 << 0,
+ LastFrameIsKeyFrame = 1 << 1,
+ FrameSizeChanged = 1 << 2,
+ ErrorResilientMode = 1 << 3,
+ LastShowFrame = 1 << 4,
+ IntraOnly = 1 << 5,
+};
+
+enum class MvJointType {
+ MvJointZero = 0, /* Zero vector */
+ MvJointHnzvz = 1, /* Vert zero, hor nonzero */
+ MvJointHzvnz = 2, /* Hor zero, vert nonzero */
+ MvJointHnzvnz = 3, /* Both components nonzero */
+};
+enum class MvClassType {
+ MvClass0 = 0, /* (0, 2] integer pel */
+ MvClass1 = 1, /* (2, 4] integer pel */
+ MvClass2 = 2, /* (4, 8] integer pel */
+ MvClass3 = 3, /* (8, 16] integer pel */
+ MvClass4 = 4, /* (16, 32] integer pel */
+ MvClass5 = 5, /* (32, 64] integer pel */
+ MvClass6 = 6, /* (64, 128] integer pel */
+ MvClass7 = 7, /* (128, 256] integer pel */
+ MvClass8 = 8, /* (256, 512] integer pel */
+ MvClass9 = 9, /* (512, 1024] integer pel */
+ MvClass10 = 10, /* (1024,2048] integer pel */
+};
+
+enum class BlockSize {
+ Block4x4 = 0,
+ Block4x8 = 1,
+ Block8x4 = 2,
+ Block8x8 = 3,
+ Block8x16 = 4,
+ Block16x8 = 5,
+ Block16x16 = 6,
+ Block16x32 = 7,
+ Block32x16 = 8,
+ Block32x32 = 9,
+ Block32x64 = 10,
+ Block64x32 = 11,
+ Block64x64 = 12,
+ BlockSizes = 13,
+ BlockInvalid = BlockSizes
+};
+
+enum class PredictionMode {
+ DcPred = 0, // Average of above and left pixels
+ VPred = 1, // Vertical
+ HPred = 2, // Horizontal
+ D45Pred = 3, // Directional 45 deg = round(arctan(1 / 1) * 180 / pi)
+ D135Pred = 4, // Directional 135 deg = 180 - 45
+ D117Pred = 5, // Directional 117 deg = 180 - 63
+ D153Pred = 6, // Directional 153 deg = 180 - 27
+ D207Pred = 7, // Directional 207 deg = 180 + 27
+ D63Pred = 8, // Directional 63 deg = round(arctan(2 / 1) * 180 / pi)
+ TmPred = 9, // True-motion
+ NearestMv = 10,
+ NearMv = 11,
+ ZeroMv = 12,
+ NewMv = 13,
+ MbModeCount = 14
+};
+
+enum class TxSize {
+ Tx4x4 = 0, // 4x4 transform
+ Tx8x8 = 1, // 8x8 transform
+ Tx16x16 = 2, // 16x16 transform
+ Tx32x32 = 3, // 32x32 transform
+ TxSizes = 4
+};
+
+enum class TxMode {
+ Only4X4 = 0, // Only 4x4 transform used
+ Allow8X8 = 1, // Allow block transform size up to 8x8
+ Allow16X16 = 2, // Allow block transform size up to 16x16
+ Allow32X32 = 3, // Allow block transform size up to 32x32
+ TxModeSelect = 4, // Transform specified for each block
+ TxModes = 5
+};
+
+enum class reference_mode {
+ SingleReference = 0,
+ CompoundReference = 1,
+ ReferenceModeSelect = 2,
+ ReferenceModes = 3
+};
+
+struct Segmentation {
+ u8 enabled{};
+ u8 update_map{};
+ u8 temporal_update{};
+ u8 abs_delta{};
+ std::array<u32, 8> feature_mask{};
+ std::array<std::array<s16, 4>, 8> feature_data{};
+};
+static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size");
+
+struct LoopFilter {
+ u8 mode_ref_delta_enabled{};
+ std::array<s8, 4> ref_deltas{};
+ std::array<s8, 2> mode_deltas{};
+};
+static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size");
+
+struct Vp9EntropyProbs {
+ std::array<u8, 36> y_mode_prob{};
+ std::array<u8, 64> partition_prob{};
+ std::array<u8, 2304> coef_probs{};
+ std::array<u8, 8> switchable_interp_prob{};
+ std::array<u8, 28> inter_mode_prob{};
+ std::array<u8, 4> intra_inter_prob{};
+ std::array<u8, 5> comp_inter_prob{};
+ std::array<u8, 10> single_ref_prob{};
+ std::array<u8, 5> comp_ref_prob{};
+ std::array<u8, 6> tx_32x32_prob{};
+ std::array<u8, 4> tx_16x16_prob{};
+ std::array<u8, 2> tx_8x8_prob{};
+ std::array<u8, 3> skip_probs{};
+ std::array<u8, 3> joints{};
+ std::array<u8, 2> sign{};
+ std::array<u8, 20> classes{};
+ std::array<u8, 2> class_0{};
+ std::array<u8, 20> prob_bits{};
+ std::array<u8, 12> class_0_fr{};
+ std::array<u8, 6> fr{};
+ std::array<u8, 2> class_0_hp{};
+ std::array<u8, 2> high_precision{};
+};
+static_assert(sizeof(Vp9EntropyProbs) == 0x9F4, "Vp9EntropyProbs is an invalid size");
+
+struct Vp9PictureInfo {
+ bool is_key_frame{};
+ bool intra_only{};
+ bool last_frame_was_key{};
+ bool frame_size_changed{};
+ bool error_resilient_mode{};
+ bool last_frame_shown{};
+ bool show_frame{};
+ std::array<s8, 4> ref_frame_sign_bias{};
+ s32 base_q_index{};
+ s32 y_dc_delta_q{};
+ s32 uv_dc_delta_q{};
+ s32 uv_ac_delta_q{};
+ bool lossless{};
+ s32 transform_mode{};
+ bool allow_high_precision_mv{};
+ s32 interp_filter{};
+ s32 reference_mode{};
+ s8 comp_fixed_ref{};
+ std::array<s8, 2> comp_var_ref{};
+ s32 log2_tile_cols{};
+ s32 log2_tile_rows{};
+ bool segment_enabled{};
+ bool segment_map_update{};
+ bool segment_map_temporal_update{};
+ s32 segment_abs_delta{};
+ std::array<u32, 8> segment_feature_enable{};
+ std::array<std::array<s16, 4>, 8> segment_feature_data{};
+ bool mode_ref_delta_enabled{};
+ bool use_prev_in_find_mv_refs{};
+ std::array<s8, 4> ref_deltas{};
+ std::array<s8, 2> mode_deltas{};
+ Vp9EntropyProbs entropy{};
+ Vp9FrameDimensions frame_size{};
+ u8 first_level{};
+ u8 sharpness_level{};
+ u32 bitstream_size{};
+ std::array<u64, 4> frame_offsets{};
+ std::array<bool, 4> refresh_frame{};
+};
+
+struct Vp9FrameContainer {
+ Vp9PictureInfo info{};
+ std::vector<u8> bit_stream;
+};
+
+struct PictureInfo {
+ INSERT_PADDING_WORDS(12);
+ u32 bitstream_size{};
+ INSERT_PADDING_WORDS(5);
+ Vp9FrameDimensions last_frame_size{};
+ Vp9FrameDimensions golden_frame_size{};
+ Vp9FrameDimensions alt_frame_size{};
+ Vp9FrameDimensions current_frame_size{};
+ u32 vp9_flags{};
+ std::array<s8, 4> ref_frame_sign_bias{};
+ u8 first_level{};
+ u8 sharpness_level{};
+ u8 base_q_index{};
+ u8 y_dc_delta_q{};
+ u8 uv_ac_delta_q{};
+ u8 uv_dc_delta_q{};
+ u8 lossless{};
+ u8 tx_mode{};
+ u8 allow_high_precision_mv{};
+ u8 interp_filter{};
+ u8 reference_mode{};
+ s8 comp_fixed_ref{};
+ std::array<s8, 2> comp_var_ref{};
+ u8 log2_tile_cols{};
+ u8 log2_tile_rows{};
+ Segmentation segmentation{};
+ LoopFilter loop_filter{};
+ INSERT_PADDING_BYTES(5);
+ u32 surface_params{};
+ INSERT_PADDING_WORDS(3);
+
+ [[nodiscard]] Vp9PictureInfo Convert() const {
+ return {
+ .is_key_frame = (vp9_flags & FrameFlags::IsKeyFrame) != 0,
+ .intra_only = (vp9_flags & FrameFlags::IntraOnly) != 0,
+ .last_frame_was_key = (vp9_flags & FrameFlags::LastFrameIsKeyFrame) != 0,
+ .frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0,
+ .error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0,
+ .last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0,
+ .ref_frame_sign_bias = ref_frame_sign_bias,
+ .base_q_index = base_q_index,
+ .y_dc_delta_q = y_dc_delta_q,
+ .uv_dc_delta_q = uv_dc_delta_q,
+ .uv_ac_delta_q = uv_ac_delta_q,
+ .lossless = lossless != 0,
+ .transform_mode = tx_mode,
+ .allow_high_precision_mv = allow_high_precision_mv != 0,
+ .interp_filter = interp_filter,
+ .reference_mode = reference_mode,
+ .comp_fixed_ref = comp_fixed_ref,
+ .comp_var_ref = comp_var_ref,
+ .log2_tile_cols = log2_tile_cols,
+ .log2_tile_rows = log2_tile_rows,
+ .segment_enabled = segmentation.enabled != 0,
+ .segment_map_update = segmentation.update_map != 0,
+ .segment_map_temporal_update = segmentation.temporal_update != 0,
+ .segment_abs_delta = segmentation.abs_delta,
+ .segment_feature_enable = segmentation.feature_mask,
+ .segment_feature_data = segmentation.feature_data,
+ .mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
+ .use_prev_in_find_mv_refs = !(vp9_flags == (FrameFlags::ErrorResilientMode)) &&
+ !(vp9_flags == (FrameFlags::FrameSizeChanged)) &&
+ !(vp9_flags == (FrameFlags::IntraOnly)) &&
+ (vp9_flags == (FrameFlags::LastShowFrame)) &&
+ !(vp9_flags == (FrameFlags::LastFrameIsKeyFrame)),
+ .ref_deltas = loop_filter.ref_deltas,
+ .mode_deltas = loop_filter.mode_deltas,
+ .frame_size = current_frame_size,
+ .first_level = first_level,
+ .sharpness_level = sharpness_level,
+ .bitstream_size = bitstream_size,
+ };
+ }
+};
+static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size");
+
+struct EntropyProbs {
+ INSERT_PADDING_BYTES(1024);
+ std::array<std::array<u8, 4>, 7> inter_mode_prob{};
+ std::array<u8, 4> intra_inter_prob{};
+ INSERT_PADDING_BYTES(80);
+ std::array<std::array<u8, 1>, 2> tx_8x8_prob{};
+ std::array<std::array<u8, 2>, 2> tx_16x16_prob{};
+ std::array<std::array<u8, 3>, 2> tx_32x32_prob{};
+ std::array<u8, 4> y_mode_prob_e8{};
+ std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7{};
+ INSERT_PADDING_BYTES(64);
+ std::array<std::array<u8, 4>, 16> partition_prob{};
+ INSERT_PADDING_BYTES(10);
+ std::array<std::array<u8, 2>, 4> switchable_interp_prob{};
+ std::array<u8, 5> comp_inter_prob{};
+ std::array<u8, 4> skip_probs{};
+ std::array<u8, 3> joints{};
+ std::array<u8, 2> sign{};
+ std::array<std::array<u8, 1>, 2> class_0{};
+ std::array<std::array<u8, 3>, 2> fr{};
+ std::array<u8, 2> class_0_hp{};
+ std::array<u8, 2> high_precision{};
+ std::array<std::array<u8, 10>, 2> classes{};
+ std::array<std::array<std::array<u8, 3>, 2>, 2> class_0_fr{};
+ std::array<std::array<u8, 10>, 2> pred_bits{};
+ std::array<std::array<u8, 2>, 5> single_ref_prob{};
+ std::array<u8, 5> comp_ref_prob{};
+ INSERT_PADDING_BYTES(17);
+ std::array<std::array<std::array<std::array<std::array<std::array<u8, 4>, 6>, 6>, 2>, 2>, 4>
+ coef_probs{};
+
+ void Convert(Vp9EntropyProbs& fc) {
+ std::memcpy(fc.inter_mode_prob.data(), inter_mode_prob.data(), fc.inter_mode_prob.size());
+
+ std::memcpy(fc.intra_inter_prob.data(), intra_inter_prob.data(),
+ fc.intra_inter_prob.size());
+
+ std::memcpy(fc.tx_8x8_prob.data(), tx_8x8_prob.data(), fc.tx_8x8_prob.size());
+ std::memcpy(fc.tx_16x16_prob.data(), tx_16x16_prob.data(), fc.tx_16x16_prob.size());
+ std::memcpy(fc.tx_32x32_prob.data(), tx_32x32_prob.data(), fc.tx_32x32_prob.size());
+
+ for (s32 i = 0; i < 4; i++) {
+ for (s32 j = 0; j < 9; j++) {
+ fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i];
+ }
+ }
+
+ std::memcpy(fc.partition_prob.data(), partition_prob.data(), fc.partition_prob.size());
+
+ std::memcpy(fc.switchable_interp_prob.data(), switchable_interp_prob.data(),
+ fc.switchable_interp_prob.size());
+ std::memcpy(fc.comp_inter_prob.data(), comp_inter_prob.data(), fc.comp_inter_prob.size());
+ std::memcpy(fc.skip_probs.data(), skip_probs.data(), fc.skip_probs.size());
+
+ std::memcpy(fc.joints.data(), joints.data(), fc.joints.size());
+
+ std::memcpy(fc.sign.data(), sign.data(), fc.sign.size());
+ std::memcpy(fc.class_0.data(), class_0.data(), fc.class_0.size());
+ std::memcpy(fc.fr.data(), fr.data(), fc.fr.size());
+ std::memcpy(fc.class_0_hp.data(), class_0_hp.data(), fc.class_0_hp.size());
+ std::memcpy(fc.high_precision.data(), high_precision.data(), fc.high_precision.size());
+ std::memcpy(fc.classes.data(), classes.data(), fc.classes.size());
+ std::memcpy(fc.class_0_fr.data(), class_0_fr.data(), fc.class_0_fr.size());
+ std::memcpy(fc.prob_bits.data(), pred_bits.data(), fc.prob_bits.size());
+ std::memcpy(fc.single_ref_prob.data(), single_ref_prob.data(), fc.single_ref_prob.size());
+ std::memcpy(fc.comp_ref_prob.data(), comp_ref_prob.data(), fc.comp_ref_prob.size());
+
+ std::memcpy(fc.coef_probs.data(), coef_probs.data(), fc.coef_probs.size());
+ }
+};
+static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size");
+
+enum class Ref { Last, Golden, AltRef };
+
+struct RefPoolElement {
+ s64 frame{};
+ Ref ref{};
+ bool refresh{};
+};
+
+struct FrameContexts {
+ s64 from{};
+ bool adapted{};
+ Vp9EntropyProbs probs{};
+};
+
+}; // namespace Decoder
+}; // namespace Tegra
diff --git a/src/video_core/command_classes/host1x.cpp b/src/video_core/command_classes/host1x.cpp
new file mode 100644
index 000000000..c4dd4881a
--- /dev/null
+++ b/src/video_core/command_classes/host1x.cpp
@@ -0,0 +1,39 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/command_classes/host1x.h"
+#include "video_core/gpu.h"
+
+Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {}
+
+Tegra::Host1x::~Host1x() = default;
+
+void Tegra::Host1x::StateWrite(u32 offset, u32 arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u32);
+ std::memcpy(state_offset, &arguments, sizeof(u32));
+}
+
+void Tegra::Host1x::ProcessMethod(Method method, const std::vector<u32>& arguments) {
+ StateWrite(static_cast<u32>(method), arguments[0]);
+ switch (method) {
+ case Method::WaitSyncpt:
+ Execute(arguments[0]);
+ break;
+ case Method::LoadSyncptPayload32:
+ syncpoint_value = arguments[0];
+ break;
+ case Method::WaitSyncpt32:
+ Execute(arguments[0]);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method));
+ break;
+ }
+}
+
+void Tegra::Host1x::Execute(u32 data) {
+ // This method waits on a valid syncpoint.
+ // TODO: Implement when proper Async is in place
+}
diff --git a/src/video_core/command_classes/host1x.h b/src/video_core/command_classes/host1x.h
new file mode 100644
index 000000000..013eaa0c1
--- /dev/null
+++ b/src/video_core/command_classes/host1x.h
@@ -0,0 +1,78 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+class Nvdec;
+
+class Host1x {
+public:
+ struct Host1xClassRegisters {
+ u32 incr_syncpt{};
+ u32 incr_syncpt_ctrl{};
+ u32 incr_syncpt_error{};
+ INSERT_PADDING_WORDS(5);
+ u32 wait_syncpt{};
+ u32 wait_syncpt_base{};
+ u32 wait_syncpt_incr{};
+ u32 load_syncpt_base{};
+ u32 incr_syncpt_base{};
+ u32 clear{};
+ u32 wait{};
+ u32 wait_with_interrupt{};
+ u32 delay_use{};
+ u32 tick_count_high{};
+ u32 tick_count_low{};
+ u32 tick_ctrl{};
+ INSERT_PADDING_WORDS(23);
+ u32 ind_ctrl{};
+ u32 ind_off2{};
+ u32 ind_off{};
+ std::array<u32, 31> ind_data{};
+ INSERT_PADDING_WORDS(1);
+ u32 load_syncpoint_payload32{};
+ u32 stall_ctrl{};
+ u32 wait_syncpt32{};
+ u32 wait_syncpt_base32{};
+ u32 load_syncpt_base32{};
+ u32 incr_syncpt_base32{};
+ u32 stall_count_high{};
+ u32 stall_count_low{};
+ u32 xref_ctrl{};
+ u32 channel_xref_high{};
+ u32 channel_xref_low{};
+ };
+ static_assert(sizeof(Host1xClassRegisters) == 0x164, "Host1xClassRegisters is an invalid size");
+
+ enum class Method : u32 {
+ WaitSyncpt = offsetof(Host1xClassRegisters, wait_syncpt) / 4,
+ LoadSyncptPayload32 = offsetof(Host1xClassRegisters, load_syncpoint_payload32) / 4,
+ WaitSyncpt32 = offsetof(Host1xClassRegisters, wait_syncpt32) / 4,
+ };
+
+ explicit Host1x(GPU& gpu);
+ ~Host1x();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(Method method, const std::vector<u32>& arguments);
+
+private:
+ /// For Host1x, execute is waiting on a syncpoint previously written into the state
+ void Execute(u32 data);
+
+ /// Write argument into the provided offset
+ void StateWrite(u32 offset, u32 arguments);
+
+ u32 syncpoint_value{};
+ Host1xClassRegisters state{};
+ GPU& gpu;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.cpp b/src/video_core/command_classes/nvdec.cpp
new file mode 100644
index 000000000..8ca7a7b06
--- /dev/null
+++ b/src/video_core/command_classes/nvdec.cpp
@@ -0,0 +1,52 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/command_classes/nvdec.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+
+Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), codec(std::make_unique<Codec>(gpu)) {}
+
+Nvdec::~Nvdec() = default;
+
+void Nvdec::ProcessMethod(Method method, const std::vector<u32>& arguments) {
+ if (method == Method::SetVideoCodec) {
+ codec->StateWrite(static_cast<u32>(method), arguments[0]);
+ } else {
+ codec->StateWrite(static_cast<u32>(method), static_cast<u64>(arguments[0]) << 8);
+ }
+
+ switch (method) {
+ case Method::SetVideoCodec:
+ codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(arguments[0]));
+ break;
+ case Method::Execute:
+ Execute();
+ break;
+ }
+}
+
+AVFrame* Nvdec::GetFrame() {
+ return codec->GetCurrentFrame();
+}
+
+const AVFrame* Nvdec::GetFrame() const {
+ return codec->GetCurrentFrame();
+}
+
+void Nvdec::Execute() {
+ switch (codec->GetCurrentCodec()) {
+ case NvdecCommon::VideoCodec::H264:
+ case NvdecCommon::VideoCodec::Vp9:
+ codec->Decode();
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec()));
+ break;
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.h b/src/video_core/command_classes/nvdec.h
new file mode 100644
index 000000000..eec4443f9
--- /dev/null
+++ b/src/video_core/command_classes/nvdec.h
@@ -0,0 +1,39 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/command_classes/codecs/codec.h"
+
+namespace Tegra {
+class GPU;
+
+class Nvdec {
+public:
+ enum class Method : u32 {
+ SetVideoCodec = 0x80,
+ Execute = 0xc0,
+ };
+
+ explicit Nvdec(GPU& gpu);
+ ~Nvdec();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(Method method, const std::vector<u32>& arguments);
+
+ /// Return most recently decoded frame
+ [[nodiscard]] AVFrame* GetFrame();
+ [[nodiscard]] const AVFrame* GetFrame() const;
+
+private:
+ /// Invoke codec to decode a frame
+ void Execute();
+
+ GPU& gpu;
+ std::unique_ptr<Codec> codec;
+};
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec_common.h b/src/video_core/command_classes/nvdec_common.h
new file mode 100644
index 000000000..01b5e086d
--- /dev/null
+++ b/src/video_core/command_classes/nvdec_common.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra::NvdecCommon {
+
+struct NvdecRegisters {
+ INSERT_PADDING_WORDS(256);
+ u64 set_codec_id{};
+ INSERT_PADDING_WORDS(254);
+ u64 set_platform_id{};
+ u64 picture_info_offset{};
+ u64 frame_bitstream_offset{};
+ u64 frame_number{};
+ u64 h264_slice_data_offsets{};
+ u64 h264_mv_dump_offset{};
+ INSERT_PADDING_WORDS(6);
+ u64 frame_stats_offset{};
+ u64 h264_last_surface_luma_offset{};
+ u64 h264_last_surface_chroma_offset{};
+ std::array<u64, 17> surface_luma_offset{};
+ std::array<u64, 17> surface_chroma_offset{};
+ INSERT_PADDING_WORDS(132);
+ u64 vp9_entropy_probs_offset{};
+ u64 vp9_backward_updates_offset{};
+ u64 vp9_last_frame_segmap_offset{};
+ u64 vp9_curr_frame_segmap_offset{};
+ INSERT_PADDING_WORDS(2);
+ u64 vp9_last_frame_mvs_offset{};
+ u64 vp9_curr_frame_mvs_offset{};
+ INSERT_PADDING_WORDS(2);
+};
+static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
+
+enum class VideoCodec : u32 {
+ None = 0x0,
+ H264 = 0x3,
+ Vp8 = 0x5,
+ H265 = 0x7,
+ Vp9 = 0x9,
+};
+
+} // namespace Tegra::NvdecCommon
diff --git a/src/video_core/command_classes/sync_manager.cpp b/src/video_core/command_classes/sync_manager.cpp
new file mode 100644
index 000000000..19dc9e0ab
--- /dev/null
+++ b/src/video_core/command_classes/sync_manager.cpp
@@ -0,0 +1,60 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <algorithm>
+#include "sync_manager.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+SyncptIncrManager::SyncptIncrManager(GPU& gpu_) : gpu(gpu_) {}
+SyncptIncrManager::~SyncptIncrManager() = default;
+
+void SyncptIncrManager::Increment(u32 id) {
+ increments.emplace_back(0, 0, id, true);
+ IncrementAllDone();
+}
+
+u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) {
+ const u32 handle = current_id++;
+ increments.emplace_back(handle, class_id, id);
+ return handle;
+}
+
+void SyncptIncrManager::SignalDone(u32 handle) {
+ const auto done_incr =
+ std::find_if(increments.begin(), increments.end(),
+ [handle](const SyncptIncr& incr) { return incr.id == handle; });
+ if (done_incr != increments.cend()) {
+ done_incr->complete = true;
+ }
+ IncrementAllDone();
+}
+
+void SyncptIncrManager::IncrementAllDone() {
+ std::size_t done_count = 0;
+ for (; done_count < increments.size(); ++done_count) {
+ if (!increments[done_count].complete) {
+ break;
+ }
+ gpu.IncrementSyncPoint(increments[done_count].syncpt_id);
+ }
+ increments.erase(increments.begin(), increments.begin() + done_count);
+}
+} // namespace Tegra
diff --git a/src/video_core/command_classes/sync_manager.h b/src/video_core/command_classes/sync_manager.h
new file mode 100644
index 000000000..2c321ec58
--- /dev/null
+++ b/src/video_core/command_classes/sync_manager.h
@@ -0,0 +1,64 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+struct SyncptIncr {
+ u32 id;
+ u32 class_id;
+ u32 syncpt_id;
+ bool complete;
+
+ SyncptIncr(u32 id_, u32 class_id_, u32 syncpt_id_, bool done = false)
+ : id(id_), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {}
+};
+
+class SyncptIncrManager {
+public:
+ explicit SyncptIncrManager(GPU& gpu);
+ ~SyncptIncrManager();
+
+ /// Add syncpoint id and increment all
+ void Increment(u32 id);
+
+ /// Returns a handle to increment later
+ u32 IncrementWhenDone(u32 class_id, u32 id);
+
+ /// IncrememntAllDone, including handle
+ void SignalDone(u32 handle);
+
+ /// Increment all sequential pending increments that are already done.
+ void IncrementAllDone();
+
+private:
+ std::vector<SyncptIncr> increments;
+ std::mutex increment_lock;
+ u32 current_id{};
+
+ GPU& gpu;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp
new file mode 100644
index 000000000..5b52da277
--- /dev/null
+++ b/src/video_core/command_classes/vic.cpp
@@ -0,0 +1,180 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include "common/assert.h"
+#include "video_core/command_classes/nvdec.h"
+#include "video_core/command_classes/vic.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/texture_cache/surface_params.h"
+
+extern "C" {
+#include <libswscale/swscale.h>
+}
+
+namespace Tegra {
+
+Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_)
+ : gpu(gpu_), nvdec_processor(std::move(nvdec_processor_)) {}
+Vic::~Vic() = default;
+
+void Vic::VicStateWrite(u32 offset, u32 arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&vic_state) + offset * sizeof(u32);
+ std::memcpy(state_offset, &arguments, sizeof(u32));
+}
+
+void Vic::ProcessMethod(Method method, const std::vector<u32>& arguments) {
+ LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", static_cast<u32>(method));
+ VicStateWrite(static_cast<u32>(method), arguments[0]);
+ const u64 arg = static_cast<u64>(arguments[0]) << 8;
+ switch (method) {
+ case Method::Execute:
+ Execute();
+ break;
+ case Method::SetConfigStructOffset:
+ config_struct_address = arg;
+ break;
+ case Method::SetOutputSurfaceLumaOffset:
+ output_surface_luma_address = arg;
+ break;
+ case Method::SetOutputSurfaceChromaUOffset:
+ output_surface_chroma_u_address = arg;
+ break;
+ case Method::SetOutputSurfaceChromaVOffset:
+ output_surface_chroma_v_address = arg;
+ break;
+ default:
+ break;
+ }
+}
+
+void Vic::Execute() {
+ if (output_surface_luma_address == 0) {
+ LOG_ERROR(Service_NVDRV, "VIC Luma address not set. Recieved 0x{:X}",
+ vic_state.output_surface.luma_offset);
+ return;
+ }
+ const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)};
+ const VideoPixelFormat pixel_format =
+ static_cast<VideoPixelFormat>(config.pixel_format.Value());
+ switch (pixel_format) {
+ case VideoPixelFormat::BGRA8:
+ case VideoPixelFormat::RGBA8: {
+ LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
+ const auto* frame = nvdec_processor->GetFrame();
+
+ if (!frame || frame->width == 0 || frame->height == 0) {
+ return;
+ }
+ if (scaler_ctx == nullptr || frame->width != scaler_width ||
+ frame->height != scaler_height) {
+ const AVPixelFormat target_format =
+ (pixel_format == VideoPixelFormat::RGBA8) ? AV_PIX_FMT_RGBA : AV_PIX_FMT_BGRA;
+
+ sws_freeContext(scaler_ctx);
+ scaler_ctx = nullptr;
+
+ // FFmpeg returns all frames in YUV420, convert it into expected format
+ scaler_ctx =
+ sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width,
+ frame->height, target_format, 0, nullptr, nullptr, nullptr);
+
+ scaler_width = frame->width;
+ scaler_height = frame->height;
+ }
+ // Get Converted frame
+ const std::size_t linear_size = frame->width * frame->height * 4;
+
+ using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
+ AVMallocPtr converted_frame_buffer{static_cast<u8*>(av_malloc(linear_size)), av_free};
+
+ const int converted_stride{frame->width * 4};
+ u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
+
+ sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height,
+ &converted_frame_buf_addr, &converted_stride);
+
+ const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
+ if (blk_kind != 0) {
+ // swizzle pitch linear to block linear
+ const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
+ const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1,
+ block_height, 0);
+ std::vector<u8> swizzled_data(size);
+ Tegra::Texture::CopySwizzledData(frame->width, frame->height, 1, 4, 4,
+ swizzled_data.data(), converted_frame_buffer.get(),
+ false, block_height, 0, 1);
+
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, swizzled_data.data(), size);
+ gpu.Maxwell3D().OnMemoryWrite();
+ } else {
+ // send pitch linear frame
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr,
+ linear_size);
+ gpu.Maxwell3D().OnMemoryWrite();
+ }
+ break;
+ }
+ case VideoPixelFormat::Yuv420: {
+ LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
+
+ const auto* frame = nvdec_processor->GetFrame();
+
+ if (!frame || frame->width == 0 || frame->height == 0) {
+ return;
+ }
+
+ const std::size_t surface_width = config.surface_width_minus1 + 1;
+ const std::size_t surface_height = config.surface_height_minus1 + 1;
+ const std::size_t half_width = surface_width / 2;
+ const std::size_t half_height = config.surface_height_minus1 / 2;
+ const std::size_t aligned_width = (surface_width + 0xff) & ~0xff;
+
+ const auto* luma_ptr = frame->data[0];
+ const auto* chroma_b_ptr = frame->data[1];
+ const auto* chroma_r_ptr = frame->data[2];
+ const auto stride = frame->linesize[0];
+ const auto half_stride = frame->linesize[1];
+
+ std::vector<u8> luma_buffer(aligned_width * surface_height);
+ std::vector<u8> chroma_buffer(aligned_width * half_height);
+
+ // Populate luma buffer
+ for (std::size_t y = 0; y < surface_height - 1; ++y) {
+ std::size_t src = y * stride;
+ std::size_t dst = y * aligned_width;
+
+ std::size_t size = surface_width;
+
+ for (std::size_t offset = 0; offset < size; ++offset) {
+ luma_buffer[dst + offset] = luma_ptr[src + offset];
+ }
+ }
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
+ luma_buffer.size());
+
+ // Populate chroma buffer from both channels with interleaving.
+ for (std::size_t y = 0; y < half_height; ++y) {
+ std::size_t src = y * half_stride;
+ std::size_t dst = y * aligned_width;
+
+ for (std::size_t x = 0; x < half_width; ++x) {
+ chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x];
+ chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x];
+ }
+ }
+ gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(),
+ chroma_buffer.size());
+ gpu.Maxwell3D().OnMemoryWrite();
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unknown video pixel format {}", config.pixel_format.Value());
+ break;
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h
new file mode 100644
index 000000000..8c4e284a1
--- /dev/null
+++ b/src/video_core/command_classes/vic.h
@@ -0,0 +1,110 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+struct SwsContext;
+
+namespace Tegra {
+class GPU;
+class Nvdec;
+
+struct PlaneOffsets {
+ u32 luma_offset{};
+ u32 chroma_u_offset{};
+ u32 chroma_v_offset{};
+};
+
+struct VicRegisters {
+ INSERT_PADDING_WORDS(64);
+ u32 nop{};
+ INSERT_PADDING_WORDS(15);
+ u32 pm_trigger{};
+ INSERT_PADDING_WORDS(47);
+ u32 set_application_id{};
+ u32 set_watchdog_timer{};
+ INSERT_PADDING_WORDS(17);
+ u32 context_save_area{};
+ u32 context_switch{};
+ INSERT_PADDING_WORDS(43);
+ u32 execute{};
+ INSERT_PADDING_WORDS(63);
+ std::array<std::array<PlaneOffsets, 8>, 8> surfacex_slots{};
+ u32 picture_index{};
+ u32 control_params{};
+ u32 config_struct_offset{};
+ u32 filter_struct_offset{};
+ u32 palette_offset{};
+ u32 hist_offset{};
+ u32 context_id{};
+ u32 fce_ucode_size{};
+ PlaneOffsets output_surface{};
+ u32 fce_ucode_offset{};
+ INSERT_PADDING_WORDS(4);
+ std::array<u32, 8> slot_context_id{};
+ INSERT_PADDING_WORDS(16);
+};
+static_assert(sizeof(VicRegisters) == 0x7A0, "VicRegisters is an invalid size");
+
+class Vic {
+public:
+ enum class Method : u32 {
+ Execute = 0xc0,
+ SetControlParams = 0x1c1,
+ SetConfigStructOffset = 0x1c2,
+ SetOutputSurfaceLumaOffset = 0x1c8,
+ SetOutputSurfaceChromaUOffset = 0x1c9,
+ SetOutputSurfaceChromaVOffset = 0x1ca
+ };
+
+ explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor);
+ ~Vic();
+
+ /// Write to the device state.
+ void ProcessMethod(Method method, const std::vector<u32>& arguments);
+
+private:
+ void Execute();
+
+ void VicStateWrite(u32 offset, u32 arguments);
+ VicRegisters vic_state{};
+
+ enum class VideoPixelFormat : u64_le {
+ RGBA8 = 0x1f,
+ BGRA8 = 0x20,
+ Yuv420 = 0x44,
+ };
+
+ union VicConfig {
+ u64_le raw{};
+ BitField<0, 7, u64_le> pixel_format;
+ BitField<7, 2, u64_le> chroma_loc_horiz;
+ BitField<9, 2, u64_le> chroma_loc_vert;
+ BitField<11, 4, u64_le> block_linear_kind;
+ BitField<15, 4, u64_le> block_linear_height_log2;
+ BitField<19, 3, u64_le> reserved0;
+ BitField<22, 10, u64_le> reserved1;
+ BitField<32, 14, u64_le> surface_width_minus1;
+ BitField<46, 14, u64_le> surface_height_minus1;
+ };
+
+ GPU& gpu;
+ std::shared_ptr<Tegra::Nvdec> nvdec_processor;
+
+ GPUVAddr config_struct_address{};
+ GPUVAddr output_surface_luma_address{};
+ GPUVAddr output_surface_chroma_u_address{};
+ GPUVAddr output_surface_chroma_v_address{};
+
+ SwsContext* scaler_ctx{};
+ s32 scaler_width{};
+ s32 scaler_height{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
new file mode 100644
index 000000000..b06c32c84
--- /dev/null
+++ b/src/video_core/compatible_formats.cpp
@@ -0,0 +1,155 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <bitset>
+#include <cstddef>
+
+#include "video_core/compatible_formats.h"
+#include "video_core/surface.h"
+
+namespace VideoCore::Surface {
+
+namespace {
+
+// Compatibility table taken from Table 3.X.2 in:
+// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt
+
+constexpr std::array VIEW_CLASS_128_BITS = {
+ PixelFormat::R32G32B32A32_FLOAT,
+ PixelFormat::R32G32B32A32_UINT,
+ PixelFormat::R32G32B32A32_SINT,
+};
+
+constexpr std::array VIEW_CLASS_96_BITS = {
+ PixelFormat::R32G32B32_FLOAT,
+};
+// Missing formats:
+// PixelFormat::RGB32UI,
+// PixelFormat::RGB32I,
+
+constexpr std::array VIEW_CLASS_64_BITS = {
+ PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_UINT,
+ PixelFormat::R32G32_SINT, PixelFormat::R16G16B16A16_FLOAT,
+ PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
+ PixelFormat::R16G16B16A16_UINT, PixelFormat::R16G16B16A16_SINT,
+};
+
+// TODO: How should we handle 48 bits?
+
+constexpr std::array VIEW_CLASS_32_BITS = {
+ PixelFormat::R16G16_FLOAT, PixelFormat::B10G11R11_FLOAT, PixelFormat::R32_FLOAT,
+ PixelFormat::A2B10G10R10_UNORM, PixelFormat::R16G16_UINT, PixelFormat::R32_UINT,
+ PixelFormat::R16G16_SINT, PixelFormat::R32_SINT, PixelFormat::A8B8G8R8_UNORM,
+ PixelFormat::R16G16_UNORM, PixelFormat::A8B8G8R8_SNORM, PixelFormat::R16G16_SNORM,
+ PixelFormat::A8B8G8R8_SRGB, PixelFormat::E5B9G9R9_FLOAT, PixelFormat::B8G8R8A8_UNORM,
+ PixelFormat::B8G8R8A8_SRGB, PixelFormat::A8B8G8R8_UINT, PixelFormat::A8B8G8R8_SINT,
+ PixelFormat::A2B10G10R10_UINT,
+};
+
+// TODO: How should we handle 24 bits?
+
+constexpr std::array VIEW_CLASS_16_BITS = {
+ PixelFormat::R16_FLOAT, PixelFormat::R8G8_UINT, PixelFormat::R16_UINT,
+ PixelFormat::R16_SINT, PixelFormat::R8G8_UNORM, PixelFormat::R16_UNORM,
+ PixelFormat::R8G8_SNORM, PixelFormat::R16_SNORM, PixelFormat::R8G8_SINT,
+};
+
+constexpr std::array VIEW_CLASS_8_BITS = {
+ PixelFormat::R8_UINT,
+ PixelFormat::R8_UNORM,
+ PixelFormat::R8_SINT,
+ PixelFormat::R8_SNORM,
+};
+
+constexpr std::array VIEW_CLASS_RGTC1_RED = {
+ PixelFormat::BC4_UNORM,
+ PixelFormat::BC4_SNORM,
+};
+
+constexpr std::array VIEW_CLASS_RGTC2_RG = {
+ PixelFormat::BC5_UNORM,
+ PixelFormat::BC5_SNORM,
+};
+
+constexpr std::array VIEW_CLASS_BPTC_UNORM = {
+ PixelFormat::BC7_UNORM,
+ PixelFormat::BC7_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_BPTC_FLOAT = {
+ PixelFormat::BC6H_SFLOAT,
+ PixelFormat::BC6H_UFLOAT,
+};
+
+// Compatibility table taken from Table 4.X.1 in:
+// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_copy_image.txt
+
+constexpr std::array COPY_CLASS_128_BITS = {
+ PixelFormat::R32G32B32A32_UINT, PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_SINT,
+ PixelFormat::BC2_UNORM, PixelFormat::BC2_SRGB, PixelFormat::BC3_UNORM,
+ PixelFormat::BC3_SRGB, PixelFormat::BC5_UNORM, PixelFormat::BC5_SNORM,
+ PixelFormat::BC7_UNORM, PixelFormat::BC7_SRGB, PixelFormat::BC6H_SFLOAT,
+ PixelFormat::BC6H_UFLOAT,
+};
+// Missing formats:
+// PixelFormat::RGBA32I
+// COMPRESSED_RG_RGTC2
+
+constexpr std::array COPY_CLASS_64_BITS = {
+ PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT,
+ PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
+ PixelFormat::R16G16B16A16_SINT, PixelFormat::R32G32_UINT,
+ PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_SINT,
+ PixelFormat::BC1_RGBA_UNORM, PixelFormat::BC1_RGBA_SRGB,
+};
+// Missing formats:
+// COMPRESSED_RGB_S3TC_DXT1_EXT
+// COMPRESSED_SRGB_S3TC_DXT1_EXT
+// COMPRESSED_RGBA_S3TC_DXT1_EXT
+// COMPRESSED_SIGNED_RED_RGTC1
+
+void Enable(FormatCompatibility::Table& compatiblity, size_t format_a, size_t format_b) {
+ compatiblity[format_a][format_b] = true;
+ compatiblity[format_b][format_a] = true;
+}
+
+void Enable(FormatCompatibility::Table& compatibility, PixelFormat format_a, PixelFormat format_b) {
+ Enable(compatibility, static_cast<size_t>(format_a), static_cast<size_t>(format_b));
+}
+
+template <typename Range>
+void EnableRange(FormatCompatibility::Table& compatibility, const Range& range) {
+ for (auto it_a = range.begin(); it_a != range.end(); ++it_a) {
+ for (auto it_b = it_a; it_b != range.end(); ++it_b) {
+ Enable(compatibility, *it_a, *it_b);
+ }
+ }
+}
+
+} // Anonymous namespace
+
+FormatCompatibility::FormatCompatibility() {
+ for (size_t i = 0; i < MaxPixelFormat; ++i) {
+ // Identity is allowed
+ Enable(view, i, i);
+ }
+
+ EnableRange(view, VIEW_CLASS_128_BITS);
+ EnableRange(view, VIEW_CLASS_96_BITS);
+ EnableRange(view, VIEW_CLASS_64_BITS);
+ EnableRange(view, VIEW_CLASS_32_BITS);
+ EnableRange(view, VIEW_CLASS_16_BITS);
+ EnableRange(view, VIEW_CLASS_8_BITS);
+ EnableRange(view, VIEW_CLASS_RGTC1_RED);
+ EnableRange(view, VIEW_CLASS_RGTC2_RG);
+ EnableRange(view, VIEW_CLASS_BPTC_UNORM);
+ EnableRange(view, VIEW_CLASS_BPTC_FLOAT);
+
+ copy = view;
+ EnableRange(copy, COPY_CLASS_128_BITS);
+ EnableRange(copy, COPY_CLASS_64_BITS);
+}
+
+} // namespace VideoCore::Surface
diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h
new file mode 100644
index 000000000..51766349b
--- /dev/null
+++ b/src/video_core/compatible_formats.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <bitset>
+#include <cstddef>
+
+#include "video_core/surface.h"
+
+namespace VideoCore::Surface {
+
+class FormatCompatibility {
+public:
+ using Table = std::array<std::bitset<MaxPixelFormat>, MaxPixelFormat>;
+
+ explicit FormatCompatibility();
+
+ bool TestView(PixelFormat format_a, PixelFormat format_b) const noexcept {
+ return view[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
+ }
+
+ bool TestCopy(PixelFormat format_a, PixelFormat format_b) const noexcept {
+ return copy[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
+ }
+
+private:
+ Table view;
+ Table copy;
+};
+
+} // namespace VideoCore::Surface
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 713c14182..d8801b1f5 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/cityhash.h"
#include "common/microprofile.h"
#include "core/core.h"
#include "core/memory.h"
@@ -12,7 +13,7 @@
namespace Tegra {
-DmaPusher::DmaPusher(GPU& gpu) : gpu(gpu) {}
+DmaPusher::DmaPusher(Core::System& system, GPU& gpu) : gpu{gpu}, system{system} {}
DmaPusher::~DmaPusher() = default;
@@ -21,17 +22,22 @@ MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128,
void DmaPusher::DispatchCalls() {
MICROPROFILE_SCOPE(DispatchCalls);
+ gpu.SyncGuestHost();
// On entering GPU code, assume all memory may be touched by the ARM core.
gpu.Maxwell3D().OnMemoryWrite();
dma_pushbuffer_subindex = 0;
- while (Core::System::GetInstance().IsPoweredOn()) {
+ dma_state.is_last_call = true;
+
+ while (system.IsPoweredOn()) {
if (!Step()) {
break;
}
}
gpu.FlushCommands();
+ gpu.SyncGuestHost();
+ gpu.OnCommandListEnd();
}
bool DmaPusher::Step() {
@@ -40,44 +46,59 @@ bool DmaPusher::Step() {
return false;
}
- const CommandList& command_list{dma_pushbuffer.front()};
- ASSERT_OR_EXECUTE(!command_list.empty(), {
- // Somehow the command_list is empty, in order to avoid a crash
- // We ignore it and assume its size is 0.
- dma_pushbuffer.pop();
- dma_pushbuffer_subindex = 0;
- return true;
- });
- const CommandListHeader command_list_header{command_list[dma_pushbuffer_subindex++]};
- GPUVAddr dma_get = command_list_header.addr;
- GPUVAddr dma_put = dma_get + command_list_header.size * sizeof(u32);
- bool non_main = command_list_header.is_non_main;
-
- if (dma_pushbuffer_subindex >= command_list.size()) {
- // We've gone through the current list, remove it from the queue
- dma_pushbuffer.pop();
- dma_pushbuffer_subindex = 0;
- }
+ CommandList& command_list{dma_pushbuffer.front()};
- if (command_list_header.size == 0) {
- return true;
- }
+ ASSERT_OR_EXECUTE(
+ command_list.command_lists.size() || command_list.prefetch_command_list.size(), {
+ // Somehow the command_list is empty, in order to avoid a crash
+ // We ignore it and assume its size is 0.
+ dma_pushbuffer.pop();
+ dma_pushbuffer_subindex = 0;
+ return true;
+ });
- // Push buffer non-empty, read a word
- command_headers.resize(command_list_header.size);
- gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
- command_list_header.size * sizeof(u32));
+ if (command_list.prefetch_command_list.size()) {
+ // Prefetched command list from nvdrv, used for things like synchronization
+ command_headers = std::move(command_list.prefetch_command_list);
+ dma_pushbuffer.pop();
+ } else {
+ const CommandListHeader command_list_header{
+ command_list.command_lists[dma_pushbuffer_subindex++]};
+ const GPUVAddr dma_get = command_list_header.addr;
+
+ if (dma_pushbuffer_subindex >= command_list.command_lists.size()) {
+ // We've gone through the current list, remove it from the queue
+ dma_pushbuffer.pop();
+ dma_pushbuffer_subindex = 0;
+ }
- for (const CommandHeader& command_header : command_headers) {
+ if (command_list_header.size == 0) {
+ return true;
+ }
- // now, see if we're in the middle of a command
- if (dma_state.length_pending) {
- // Second word of long non-inc methods command - method count
- dma_state.length_pending = 0;
- dma_state.method_count = command_header.method_count_;
- } else if (dma_state.method_count) {
+ // Push buffer non-empty, read a word
+ command_headers.resize(command_list_header.size);
+ gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
+ }
+ for (std::size_t index = 0; index < command_headers.size();) {
+ const CommandHeader& command_header = command_headers[index];
+
+ if (dma_state.method_count) {
// Data word of methods command
- CallMethod(command_header.argument);
+ if (dma_state.non_incrementing) {
+ const u32 max_write = static_cast<u32>(
+ std::min<std::size_t>(index + dma_state.method_count, command_headers.size()) -
+ index);
+ CallMultiMethod(&command_header.argument, max_write);
+ dma_state.method_count -= max_write;
+ dma_state.is_last_call = true;
+ index += max_write;
+ continue;
+ } else {
+ dma_state.is_last_call = dma_state.method_count <= 1;
+ CallMethod(command_header.argument);
+ }
if (!dma_state.non_incrementing) {
dma_state.method++;
@@ -117,11 +138,7 @@ bool DmaPusher::Step() {
break;
}
}
- }
-
- if (!non_main) {
- // TODO (degasus): This is dead code, as dma_mget is never read.
- dma_mget = dma_put;
+ index++;
}
return true;
@@ -134,7 +151,22 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
}
void DmaPusher::CallMethod(u32 argument) const {
- gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count});
+ if (dma_state.method < non_puller_methods) {
+ gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count});
+ } else {
+ subchannels[dma_state.subchannel]->CallMethod(dma_state.method, argument,
+ dma_state.is_last_call);
+ }
+}
+
+void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
+ if (dma_state.method < non_puller_methods) {
+ gpu.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
+ dma_state.method_count);
+ } else {
+ subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
+ num_methods, dma_state.method_count);
+ }
}
} // namespace Tegra
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 6ab06518f..96ac267f7 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -4,14 +4,22 @@
#pragma once
+#include <array>
#include <vector>
#include <queue>
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "video_core/engines/engine_interface.h"
+
+namespace Core {
+class System;
+}
namespace Tegra {
+class GPU;
+
enum class SubmissionMode : u32 {
IncreasingOld = 0,
Increasing = 1,
@@ -21,6 +29,31 @@ enum class SubmissionMode : u32 {
IncreaseOnce = 5
};
+// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
+// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
+// So the values you see in docs might be multiplied by 4.
+enum class BufferMethods : u32 {
+ BindObject = 0x0,
+ Nop = 0x2,
+ SemaphoreAddressHigh = 0x4,
+ SemaphoreAddressLow = 0x5,
+ SemaphoreSequence = 0x6,
+ SemaphoreTrigger = 0x7,
+ NotifyIntr = 0x8,
+ WrcacheFlush = 0x9,
+ Unk28 = 0xA,
+ UnkCacheFlush = 0xB,
+ RefCnt = 0x14,
+ SemaphoreAcquire = 0x1A,
+ SemaphoreRelease = 0x1B,
+ FenceValue = 0x1C,
+ FenceAction = 0x1D,
+ WaitForInterrupt = 0x1E,
+ Unk7c = 0x1F,
+ Yield = 0x20,
+ NonPullerMethods = 0x40,
+};
+
struct CommandListHeader {
union {
u64 raw;
@@ -43,9 +76,23 @@ union CommandHeader {
static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout");
static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");
-class GPU;
-
-using CommandList = std::vector<Tegra::CommandListHeader>;
+inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, SubmissionMode mode) {
+ CommandHeader result{};
+ result.method.Assign(static_cast<u32>(method));
+ result.arg_count.Assign(arg_count);
+ result.mode.Assign(mode);
+ return result;
+}
+
+struct CommandList final {
+ CommandList() = default;
+ explicit CommandList(std::size_t size) : command_lists(size) {}
+ explicit CommandList(std::vector<Tegra::CommandHeader>&& prefetch_command_list)
+ : prefetch_command_list{std::move(prefetch_command_list)} {}
+
+ std::vector<Tegra::CommandListHeader> command_lists;
+ std::vector<Tegra::CommandHeader> prefetch_command_list;
+};
/**
* The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the
@@ -54,9 +101,9 @@ using CommandList = std::vector<Tegra::CommandListHeader>;
* See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for
* details on this implementation.
*/
-class DmaPusher {
+class DmaPusher final {
public:
- explicit DmaPusher(GPU& gpu);
+ explicit DmaPusher(Core::System& system, GPU& gpu);
~DmaPusher();
void Push(CommandList&& entries) {
@@ -65,14 +112,19 @@ public:
void DispatchCalls();
+ void BindSubchannel(Tegra::Engines::EngineInterface* engine, u32 subchannel_id) {
+ subchannels[subchannel_id] = engine;
+ }
+
private:
+ static constexpr u32 non_puller_methods = 0x40;
+ static constexpr u32 max_subchannels = 8;
bool Step();
void SetState(const CommandHeader& command_header);
void CallMethod(u32 argument) const;
-
- GPU& gpu;
+ void CallMultiMethod(const u32* base_start, u32 num_methods) const;
std::vector<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once
@@ -85,13 +137,18 @@ private:
u32 method_count; ///< Current method count
u32 length_pending; ///< Large NI command length pending
bool non_incrementing; ///< Current command's NI flag
+ bool is_last_call;
};
DmaState dma_state{};
bool dma_increment_once{};
- GPUVAddr dma_mget{}; ///< main pushbuffer last read address
bool ib_enable{true}; ///< IB mode enabled
+
+ std::array<Tegra::Engines::EngineInterface*, max_subchannels> subchannels{};
+
+ GPU& gpu;
+ Core::System& system;
};
} // namespace Tegra
diff --git a/src/video_core/engines/const_buffer_engine_interface.h b/src/video_core/engines/const_buffer_engine_interface.h
index ebe139504..f46e81bb7 100644
--- a/src/video_core/engines/const_buffer_engine_interface.h
+++ b/src/video_core/engines/const_buffer_engine_interface.h
@@ -93,6 +93,7 @@ public:
virtual SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const = 0;
virtual SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
u64 offset) const = 0;
+ virtual SamplerDescriptor AccessSampler(u32 handle) const = 0;
virtual u32 GetBoundBuffer() const = 0;
virtual VideoCore::GuestDriverProfile& AccessGuestDriverProfile() = 0;
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h
new file mode 100644
index 000000000..18a9db7e6
--- /dev/null
+++ b/src/video_core/engines/engine_interface.h
@@ -0,0 +1,22 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+#include "common/common_types.h"
+
+namespace Tegra::Engines {
+
+class EngineInterface {
+public:
+ /// Write the value to the register identified by method.
+ virtual void CallMethod(u32 method, u32 method_argument, bool is_last_call) = 0;
+
+ /// Write multiple values to the register identified by method.
+ virtual void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) = 0;
+};
+
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 85d308e26..9409c4075 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -10,15 +10,21 @@
namespace Tegra::Engines {
-Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
+Fermi2D::Fermi2D() = default;
-void Fermi2D::CallMethod(const GPU::MethodCall& method_call) {
- ASSERT_MSG(method_call.method < Regs::NUM_REGS,
+Fermi2D::~Fermi2D() = default;
+
+void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+
+void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Fermi2D register, increase the size of the Regs structure");
- regs.reg_array[method_call.method] = method_call.argument;
+ regs.reg_array[method] = method_argument;
- switch (method_call.method) {
+ switch (method) {
// Trigger the surface copy on the last register write. This is blit_src_y, but this is 64-bit,
// so trigger on the second 32-bit write.
case FERMI2D_REG_INDEX(blit_src_y) + 1: {
@@ -28,7 +34,13 @@ void Fermi2D::CallMethod(const GPU::MethodCall& method_call) {
}
}
-std::pair<u32, u32> DelimitLine(u32 src_1, u32 src_2, u32 dst_1, u32 dst_2, u32 src_line) {
+void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+}
+
+static std::pair<u32, u32> DelimitLine(u32 src_1, u32 src_2, u32 dst_1, u32 dst_2, u32 src_line) {
const u32 line_a = src_2 - src_1;
const u32 line_b = dst_2 - dst_1;
const u32 excess = std::max<s32>(0, line_a - src_line + src_1);
@@ -75,13 +87,13 @@ void Fermi2D::HandleSurfaceCopy() {
const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2,
dst_blit_y2};
- Config copy_config;
- copy_config.operation = regs.operation;
- copy_config.filter = regs.blit_control.filter;
- copy_config.src_rect = src_rect;
- copy_config.dst_rect = dst_rect;
-
- if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
+ const Config copy_config{
+ .operation = regs.operation,
+ .filter = regs.blit_control.filter,
+ .src_rect = src_rect,
+ .dst_rect = dst_rect,
+ };
+ if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
UNIMPLEMENTED();
}
}
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index dba342c70..0909709ec 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -10,6 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/math_util.h"
+#include "video_core/engines/engine_interface.h"
#include "video_core/gpu.h"
namespace Tegra {
@@ -31,13 +32,20 @@ namespace Tegra::Engines {
#define FERMI2D_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32))
-class Fermi2D final {
+class Fermi2D final : public EngineInterface {
public:
- explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer);
- ~Fermi2D() = default;
+ explicit Fermi2D();
+ ~Fermi2D();
+
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
/// Write the value to the register identified by method.
- void CallMethod(const GPU::MethodCall& method_call);
+ void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
+
+ /// Write multiple values to the register identified by method.
+ void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) override;
enum class Origin : u32 {
Center = 0,
@@ -137,14 +145,14 @@ public:
} regs{};
struct Config {
- Operation operation;
- Filter filter;
+ Operation operation{};
+ Filter filter{};
Common::Rectangle<u32> src_rect;
Common::Rectangle<u32> dst_rect;
};
private:
- VideoCore::RasterizerInterface& rasterizer;
+ VideoCore::RasterizerInterface* rasterizer;
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 368c75a66..898370739 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -16,28 +16,28 @@
namespace Tegra::Engines {
-KeplerCompute::KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager)
- : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, upload_state{
- memory_manager,
- regs.upload} {}
+KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_)
+ : system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} {}
KeplerCompute::~KeplerCompute() = default;
-void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
- ASSERT_MSG(method_call.method < Regs::NUM_REGS,
+void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+
+void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid KeplerCompute register, increase the size of the Regs structure");
- regs.reg_array[method_call.method] = method_call.argument;
+ regs.reg_array[method] = method_argument;
- switch (method_call.method) {
+ switch (method) {
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
upload_state.ProcessExec(regs.exec_upload.linear != 0);
break;
}
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
- const bool is_last_call = method_call.IsLastCall();
- upload_state.ProcessData(method_call.argument, is_last_call);
+ upload_state.ProcessData(method_argument, is_last_call);
if (is_last_call) {
system.GPU().Maxwell3D().OnMemoryWrite();
}
@@ -51,6 +51,13 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
}
}
+void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+}
+
Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const {
const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value();
ASSERT(cbuf_mask[regs.tex_cb_index]);
@@ -86,8 +93,11 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con
ASSERT(stage == ShaderType::Compute);
const auto& tex_info_buffer = launch_description.const_buffer_config[const_buffer];
const GPUVAddr tex_info_address = tex_info_buffer.Address() + offset;
+ return AccessSampler(memory_manager.Read<u32>(tex_info_address));
+}
- const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
+SamplerDescriptor KeplerCompute::AccessSampler(u32 handle) const {
+ const Texture::TextureHandle tex_handle{handle};
const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle);
SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic);
result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
@@ -95,11 +105,11 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con
}
VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
const VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() const {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
void KeplerCompute::ProcessLaunch() {
@@ -110,7 +120,7 @@ void KeplerCompute::ProcessLaunch() {
const GPUVAddr code_addr = regs.code_loc.Address() + launch_description.program_start;
LOG_TRACE(HW_GPU, "Compute invocation launched at address 0x{:016x}", code_addr);
- rasterizer.DispatchCompute(code_addr);
+ rasterizer->DispatchCompute(code_addr);
}
Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const {
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index eeb79c56f..7f2500aab 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -11,6 +11,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/engines/const_buffer_engine_interface.h"
+#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
#include "video_core/engines/shader_type.h"
#include "video_core/gpu.h"
@@ -39,12 +40,14 @@ namespace Tegra::Engines {
#define KEPLER_COMPUTE_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
-class KeplerCompute final : public ConstBufferEngineInterface {
+class KeplerCompute final : public ConstBufferEngineInterface, public EngineInterface {
public:
- explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager);
+ explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
~KeplerCompute();
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+
static constexpr std::size_t NumConstBuffers = 8;
struct Regs {
@@ -200,7 +203,11 @@ public:
"KeplerCompute LaunchParams has wrong size");
/// Write the value to the register identified by method.
- void CallMethod(const GPU::MethodCall& method_call);
+ void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
+
+ /// Write multiple values to the register identified by method.
+ void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) override;
Texture::FullTextureInfo GetTexture(std::size_t offset) const;
@@ -214,6 +221,8 @@ public:
SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
u64 offset) const override;
+ SamplerDescriptor AccessSampler(u32 handle) const override;
+
u32 GetBoundBuffer() const override {
return regs.tex_cb_index;
}
@@ -223,11 +232,6 @@ public:
const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override;
private:
- Core::System& system;
- VideoCore::RasterizerInterface& rasterizer;
- MemoryManager& memory_manager;
- Upload::State upload_state;
-
void ProcessLaunch();
/// Retrieves information about a specific TIC entry from the TIC buffer.
@@ -235,6 +239,11 @@ private:
/// Retrieves information about a specific TSC entry from the TSC buffer.
Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
+
+ Core::System& system;
+ MemoryManager& memory_manager;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+ Upload::State upload_state;
};
#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index 597872e43..dc71b2eec 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -19,20 +19,19 @@ KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager)
KeplerMemory::~KeplerMemory() = default;
-void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) {
- ASSERT_MSG(method_call.method < Regs::NUM_REGS,
+void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid KeplerMemory register, increase the size of the Regs structure");
- regs.reg_array[method_call.method] = method_call.argument;
+ regs.reg_array[method] = method_argument;
- switch (method_call.method) {
+ switch (method) {
case KEPLERMEMORY_REG_INDEX(exec): {
upload_state.ProcessExec(regs.exec.linear != 0);
break;
}
case KEPLERMEMORY_REG_INDEX(data): {
- const bool is_last_call = method_call.IsLastCall();
- upload_state.ProcessData(method_call.argument, is_last_call);
+ upload_state.ProcessData(method_argument, is_last_call);
if (is_last_call) {
system.GPU().Maxwell3D().OnMemoryWrite();
}
@@ -41,4 +40,11 @@ void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) {
}
}
+void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 396fb6e86..5b7f71a00 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -10,6 +10,7 @@
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
#include "video_core/gpu.h"
@@ -32,13 +33,17 @@ namespace Tegra::Engines {
#define KEPLERMEMORY_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::KeplerMemory::Regs, field_name) / sizeof(u32))
-class KeplerMemory final {
+class KeplerMemory final : public EngineInterface {
public:
KeplerMemory(Core::System& system, MemoryManager& memory_manager);
~KeplerMemory();
/// Write the value to the register identified by method.
- void CallMethod(const GPU::MethodCall& method_call);
+ void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
+
+ /// Write multiple values to the register identified by method.
+ void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) override;
struct Regs {
static constexpr size_t NUM_REGS = 0x7F;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index ba63b44b4..6287df633 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -22,15 +22,19 @@ using VideoCore::QueryType;
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
-Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager)
- : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager},
- macro_interpreter{*this}, upload_state{memory_manager, regs.upload} {
+Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
+ : system{system_}, memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)},
+ upload_state{memory_manager, regs.upload} {
dirty.flags.flip();
-
InitializeRegisterDefaults();
}
+Maxwell3D::~Maxwell3D() = default;
+
+void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+
void Maxwell3D::InitializeRegisterDefaults() {
// Initializes registers to their default values - what games expect them to be at boot. This is
// for certain registers that may not be explicitly set by games.
@@ -44,6 +48,12 @@ void Maxwell3D::InitializeRegisterDefaults() {
viewport.depth_range_near = 0.0f;
viewport.depth_range_far = 1.0f;
}
+ for (auto& viewport : regs.viewport_transform) {
+ viewport.swizzle.x.Assign(Regs::ViewportSwizzle::PositiveX);
+ viewport.swizzle.y.Assign(Regs::ViewportSwizzle::PositiveY);
+ viewport.swizzle.z.Assign(Regs::ViewportSwizzle::PositiveZ);
+ viewport.swizzle.w.Assign(Regs::ViewportSwizzle::PositiveW);
+ }
// Doom and Bomberman seems to use the uninitialized registers and just enable blend
// so initialize blend registers with sane values
@@ -92,11 +102,19 @@ void Maxwell3D::InitializeRegisterDefaults() {
color_mask.A.Assign(1);
}
+ for (auto& format : regs.vertex_attrib_format) {
+ format.constant.Assign(1);
+ }
+
// NVN games expect these values to be enabled at boot
regs.rasterize_enable = 1;
regs.rt_separate_frag_data = 1;
regs.framebuffer_srgb = 1;
+ regs.line_width_aliased = 1.0f;
+ regs.line_width_smooth = 1.0f;
regs.front_face = Maxwell3D::Regs::FrontFace::ClockWise;
+ regs.polygon_mode_back = Maxwell3D::Regs::PolygonMode::Fill;
+ regs.polygon_mode_front = Maxwell3D::Regs::PolygonMode::Fill;
shadow_state = regs;
@@ -106,7 +124,113 @@ void Maxwell3D::InitializeRegisterDefaults() {
mme_inline[MAXWELL3D_REG_INDEX(index_array.count)] = true;
}
-void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) {
+void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) {
+ if (executing_macro == 0) {
+ // A macro call must begin by writing the macro method's register, not its argument.
+ ASSERT_MSG((method % 2) == 0,
+ "Can't start macro execution by writing to the ARGS register");
+ executing_macro = method;
+ }
+
+ macro_params.insert(macro_params.end(), base_start, base_start + amount);
+
+ // Call the macro when there are no more parameters in the command buffer
+ if (is_last_call) {
+ CallMacroMethod(executing_macro, macro_params);
+ macro_params.clear();
+ }
+}
+
+u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
+ // Keep track of the register value in shadow_state when requested.
+ const auto control = shadow_state.shadow_ram_control;
+ if (control == Regs::ShadowRamControl::Track ||
+ control == Regs::ShadowRamControl::TrackWithFilter) {
+ shadow_state.reg_array[method] = argument;
+ return argument;
+ }
+ if (control == Regs::ShadowRamControl::Replay) {
+ return shadow_state.reg_array[method];
+ }
+ return argument;
+}
+
+void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) {
+ if (regs.reg_array[method] == argument) {
+ return;
+ }
+ regs.reg_array[method] = argument;
+
+ for (const auto& table : dirty.tables) {
+ dirty.flags[table[method]] = true;
+ }
+}
+
+void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument,
+ bool is_last_call) {
+ switch (method) {
+ case MAXWELL3D_REG_INDEX(wait_for_idle):
+ return rasterizer->WaitForIdle();
+ case MAXWELL3D_REG_INDEX(shadow_ram_control):
+ shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
+ return;
+ case MAXWELL3D_REG_INDEX(macros.data):
+ return macro_engine->AddCode(regs.macros.upload_address, argument);
+ case MAXWELL3D_REG_INDEX(macros.bind):
+ return ProcessMacroBind(argument);
+ case MAXWELL3D_REG_INDEX(firmware[4]):
+ return ProcessFirmwareCall4();
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]):
+ return StartCBData(method);
+ case MAXWELL3D_REG_INDEX(cb_bind[0]):
+ return ProcessCBBind(0);
+ case MAXWELL3D_REG_INDEX(cb_bind[1]):
+ return ProcessCBBind(1);
+ case MAXWELL3D_REG_INDEX(cb_bind[2]):
+ return ProcessCBBind(2);
+ case MAXWELL3D_REG_INDEX(cb_bind[3]):
+ return ProcessCBBind(3);
+ case MAXWELL3D_REG_INDEX(cb_bind[4]):
+ return ProcessCBBind(4);
+ case MAXWELL3D_REG_INDEX(draw.vertex_end_gl):
+ return DrawArrays();
+ case MAXWELL3D_REG_INDEX(clear_buffers):
+ return ProcessClearBuffers();
+ case MAXWELL3D_REG_INDEX(query.query_get):
+ return ProcessQueryGet();
+ case MAXWELL3D_REG_INDEX(condition.mode):
+ return ProcessQueryCondition();
+ case MAXWELL3D_REG_INDEX(counter_reset):
+ return ProcessCounterReset();
+ case MAXWELL3D_REG_INDEX(sync_info):
+ return ProcessSyncPoint();
+ case MAXWELL3D_REG_INDEX(exec_upload):
+ return upload_state.ProcessExec(regs.exec_upload.linear != 0);
+ case MAXWELL3D_REG_INDEX(data_upload):
+ upload_state.ProcessData(argument, is_last_call);
+ if (is_last_call) {
+ OnMemoryWrite();
+ }
+ return;
+ }
+}
+
+void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) {
// Reset the current macro.
executing_macro = 0;
@@ -115,18 +239,16 @@ void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u3
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
// Execute the current macro.
- macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters);
+ macro_engine->Execute(*this, macro_positions[entry], parameters);
if (mme_draw.current_mode != MMEDrawMode::Undefined) {
FlushMMEInlineDraw();
}
}
-void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
- const u32 method = method_call.method;
-
+void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
if (method == cb_data_state.current) {
- regs.reg_array[method] = method_call.argument;
- ProcessCBData(method_call.argument);
+ regs.reg_array[method] = method_argument;
+ ProcessCBData(method_argument);
return;
} else if (cb_data_state.current != null_cb_data) {
FinishCBData();
@@ -141,61 +263,27 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
- // We're trying to execute a macro
- if (executing_macro == 0) {
- // A macro call must begin by writing the macro method's register, not its argument.
- ASSERT_MSG((method % 2) == 0,
- "Can't start macro execution by writing to the ARGS register");
- executing_macro = method;
- }
-
- macro_params.push_back(method_call.argument);
-
- // Call the macro when there are no more parameters in the command buffer
- if (method_call.IsLastCall()) {
- CallMacroMethod(executing_macro, macro_params.size(), macro_params.data());
- macro_params.clear();
- }
+ ProcessMacro(method, &method_argument, 1, is_last_call);
return;
}
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
- u32 arg = method_call.argument;
- // Keep track of the register value in shadow_state when requested.
- if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Track ||
- shadow_state.shadow_ram_control == Regs::ShadowRamControl::TrackWithFilter) {
- shadow_state.reg_array[method] = arg;
- } else if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Replay) {
- arg = shadow_state.reg_array[method];
- }
-
- if (regs.reg_array[method] != arg) {
- regs.reg_array[method] = arg;
+ const u32 argument = ProcessShadowRam(method, method_argument);
+ ProcessDirtyRegisters(method, argument);
+ ProcessMethodCall(method, argument, method_argument, is_last_call);
+}
- for (const auto& table : dirty.tables) {
- dirty.flags[table[method]] = true;
- }
+void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ // Methods after 0xE00 are special, they're actually triggers for some microcode that was
+ // uploaded to the GPU during initialization.
+ if (method >= MacroRegistersStart) {
+ ProcessMacro(method, base_start, amount, amount == methods_pending);
+ return;
}
-
switch (method) {
- case MAXWELL3D_REG_INDEX(shadow_ram_control): {
- shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(method_call.argument);
- break;
- }
- case MAXWELL3D_REG_INDEX(macros.data): {
- ProcessMacroUpload(arg);
- break;
- }
- case MAXWELL3D_REG_INDEX(macros.bind): {
- ProcessMacroBind(arg);
- break;
- }
- case MAXWELL3D_REG_INDEX(firmware[4]): {
- ProcessFirmwareCall4();
- break;
- }
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
@@ -211,67 +299,13 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
- StartCBData(method);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[0]): {
- ProcessCBBind(0);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[1]): {
- ProcessCBBind(1);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[2]): {
- ProcessCBBind(2);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[3]): {
- ProcessCBBind(3);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[4]): {
- ProcessCBBind(4);
- break;
- }
- case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): {
- DrawArrays();
- break;
- }
- case MAXWELL3D_REG_INDEX(clear_buffers): {
- ProcessClearBuffers();
- break;
- }
- case MAXWELL3D_REG_INDEX(query.query_get): {
- ProcessQueryGet();
- break;
- }
- case MAXWELL3D_REG_INDEX(condition.mode): {
- ProcessQueryCondition();
- break;
- }
- case MAXWELL3D_REG_INDEX(counter_reset): {
- ProcessCounterReset();
- break;
- }
- case MAXWELL3D_REG_INDEX(sync_info): {
- ProcessSyncPoint();
- break;
- }
- case MAXWELL3D_REG_INDEX(exec_upload): {
- upload_state.ProcessExec(regs.exec_upload.linear != 0);
- break;
- }
- case MAXWELL3D_REG_INDEX(data_upload): {
- const bool is_last_call = method_call.IsLastCall();
- upload_state.ProcessData(arg, is_last_call);
- if (is_last_call) {
- OnMemoryWrite();
- }
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]):
+ ProcessCBMultiData(method, base_start, amount);
break;
- }
default:
+ for (std::size_t i = 0; i < amount; i++) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
break;
}
}
@@ -300,16 +334,15 @@ void Maxwell3D::StepInstance(const MMEDrawMode expected_mode, const u32 count) {
StepInstance(expected_mode, count);
}
-void Maxwell3D::CallMethodFromMME(const GPU::MethodCall& method_call) {
- const u32 method = method_call.method;
+void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) {
if (mme_inline[method]) {
- regs.reg_array[method] = method_call.argument;
+ regs.reg_array[method] = method_argument;
if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count) ||
method == MAXWELL3D_REG_INDEX(index_array.count)) {
const MMEDrawMode expected_mode = method == MAXWELL3D_REG_INDEX(vertex_buffer.count)
? MMEDrawMode::Array
: MMEDrawMode::Indexed;
- StepInstance(expected_mode, method_call.argument);
+ StepInstance(expected_mode, method_argument);
} else if (method == MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)) {
mme_draw.instance_mode =
(regs.draw.instance_next != 0) || (regs.draw.instance_cont != 0);
@@ -321,7 +354,7 @@ void Maxwell3D::CallMethodFromMME(const GPU::MethodCall& method_call) {
if (mme_draw.current_mode != MMEDrawMode::Undefined) {
FlushMMEInlineDraw();
}
- CallMethod(method_call);
+ CallMethod(method, method_argument, true);
}
}
@@ -337,7 +370,7 @@ void Maxwell3D::FlushMMEInlineDraw() {
const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
if (ShouldExecute()) {
- rasterizer.Draw(is_indexed, true);
+ rasterizer->Draw(is_indexed, true);
}
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -358,9 +391,7 @@ void Maxwell3D::FlushMMEInlineDraw() {
}
void Maxwell3D::ProcessMacroUpload(u32 data) {
- ASSERT_MSG(regs.macros.upload_address < macro_memory.size(),
- "upload_address exceeded macro_memory size!");
- macro_memory[regs.macros.upload_address++] = data;
+ macro_engine->AddCode(regs.macros.upload_address++, data);
}
void Maxwell3D::ProcessMacroBind(u32 data) {
@@ -395,12 +426,17 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
void Maxwell3D::ProcessQueryGet() {
// TODO(Subv): Support the other query units.
- ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
- "Units other than CROP are unimplemented");
+ if (regs.query.query_get.unit != Regs::QueryUnit::Crop) {
+ LOG_DEBUG(HW_GPU, "Units other than CROP are unimplemented");
+ }
switch (regs.query.query_get.operation) {
case Regs::QueryOperation::Release:
- StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
+ if (regs.query.query_get.fence == 1) {
+ rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
+ } else {
+ StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
+ }
break;
case Regs::QueryOperation::Acquire:
// TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
@@ -465,11 +501,11 @@ void Maxwell3D::ProcessQueryCondition() {
void Maxwell3D::ProcessCounterReset() {
switch (regs.counter_reset) {
case Regs::CounterReset::SampleCnt:
- rasterizer.ResetCounter(QueryType::SamplesPassed);
+ rasterizer->ResetCounter(QueryType::SamplesPassed);
break;
default:
- LOG_WARNING(Render_OpenGL, "Unimplemented counter reset={}",
- static_cast<int>(regs.counter_reset));
+ LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}",
+ static_cast<int>(regs.counter_reset));
break;
}
}
@@ -479,7 +515,7 @@ void Maxwell3D::ProcessSyncPoint() {
const u32 increment = regs.sync_info.increment.Value();
[[maybe_unused]] const u32 cache_flush = regs.sync_info.unknown.Value();
if (increment) {
- system.GPU().IncrementSyncPoint(sync_point);
+ rasterizer->SignalSyncPoint(sync_point);
}
}
@@ -502,7 +538,7 @@ void Maxwell3D::DrawArrays() {
const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
if (ShouldExecute()) {
- rasterizer.Draw(is_indexed, false);
+ rasterizer->Draw(is_indexed, false);
}
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -522,12 +558,12 @@ std::optional<u64> Maxwell3D::GetQueryResult() {
return 0;
case Regs::QuerySelect::SamplesPassed:
// Deferred.
- rasterizer.Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
- system.GPU().GetTicks());
- return {};
+ rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
+ system.GPU().GetTicks());
+ return std::nullopt;
default:
- UNIMPLEMENTED_MSG("Unimplemented query select type {}",
- static_cast<u32>(regs.query.query_get.select.Value()));
+ LOG_DEBUG(HW_GPU, "Unimplemented query select type {}",
+ static_cast<u32>(regs.query.query_get.select.Value()));
return 1;
}
}
@@ -562,6 +598,28 @@ void Maxwell3D::StartCBData(u32 method) {
ProcessCBData(regs.const_buffer.cb_data[cb_data_state.id]);
}
+void Maxwell3D::ProcessCBMultiData(u32 method, const u32* start_base, u32 amount) {
+ if (cb_data_state.current != method) {
+ if (cb_data_state.current != null_cb_data) {
+ FinishCBData();
+ }
+ constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]);
+ cb_data_state.start_pos = regs.const_buffer.cb_pos;
+ cb_data_state.id = method - first_cb_data;
+ cb_data_state.current = method;
+ cb_data_state.counter = 0;
+ }
+ const std::size_t id = cb_data_state.id;
+ const std::size_t size = amount;
+ std::size_t i = 0;
+ for (; i < size; i++) {
+ cb_data_state.buffer[id][cb_data_state.counter] = start_base[i];
+ cb_data_state.counter++;
+ }
+ // Increment the current buffer position.
+ regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4 * amount;
+}
+
void Maxwell3D::FinishCBData() {
// Write the input value to the current const buffer at the current position.
const GPUVAddr buffer_address = regs.const_buffer.BufferAddress();
@@ -628,7 +686,7 @@ void Maxwell3D::ProcessClearBuffers() {
regs.clear_buffers.R == regs.clear_buffers.B &&
regs.clear_buffers.R == regs.clear_buffers.A);
- rasterizer.Clear();
+ rasterizer->Clear();
}
u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const {
@@ -650,8 +708,11 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b
const auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
const auto& tex_info_buffer = shader.const_buffers[const_buffer];
const GPUVAddr tex_info_address = tex_info_buffer.address + offset;
+ return AccessSampler(memory_manager.Read<u32>(tex_info_address));
+}
- const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
+SamplerDescriptor Maxwell3D::AccessSampler(u32 handle) const {
+ const Texture::TextureHandle tex_handle{handle};
const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle);
SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic);
result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
@@ -659,11 +720,11 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b
}
VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
const VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() const {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 5cf6a4cc3..1cbe8fe67 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -19,10 +19,11 @@
#include "common/math_util.h"
#include "video_core/engines/const_buffer_engine_interface.h"
#include "video_core/engines/const_buffer_info.h"
+#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
#include "video_core/engines/shader_type.h"
#include "video_core/gpu.h"
-#include "video_core/macro_interpreter.h"
+#include "video_core/macro/macro.h"
#include "video_core/textures/texture.h"
namespace Core {
@@ -48,11 +49,13 @@ namespace Tegra::Engines {
#define MAXWELL3D_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32))
-class Maxwell3D final : public ConstBufferEngineInterface {
+class Maxwell3D final : public ConstBufferEngineInterface, public EngineInterface {
public:
- explicit Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager);
- ~Maxwell3D() = default;
+ explicit Maxwell3D(Core::System& system, MemoryManager& memory_manager);
+ ~Maxwell3D();
+
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
@@ -575,6 +578,17 @@ public:
Replay = 3,
};
+ enum class ViewportSwizzle : u32 {
+ PositiveX = 0,
+ NegativeX = 1,
+ PositiveY = 2,
+ NegativeY = 3,
+ PositiveZ = 4,
+ NegativeZ = 5,
+ PositiveW = 6,
+ NegativeW = 7,
+ };
+
struct RenderTargetConfig {
u32 address_high;
u32 address_low;
@@ -586,6 +600,7 @@ public:
BitField<4, 3, u32> block_height;
BitField<8, 3, u32> block_depth;
BitField<12, 1, InvMemoryLayout> type;
+ BitField<16, 1, u32> is_3d;
} memory_layout;
union {
BitField<0, 16, u32> layers;
@@ -618,7 +633,14 @@ public:
f32 translate_x;
f32 translate_y;
f32 translate_z;
- INSERT_UNION_PADDING_WORDS(2);
+ union {
+ u32 raw;
+ BitField<0, 3, ViewportSwizzle> x;
+ BitField<4, 3, ViewportSwizzle> y;
+ BitField<8, 3, ViewportSwizzle> z;
+ BitField<12, 3, ViewportSwizzle> w;
+ } swizzle;
+ INSERT_UNION_PADDING_WORDS(1);
Common::Rectangle<f32> GetRect() const {
return {
@@ -627,7 +649,7 @@ public:
GetX() + GetWidth(), // right
GetY() // bottom
};
- };
+ }
f32 GetX() const {
return std::max(0.0f, translate_x - std::fabs(scale_x));
@@ -709,7 +731,9 @@ public:
union {
struct {
- INSERT_UNION_PADDING_WORDS(0x45);
+ INSERT_UNION_PADDING_WORDS(0x44);
+
+ u32 wait_for_idle;
struct {
u32 upload_address;
@@ -1149,7 +1173,7 @@ public:
/// Returns whether the vertex array specified by index is supposed to be
/// accessed per instance or not.
- bool IsInstancingEnabled(u32 index) const {
+ bool IsInstancingEnabled(std::size_t index) const {
return is_instanced[index];
}
} instanced_arrays;
@@ -1179,6 +1203,7 @@ public:
BitField<0, 1, u32> depth_range_0_1;
BitField<3, 1, u32> depth_clamp_near;
BitField<4, 1, u32> depth_clamp_far;
+ BitField<11, 1, u32> depth_clamp_disabled;
} view_volume_clip_control;
INSERT_UNION_PADDING_WORDS(0x1F);
@@ -1259,7 +1284,8 @@ public:
GPUVAddr LimitAddress() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) |
- limit_low);
+ limit_low) +
+ 1;
}
} vertex_array_limit[NumVertexArrays];
@@ -1356,10 +1382,14 @@ public:
u32 GetRegisterValue(u32 method) const;
/// Write the value to the register identified by method.
- void CallMethod(const GPU::MethodCall& method_call);
+ void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
+
+ /// Write multiple values to the register identified by method.
+ void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) override;
/// Write the value to the register identified by method.
- void CallMethodFromMME(const GPU::MethodCall& method_call);
+ void CallMethodFromMME(u32 method, u32 method_argument);
void FlushMMEInlineDraw();
@@ -1376,6 +1406,8 @@ public:
SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
u64 offset) const override;
+ SamplerDescriptor AccessSampler(u32 handle) const override;
+
u32 GetBoundBuffer() const override {
return regs.tex_cb_index;
}
@@ -1384,17 +1416,16 @@ public:
const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override;
- /// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
- /// we've seen used.
- using MacroMemory = std::array<u32, 0x40000>;
+ bool ShouldExecute() const {
+ return execute_on;
+ }
- /// Gets a reference to macro memory.
- const MacroMemory& GetMacroMemory() const {
- return macro_memory;
+ VideoCore::RasterizerInterface& Rasterizer() {
+ return *rasterizer;
}
- bool ShouldExecute() const {
- return execute_on;
+ const VideoCore::RasterizerInterface& Rasterizer() const {
+ return *rasterizer;
}
/// Notify a memory write has happened.
@@ -1430,27 +1461,31 @@ public:
private:
void InitializeRegisterDefaults();
- Core::System& system;
+ void ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call);
- VideoCore::RasterizerInterface& rasterizer;
+ u32 ProcessShadowRam(u32 method, u32 argument);
+ void ProcessDirtyRegisters(u32 method, u32 argument);
+
+ void ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call);
+
+ Core::System& system;
MemoryManager& memory_manager;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+
/// Start offsets of each macro in macro_memory
std::array<u32, 0x80> macro_positions = {};
std::array<bool, Regs::NUM_REGS> mme_inline{};
- /// Memory for macro code
- MacroMemory macro_memory;
-
/// Macro method that is currently being executed / being fed parameters.
u32 executing_macro = 0;
/// Parameters that have been submitted to the macro call so far.
std::vector<u32> macro_params;
/// Interpreter for the macro codes uploaded to the GPU.
- MacroInterpreter macro_interpreter;
+ std::unique_ptr<MacroEngine> macro_engine;
static constexpr u32 null_cb_data = 0xFFFFFFFF;
struct {
@@ -1479,7 +1514,7 @@ private:
* @param num_parameters Number of arguments
* @param parameters Arguments to the method call
*/
- void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters);
+ void CallMacroMethod(u32 method, const std::vector<u32>& parameters);
/// Handles writes to the macro uploading register.
void ProcessMacroUpload(u32 data);
@@ -1511,6 +1546,7 @@ private:
/// Handles a write to the CB_DATA[i] register.
void StartCBData(u32 method);
void ProcessCBData(u32 value);
+ void ProcessCBMultiData(u32 method, const u32* start_base, u32 amount);
void FinishCBData();
/// Handles a write to the CB_BIND register.
@@ -1530,6 +1566,7 @@ private:
static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
+ASSERT_REG_POSITION(wait_for_idle, 0x44);
ASSERT_REG_POSITION(macros, 0x45);
ASSERT_REG_POSITION(shadow_ram_control, 0x49);
ASSERT_REG_POSITION(upload, 0x60);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index c2610f992..8fa359d0a 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -14,43 +14,45 @@
namespace Tegra::Engines {
+using namespace Texture;
+
MaxwellDMA::MaxwellDMA(Core::System& system, MemoryManager& memory_manager)
: system{system}, memory_manager{memory_manager} {}
-void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) {
- ASSERT_MSG(method_call.method < Regs::NUM_REGS,
- "Invalid MaxwellDMA register, increase the size of the Regs structure");
-
- regs.reg_array[method_call.method] = method_call.argument;
+void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
+ ASSERT_MSG(method < NUM_REGS, "Invalid MaxwellDMA register");
-#define MAXWELLDMA_REG_INDEX(field_name) \
- (offsetof(Tegra::Engines::MaxwellDMA::Regs, field_name) / sizeof(u32))
+ regs.reg_array[method] = method_argument;
- switch (method_call.method) {
- case MAXWELLDMA_REG_INDEX(exec): {
- HandleCopy();
- break;
- }
+ if (method == offsetof(Regs, launch_dma) / sizeof(u32)) {
+ Launch();
}
-
-#undef MAXWELLDMA_REG_INDEX
}
-void MaxwellDMA::HandleCopy() {
- LOG_TRACE(HW_GPU, "Requested a DMA copy");
+void MaxwellDMA::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ for (size_t i = 0; i < amount; ++i) {
+ CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ }
+}
- const GPUVAddr source = regs.src_address.Address();
- const GPUVAddr dest = regs.dst_address.Address();
+void MaxwellDMA::Launch() {
+ LOG_TRACE(Render_OpenGL, "DMA copy 0x{:x} -> 0x{:x}", static_cast<GPUVAddr>(regs.offset_in),
+ static_cast<GPUVAddr>(regs.offset_out));
// TODO(Subv): Perform more research and implement all features of this engine.
- ASSERT(regs.exec.enable_swizzle == 0);
- ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
- ASSERT(regs.exec.query_intr == Regs::QueryIntr::None);
- ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2);
- ASSERT(regs.dst_params.pos_x == 0);
- ASSERT(regs.dst_params.pos_y == 0);
-
- if (!regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
+ const LaunchDMA& launch = regs.launch_dma;
+ ASSERT(launch.remap_enable == 0);
+ ASSERT(launch.semaphore_type == LaunchDMA::SemaphoreType::NONE);
+ ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
+ ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
+ ASSERT(regs.dst_params.origin.x == 0);
+ ASSERT(regs.dst_params.origin.y == 0);
+
+ const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
+ const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
+
+ if (!is_src_pitch && !is_dst_pitch) {
// If both the source and the destination are in block layout, assert.
UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented");
return;
@@ -59,99 +61,154 @@ void MaxwellDMA::HandleCopy() {
// All copies here update the main memory, so mark all rasterizer states as invalid.
system.GPU().Maxwell3D().OnMemoryWrite();
- if (regs.exec.is_dst_linear && regs.exec.is_src_linear) {
- // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D
- // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
- // y_count).
- if (!regs.exec.enable_2d) {
- memory_manager.CopyBlock(dest, source, regs.x_count);
- return;
- }
+ if (is_src_pitch && is_dst_pitch) {
+ CopyPitchToPitch();
+ } else {
+ ASSERT(launch.multi_line_enable == 1);
- // If both the source and the destination are in linear layout, perform a line-by-line
- // copy. We're going to take a subrect of size (x_count, y_count) from the source
- // rectangle. There is no need to manually flush/invalidate the regions because
- // CopyBlock does that for us.
- for (u32 line = 0; line < regs.y_count; ++line) {
- const GPUVAddr source_line = source + line * regs.src_pitch;
- const GPUVAddr dest_line = dest + line * regs.dst_pitch;
- memory_manager.CopyBlock(dest_line, source_line, regs.x_count);
+ if (!is_src_pitch && is_dst_pitch) {
+ CopyBlockLinearToPitch();
+ } else {
+ CopyPitchToBlockLinear();
}
+ }
+}
+
+void MaxwellDMA::CopyPitchToPitch() {
+ // When `multi_line_enable` bit is disabled the copy is performed as if we were copying a 1D
+ // buffer of length `line_length_in`.
+ // Otherwise we copy a 2D image of dimensions (line_length_in, line_count).
+ if (!regs.launch_dma.multi_line_enable) {
+ memory_manager.CopyBlock(regs.offset_out, regs.offset_in, regs.line_length_in);
return;
}
- ASSERT(regs.exec.enable_2d == 1);
+ // Perform a line-by-line copy.
+ // We're going to take a subrect of size (line_length_in, line_count) from the source rectangle.
+ // There is no need to manually flush/invalidate the regions because CopyBlock does that for us.
+ for (u32 line = 0; line < regs.line_count; ++line) {
+ const GPUVAddr source_line = regs.offset_in + static_cast<size_t>(line) * regs.pitch_in;
+ const GPUVAddr dest_line = regs.offset_out + static_cast<size_t>(line) * regs.pitch_out;
+ memory_manager.CopyBlock(dest_line, source_line, regs.line_length_in);
+ }
+}
- if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
- ASSERT(regs.src_params.BlockDepth() == 0);
- // If the input is tiled and the output is linear, deswizzle the input and copy it over.
- const u32 bytes_per_pixel = regs.dst_pitch / regs.x_count;
- const std::size_t src_size = Texture::CalculateSize(
- true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y,
- regs.src_params.size_z, regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
+void MaxwellDMA::CopyBlockLinearToPitch() {
+ UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
+ UNIMPLEMENTED_IF(regs.src_params.layer != 0);
- const std::size_t src_layer_size = Texture::CalculateSize(
- true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y, 1,
- regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
+ // Optimized path for micro copies.
+ const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
+ if (dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X) {
+ FastCopyBlockLinearToPitch();
+ return;
+ }
- const std::size_t dst_size = regs.dst_pitch * regs.y_count;
+ // Deswizzle the input and copy it over.
+ const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in;
+ const Parameters& src_params = regs.src_params;
+ const u32 width = src_params.width;
+ const u32 height = src_params.height;
+ const u32 depth = src_params.depth;
+ const u32 block_height = src_params.block_size.height;
+ const u32 block_depth = src_params.block_size.depth;
+ const size_t src_size =
+ CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
+
+ if (read_buffer.size() < src_size) {
+ read_buffer.resize(src_size);
+ }
+ if (write_buffer.size() < dst_size) {
+ write_buffer.resize(dst_size);
+ }
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
+ memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+ UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel,
+ block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(),
+ read_buffer.data());
- memory_manager.ReadBlock(source, read_buffer.data(), src_size);
- memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
+ memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
+}
- Texture::UnswizzleSubrect(
- regs.x_count, regs.y_count, regs.dst_pitch, regs.src_params.size_x, bytes_per_pixel,
- read_buffer.data() + src_layer_size * regs.src_params.pos_z, write_buffer.data(),
- regs.src_params.BlockHeight(), regs.src_params.pos_x, regs.src_params.pos_y);
+void MaxwellDMA::CopyPitchToBlockLinear() {
+ const auto& dst_params = regs.dst_params;
+ const u32 bytes_per_pixel = regs.pitch_in / regs.line_length_in;
+ const u32 width = dst_params.width;
+ const u32 height = dst_params.height;
+ const u32 depth = dst_params.depth;
+ const u32 block_height = dst_params.block_size.height;
+ const u32 block_depth = dst_params.block_size.depth;
+ const size_t dst_size =
+ CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
+ const size_t dst_layer_size =
+ CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth);
+
+ const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
+
+ if (read_buffer.size() < src_size) {
+ read_buffer.resize(src_size);
+ }
+ if (write_buffer.size() < dst_size) {
+ write_buffer.resize(dst_size);
+ }
- memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
+ if (Settings::IsGPULevelExtreme()) {
+ memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
} else {
- ASSERT(regs.dst_params.BlockDepth() == 0);
-
- const u32 bytes_per_pixel = regs.src_pitch / regs.x_count;
-
- const std::size_t dst_size = Texture::CalculateSize(
- true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y,
- regs.dst_params.size_z, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
-
- const std::size_t dst_layer_size = Texture::CalculateSize(
- true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1,
- regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
+ memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size);
+ memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
+ }
- const std::size_t src_size = regs.src_pitch * regs.y_count;
+ // If the input is linear and the output is tiled, swizzle the input and copy it over.
+ if (regs.dst_params.block_size.depth > 0) {
+ ASSERT(dst_params.layer == 0);
+ SwizzleSliceToVoxel(regs.line_length_in, regs.line_count, regs.pitch_in, width, height,
+ bytes_per_pixel, block_height, block_depth, dst_params.origin.x,
+ dst_params.origin.y, write_buffer.data(), read_buffer.data());
+ } else {
+ SwizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_in, width, bytes_per_pixel,
+ write_buffer.data() + dst_layer_size * dst_params.layer, read_buffer.data(),
+ block_height, dst_params.origin.x, dst_params.origin.y);
+ }
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
+ memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
+}
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+void MaxwellDMA::FastCopyBlockLinearToPitch() {
+ const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in;
+ const size_t src_size = GOB_SIZE;
+ const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
+ u32 pos_x = regs.src_params.origin.x;
+ u32 pos_y = regs.src_params.origin.y;
+ const u64 offset = GetGOBOffset(regs.src_params.width, regs.src_params.height, pos_x, pos_y,
+ regs.src_params.block_size.height, bytes_per_pixel);
+ const u32 x_in_gob = 64 / bytes_per_pixel;
+ pos_x = pos_x % x_in_gob;
+ pos_y = pos_y % 8;
+
+ if (read_buffer.size() < src_size) {
+ read_buffer.resize(src_size);
+ }
+ if (write_buffer.size() < dst_size) {
+ write_buffer.resize(dst_size);
+ }
- if (Settings::values.use_accurate_gpu_emulation) {
- memory_manager.ReadBlock(source, read_buffer.data(), src_size);
- memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
- } else {
- memory_manager.ReadBlockUnsafe(source, read_buffer.data(), src_size);
- memory_manager.ReadBlockUnsafe(dest, write_buffer.data(), dst_size);
- }
+ if (Settings::IsGPULevelExtreme()) {
+ memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
+ } else {
+ memory_manager.ReadBlockUnsafe(regs.offset_in + offset, read_buffer.data(), src_size);
+ memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
+ }
- // If the input is linear and the output is tiled, swizzle the input and copy it over.
- Texture::SwizzleSubrect(
- regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, bytes_per_pixel,
- write_buffer.data() + dst_layer_size * regs.dst_params.pos_z, read_buffer.data(),
- regs.dst_params.BlockHeight(), regs.dst_params.pos_x, regs.dst_params.pos_y);
+ UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width,
+ bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y,
+ write_buffer.data(), read_buffer.data());
- memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
- }
+ memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 4f40d1d1f..50f445efc 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -10,6 +10,7 @@
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/engines/engine_interface.h"
#include "video_core/gpu.h"
namespace Core {
@@ -23,156 +24,190 @@ class MemoryManager;
namespace Tegra::Engines {
/**
- * This Engine is known as GK104_Copy. Documentation can be found in:
+ * This engine is known as gk104_copy. Documentation can be found in:
+ * https://github.com/NVIDIA/open-gpu-doc/blob/master/classes/dma-copy/clb0b5.h
* https://github.com/envytools/envytools/blob/master/rnndb/fifo/gk104_copy.xml
*/
-class MaxwellDMA final {
+class MaxwellDMA final : public EngineInterface {
public:
- explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager);
- ~MaxwellDMA() = default;
-
- /// Write the value to the register identified by method.
- void CallMethod(const GPU::MethodCall& method_call);
+ struct PackedGPUVAddr {
+ u32 upper;
+ u32 lower;
+
+ constexpr operator GPUVAddr() const noexcept {
+ return (static_cast<GPUVAddr>(upper & 0xff) << 32) | lower;
+ }
+ };
+
+ union BlockSize {
+ BitField<0, 4, u32> width;
+ BitField<4, 4, u32> height;
+ BitField<8, 4, u32> depth;
+ BitField<12, 4, u32> gob_height;
+ };
+ static_assert(sizeof(BlockSize) == 4);
+
+ union Origin {
+ BitField<0, 16, u32> x;
+ BitField<16, 16, u32> y;
+ };
+ static_assert(sizeof(Origin) == 4);
+
+ struct Parameters {
+ BlockSize block_size;
+ u32 width;
+ u32 height;
+ u32 depth;
+ u32 layer;
+ Origin origin;
+ };
+ static_assert(sizeof(Parameters) == 24);
+
+ struct Semaphore {
+ PackedGPUVAddr address;
+ u32 payload;
+ };
+ static_assert(sizeof(Semaphore) == 12);
+
+ struct RenderEnable {
+ enum class Mode : u32 {
+ FALSE = 0,
+ TRUE = 1,
+ CONDITIONAL = 2,
+ RENDER_IF_EQUAL = 3,
+ RENDER_IF_NOT_EQUAL = 4,
+ };
- struct Regs {
- static constexpr std::size_t NUM_REGS = 0x1D6;
+ PackedGPUVAddr address;
+ BitField<0, 3, Mode> mode;
+ };
+ static_assert(sizeof(RenderEnable) == 12);
+
+ enum class PhysModeTarget : u32 {
+ LOCAL_FB = 0,
+ COHERENT_SYSMEM = 1,
+ NONCOHERENT_SYSMEM = 2,
+ };
+ using PhysMode = BitField<0, 2, PhysModeTarget>;
+
+ union LaunchDMA {
+ enum class DataTransferType : u32 {
+ NONE = 0,
+ PIPELINED = 1,
+ NON_PIPELINED = 2,
+ };
- struct Parameters {
- union {
- BitField<0, 4, u32> block_depth;
- BitField<4, 4, u32> block_height;
- BitField<8, 4, u32> block_width;
- };
- u32 size_x;
- u32 size_y;
- u32 size_z;
- u32 pos_z;
- union {
- BitField<0, 16, u32> pos_x;
- BitField<16, 16, u32> pos_y;
- };
+ enum class SemaphoreType : u32 {
+ NONE = 0,
+ RELEASE_ONE_WORD_SEMAPHORE = 1,
+ RELEASE_FOUR_WORD_SEMAPHORE = 2,
+ };
- u32 BlockHeight() const {
- return block_height.Value();
- }
+ enum class InterruptType : u32 {
+ NONE = 0,
+ BLOCKING = 1,
+ NON_BLOCKING = 2,
+ };
- u32 BlockDepth() const {
- return block_depth.Value();
- }
+ enum class MemoryLayout : u32 {
+ BLOCKLINEAR = 0,
+ PITCH = 1,
};
- static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
+ enum class Type : u32 {
+ VIRTUAL = 0,
+ PHYSICAL = 1,
+ };
- enum class ComponentMode : u32 {
- Src0 = 0,
- Src1 = 1,
- Src2 = 2,
- Src3 = 3,
- Const0 = 4,
- Const1 = 5,
- Zero = 6,
+ enum class SemaphoreReduction : u32 {
+ IMIN = 0,
+ IMAX = 1,
+ IXOR = 2,
+ IAND = 3,
+ IOR = 4,
+ IADD = 5,
+ INC = 6,
+ DEC = 7,
+ FADD = 0xA,
};
- enum class CopyMode : u32 {
- None = 0,
- Unk1 = 1,
- Unk2 = 2,
+ enum class SemaphoreReductionSign : u32 {
+ SIGNED = 0,
+ UNSIGNED = 1,
};
- enum class QueryMode : u32 {
- None = 0,
- Short = 1,
- Long = 2,
+ enum class BypassL2 : u32 {
+ USE_PTE_SETTING = 0,
+ FORCE_VOLATILE = 1,
};
- enum class QueryIntr : u32 {
- None = 0,
- Block = 1,
- NonBlock = 2,
+ BitField<0, 2, DataTransferType> data_transfer_type;
+ BitField<2, 1, u32> flush_enable;
+ BitField<3, 2, SemaphoreType> semaphore_type;
+ BitField<5, 2, InterruptType> interrupt_type;
+ BitField<7, 1, MemoryLayout> src_memory_layout;
+ BitField<8, 1, MemoryLayout> dst_memory_layout;
+ BitField<9, 1, u32> multi_line_enable;
+ BitField<10, 1, u32> remap_enable;
+ BitField<11, 1, u32> rmwdisable;
+ BitField<12, 1, Type> src_type;
+ BitField<13, 1, Type> dst_type;
+ BitField<14, 4, SemaphoreReduction> semaphore_reduction;
+ BitField<18, 1, SemaphoreReductionSign> semaphore_reduction_sign;
+ BitField<19, 1, u32> reduction_enable;
+ BitField<20, 1, BypassL2> bypass_l2;
+ };
+ static_assert(sizeof(LaunchDMA) == 4);
+
+ struct RemapConst {
+ enum Swizzle : u32 {
+ SRC_X = 0,
+ SRC_Y = 1,
+ SRC_Z = 2,
+ SRC_W = 3,
+ CONST_A = 4,
+ CONST_B = 5,
+ NO_WRITE = 6,
};
+ PackedGPUVAddr address;
+
union {
- struct {
- INSERT_UNION_PADDING_WORDS(0xC0);
-
- struct {
- union {
- BitField<0, 2, CopyMode> copy_mode;
- BitField<2, 1, u32> flush;
-
- BitField<3, 2, QueryMode> query_mode;
- BitField<5, 2, QueryIntr> query_intr;
-
- BitField<7, 1, u32> is_src_linear;
- BitField<8, 1, u32> is_dst_linear;
-
- BitField<9, 1, u32> enable_2d;
- BitField<10, 1, u32> enable_swizzle;
- };
- } exec;
-
- INSERT_UNION_PADDING_WORDS(0x3F);
-
- struct {
- u32 address_high;
- u32 address_low;
-
- GPUVAddr Address() const {
- return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
- address_low);
- }
- } src_address;
-
- struct {
- u32 address_high;
- u32 address_low;
-
- GPUVAddr Address() const {
- return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
- address_low);
- }
- } dst_address;
-
- u32 src_pitch;
- u32 dst_pitch;
- u32 x_count;
- u32 y_count;
-
- INSERT_UNION_PADDING_WORDS(0xB8);
-
- u32 const0;
- u32 const1;
- union {
- BitField<0, 4, ComponentMode> component0;
- BitField<4, 4, ComponentMode> component1;
- BitField<8, 4, ComponentMode> component2;
- BitField<12, 4, ComponentMode> component3;
- BitField<16, 2, u32> component_size;
- BitField<20, 3, u32> src_num_components;
- BitField<24, 3, u32> dst_num_components;
-
- u32 SrcBytePerPixel() const {
- return src_num_components.Value() * component_size.Value();
- }
- u32 DstBytePerPixel() const {
- return dst_num_components.Value() * component_size.Value();
- }
- } swizzle_config;
+ BitField<0, 3, Swizzle> dst_x;
+ BitField<4, 3, Swizzle> dst_y;
+ BitField<8, 3, Swizzle> dst_z;
+ BitField<12, 3, Swizzle> dst_w;
+ BitField<16, 2, u32> component_size_minus_one;
+ BitField<20, 2, u32> num_src_components_minus_one;
+ BitField<24, 2, u32> num_dst_components_minus_one;
+ };
+ };
+ static_assert(sizeof(RemapConst) == 12);
- Parameters dst_params;
+ explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager);
+ ~MaxwellDMA() = default;
- INSERT_UNION_PADDING_WORDS(1);
+ /// Write the value to the register identified by method.
+ void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
- Parameters src_params;
-
- INSERT_UNION_PADDING_WORDS(0x13);
- };
- std::array<u32, NUM_REGS> reg_array;
- };
- } regs{};
+ /// Write multiple values to the register identified by method.
+ void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
+ u32 methods_pending) override;
private:
+ /// Performs the copy from the source buffer to the destination buffer as configured in the
+ /// registers.
+ void Launch();
+
+ void CopyPitchToPitch();
+
+ void CopyBlockLinearToPitch();
+
+ void CopyPitchToBlockLinear();
+
+ void FastCopyBlockLinearToPitch();
+
Core::System& system;
MemoryManager& memory_manager;
@@ -180,28 +215,58 @@ private:
std::vector<u8> read_buffer;
std::vector<u8> write_buffer;
- /// Performs the copy from the source buffer to the destination buffer as configured in the
- /// registers.
- void HandleCopy();
-};
+ static constexpr std::size_t NUM_REGS = 0x800;
+ struct Regs {
+ union {
+ struct {
+ u32 reserved[0x40];
+ u32 nop;
+ u32 reserved01[0xf];
+ u32 pm_trigger;
+ u32 reserved02[0x3f];
+ Semaphore semaphore;
+ u32 reserved03[0x2];
+ RenderEnable render_enable;
+ PhysMode src_phys_mode;
+ PhysMode dst_phys_mode;
+ u32 reserved04[0x26];
+ LaunchDMA launch_dma;
+ u32 reserved05[0x3f];
+ PackedGPUVAddr offset_in;
+ PackedGPUVAddr offset_out;
+ u32 pitch_in;
+ u32 pitch_out;
+ u32 line_length_in;
+ u32 line_count;
+ u32 reserved06[0xb8];
+ RemapConst remap_const;
+ Parameters dst_params;
+ u32 reserved07[0x1];
+ Parameters src_params;
+ u32 reserved08[0x275];
+ u32 pm_trigger_end;
+ u32 reserved09[0x3ba];
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(exec, 0xC0);
-ASSERT_REG_POSITION(src_address, 0x100);
-ASSERT_REG_POSITION(dst_address, 0x102);
-ASSERT_REG_POSITION(src_pitch, 0x104);
-ASSERT_REG_POSITION(dst_pitch, 0x105);
-ASSERT_REG_POSITION(x_count, 0x106);
-ASSERT_REG_POSITION(y_count, 0x107);
-ASSERT_REG_POSITION(const0, 0x1C0);
-ASSERT_REG_POSITION(const1, 0x1C1);
-ASSERT_REG_POSITION(swizzle_config, 0x1C2);
-ASSERT_REG_POSITION(dst_params, 0x1C3);
-ASSERT_REG_POSITION(src_params, 0x1CA);
+ ASSERT_REG_POSITION(launch_dma, 0xC0);
+ ASSERT_REG_POSITION(offset_in, 0x100);
+ ASSERT_REG_POSITION(offset_out, 0x102);
+ ASSERT_REG_POSITION(pitch_in, 0x104);
+ ASSERT_REG_POSITION(pitch_out, 0x105);
+ ASSERT_REG_POSITION(line_length_in, 0x106);
+ ASSERT_REG_POSITION(line_count, 0x107);
+ ASSERT_REG_POSITION(remap_const, 0x1C0);
+ ASSERT_REG_POSITION(dst_params, 0x1C3);
+ ASSERT_REG_POSITION(src_params, 0x1CA);
#undef ASSERT_REG_POSITION
+};
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 5e9cfba22..37d17efdc 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -32,31 +32,31 @@ struct Register {
constexpr Register() = default;
- constexpr Register(u64 value) : value(value) {}
+ constexpr Register(u64 value_) : value(value_) {}
- constexpr operator u64() const {
+ [[nodiscard]] constexpr operator u64() const {
return value;
}
template <typename T>
- constexpr u64 operator-(const T& oth) const {
+ [[nodiscard]] constexpr u64 operator-(const T& oth) const {
return value - oth;
}
template <typename T>
- constexpr u64 operator&(const T& oth) const {
+ [[nodiscard]] constexpr u64 operator&(const T& oth) const {
return value & oth;
}
- constexpr u64 operator&(const Register& oth) const {
+ [[nodiscard]] constexpr u64 operator&(const Register& oth) const {
return value & oth.value;
}
- constexpr u64 operator~() const {
+ [[nodiscard]] constexpr u64 operator~() const {
return ~value;
}
- u64 GetSwizzledIndex(u64 elem) const {
+ [[nodiscard]] u64 GetSwizzledIndex(u64 elem) const {
elem = (value + elem) & 3;
return (value & ~3) + elem;
}
@@ -75,7 +75,7 @@ enum class AttributeSize : u64 {
union Attribute {
Attribute() = default;
- constexpr explicit Attribute(u64 value) : value(value) {}
+ constexpr explicit Attribute(u64 value_) : value(value_) {}
enum class Index : u64 {
LayerViewportPointSize = 6,
@@ -107,7 +107,7 @@ union Attribute {
BitField<31, 1, u64> patch;
BitField<47, 3, AttributeSize> size;
- bool IsPhysical() const {
+ [[nodiscard]] bool IsPhysical() const {
return patch == 0 && element == 0 && static_cast<u64>(index.Value()) == 0;
}
} fmt20;
@@ -124,7 +124,7 @@ union Attribute {
union Sampler {
Sampler() = default;
- constexpr explicit Sampler(u64 value) : value(value) {}
+ constexpr explicit Sampler(u64 value_) : value(value_) {}
enum class Index : u64 {
Sampler_0 = 8,
@@ -137,7 +137,7 @@ union Sampler {
union Image {
Image() = default;
- constexpr explicit Image(u64 value) : value{value} {}
+ constexpr explicit Image(u64 value_) : value{value_} {}
BitField<36, 13, u64> index;
u64 value;
@@ -168,18 +168,22 @@ enum class Pred : u64 {
};
enum class PredCondition : u64 {
- LessThan = 1,
- Equal = 2,
- LessEqual = 3,
- GreaterThan = 4,
- NotEqual = 5,
- GreaterEqual = 6,
- LessThanWithNan = 9,
- LessEqualWithNan = 11,
- GreaterThanWithNan = 12,
- NotEqualWithNan = 13,
- GreaterEqualWithNan = 14,
- // TODO(Subv): Other condition types
+ F = 0, // Always false
+ LT = 1, // Ordered less than
+ EQ = 2, // Ordered equal
+ LE = 3, // Ordered less than or equal
+ GT = 4, // Ordered greater than
+ NE = 5, // Ordered not equal
+ GE = 6, // Ordered greater than or equal
+ NUM = 7, // Ordered
+ NAN_ = 8, // Unordered
+ LTU = 9, // Unordered less than
+ EQU = 10, // Unordered equal
+ LEU = 11, // Unordered less than or equal
+ GTU = 12, // Unordered greater than
+ NEU = 13, // Unordered not equal
+ GEU = 14, // Unordered greater than or equal
+ T = 15, // Always true
};
enum class PredOperation : u64 {
@@ -501,14 +505,14 @@ struct IpaMode {
IpaInterpMode interpolation_mode;
IpaSampleMode sampling_mode;
- bool operator==(const IpaMode& a) const {
+ [[nodiscard]] bool operator==(const IpaMode& a) const {
return std::tie(interpolation_mode, sampling_mode) ==
std::tie(a.interpolation_mode, a.sampling_mode);
}
- bool operator!=(const IpaMode& a) const {
+ [[nodiscard]] bool operator!=(const IpaMode& a) const {
return !operator==(a);
}
- bool operator<(const IpaMode& a) const {
+ [[nodiscard]] bool operator<(const IpaMode& a) const {
return std::tie(interpolation_mode, sampling_mode) <
std::tie(a.interpolation_mode, a.sampling_mode);
}
@@ -654,7 +658,12 @@ union Instruction {
return *this;
}
- constexpr Instruction(u64 value) : value{value} {}
+ constexpr Instruction(u64 value_) : value{value_} {}
+ constexpr Instruction(const Instruction& instr) : value(instr.value) {}
+
+ [[nodiscard]] constexpr bool Bit(u64 offset) const {
+ return ((value >> offset) & 1) != 0;
+ }
BitField<0, 8, Register> gpr0;
BitField<8, 8, Register> gpr8;
@@ -737,34 +746,34 @@ union Instruction {
BitField<28, 8, u64> imm_lut28;
BitField<48, 8, u64> imm_lut48;
- u32 GetImmLut28() const {
+ [[nodiscard]] u32 GetImmLut28() const {
return static_cast<u32>(imm_lut28);
}
- u32 GetImmLut48() const {
+ [[nodiscard]] u32 GetImmLut48() const {
return static_cast<u32>(imm_lut48);
}
} lop3;
- u16 GetImm20_16() const {
+ [[nodiscard]] u16 GetImm20_16() const {
return static_cast<u16>(imm20_16);
}
- u32 GetImm20_19() const {
+ [[nodiscard]] u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
imm |= negate_imm ? 0x80000000 : 0;
return imm;
}
- u32 GetImm20_32() const {
+ [[nodiscard]] u32 GetImm20_32() const {
return static_cast<u32>(imm20_32);
}
- s32 GetSignedImm20_20() const {
- u32 immediate = static_cast<u32>(imm20_19 | (negate_imm << 19));
+ [[nodiscard]] s32 GetSignedImm20_20() const {
+ const auto immediate = static_cast<u32>(imm20_19 | (negate_imm << 19));
// Sign extend the 20-bit value.
- u32 mask = 1U << (20 - 1);
+ const auto mask = 1U << (20 - 1);
return static_cast<s32>((immediate ^ mask) - mask);
}
} alu;
@@ -813,15 +822,17 @@ union Instruction {
} alu_integer;
union {
+ BitField<43, 1, u64> x;
+ } iadd;
+
+ union {
BitField<39, 1, u64> ftz;
BitField<32, 1, u64> saturate;
BitField<49, 2, HalfMerge> merge;
- BitField<43, 1, u64> negate_a;
BitField<44, 1, u64> abs_a;
BitField<47, 2, HalfType> type_a;
- BitField<31, 1, u64> negate_b;
BitField<30, 1, u64> abs_b;
BitField<28, 2, HalfType> type_b;
@@ -846,7 +857,7 @@ union Instruction {
BitField<56, 1, u64> second_negate;
BitField<30, 9, u64> second;
- u32 PackImmediates() const {
+ [[nodiscard]] u32 PackImmediates() const {
// Immediates are half floats shifted.
constexpr u32 imm_shift = 6;
return static_cast<u32>((first << imm_shift) | (second << (16 + imm_shift)));
@@ -1022,7 +1033,7 @@ union Instruction {
BitField<28, 2, AtomicType> type;
BitField<30, 22, s64> offset;
- s32 GetImmediateOffset() const {
+ [[nodiscard]] s32 GetImmediateOffset() const {
return static_cast<s32>(offset << 2);
}
} atoms;
@@ -1204,7 +1215,7 @@ union Instruction {
BitField<39, 4, u64> rounding;
// H0, H1 extract for F16 missing
BitField<41, 1, u64> selector; // Guessed as some games set it, TODO: reverse this value
- F2fRoundingOp GetRoundingMode() const {
+ [[nodiscard]] F2fRoundingOp GetRoundingMode() const {
constexpr u64 rounding_mask = 0x0B;
return static_cast<F2fRoundingOp>(rounding.Value() & rounding_mask);
}
@@ -1228,15 +1239,15 @@ union Instruction {
BitField<54, 1, u64> aoffi_flag;
BitField<55, 3, TextureProcessMode> process_mode;
- bool IsComponentEnabled(std::size_t component) const {
- return ((1ull << component) & component_mask) != 0;
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1260,15 +1271,15 @@ union Instruction {
BitField<36, 1, u64> aoffi_flag;
BitField<37, 3, TextureProcessMode> process_mode;
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
return ((1ULL << component) & component_mask) != 0;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1288,7 +1299,7 @@ union Instruction {
BitField<31, 4, u64> component_mask;
BitField<49, 1, u64> nodep_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NODEP:
return nodep_flag != 0;
@@ -1298,7 +1309,7 @@ union Instruction {
return false;
}
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
return ((1ULL << component) & component_mask) != 0;
}
} txq;
@@ -1310,11 +1321,11 @@ union Instruction {
BitField<35, 1, u64> ndv_flag;
BitField<49, 1, u64> nodep_flag;
- bool IsComponentEnabled(std::size_t component) const {
- return ((1ull << component) & component_mask) != 0;
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return (ndv_flag != 0);
@@ -1336,7 +1347,7 @@ union Instruction {
BitField<54, 2, u64> offset_mode;
BitField<56, 2, u64> component;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return ndv_flag != 0;
@@ -1362,7 +1373,7 @@ union Instruction {
BitField<33, 2, u64> offset_mode;
BitField<37, 2, u64> component;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return ndv_flag != 0;
@@ -1388,7 +1399,7 @@ union Instruction {
BitField<52, 2, u64> component;
BitField<55, 1, u64> fp16_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1411,16 +1422,20 @@ union Instruction {
BitField<53, 4, u64> texture_info;
BitField<59, 1, u64> fp32_flag;
- TextureType GetTextureType() const {
+ [[nodiscard]] TextureType GetTextureType() const {
// The TEXS instruction has a weird encoding for the texture type.
- if (texture_info == 0)
+ if (texture_info == 0) {
return TextureType::Texture1D;
- if (texture_info >= 1 && texture_info <= 9)
+ }
+ if (texture_info >= 1 && texture_info <= 9) {
return TextureType::Texture2D;
- if (texture_info >= 10 && texture_info <= 11)
+ }
+ if (texture_info >= 10 && texture_info <= 11) {
return TextureType::Texture3D;
- if (texture_info >= 12 && texture_info <= 13)
+ }
+ if (texture_info >= 12 && texture_info <= 13) {
return TextureType::TextureCube;
+ }
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
@@ -1428,7 +1443,7 @@ union Instruction {
return TextureType::Texture1D;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
switch (texture_info) {
case 0:
case 2:
@@ -1447,7 +1462,7 @@ union Instruction {
return TextureProcessMode::None;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return (texture_info >= 4 && texture_info <= 6) || texture_info == 9;
@@ -1459,16 +1474,16 @@ union Instruction {
return false;
}
- bool IsArrayTexture() const {
+ [[nodiscard]] bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info >= 7 && texture_info <= 9;
}
- bool HasTwoDestinations() const {
+ [[nodiscard]] bool HasTwoDestinations() const {
return gpr28.Value() != Register::ZeroIndex;
}
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{
{},
{0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc},
@@ -1495,7 +1510,7 @@ union Instruction {
BitField<54, 1, u64> cl;
BitField<55, 1, u64> process_mode;
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode == 0 ? TextureProcessMode::LZ : TextureProcessMode::LL;
}
} tld;
@@ -1505,9 +1520,9 @@ union Instruction {
BitField<53, 4, u64> texture_info;
BitField<59, 1, u64> fp32_flag;
- TextureType GetTextureType() const {
+ [[nodiscard]] TextureType GetTextureType() const {
// The TLDS instruction has a weird encoding for the texture type.
- if (texture_info >= 0 && texture_info <= 1) {
+ if (texture_info <= 1) {
return TextureType::Texture1D;
}
if (texture_info == 2 || texture_info == 8 || texture_info == 12 ||
@@ -1524,13 +1539,14 @@ union Instruction {
return TextureType::Texture1D;
}
- TextureProcessMode GetTextureProcessMode() const {
- if (texture_info == 1 || texture_info == 5 || texture_info == 12)
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
+ if (texture_info == 1 || texture_info == 5 || texture_info == 12) {
return TextureProcessMode::LL;
+ }
return TextureProcessMode::LZ;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::AOFFI:
return texture_info == 12 || texture_info == 4;
@@ -1544,7 +1560,7 @@ union Instruction {
return false;
}
- bool IsArrayTexture() const {
+ [[nodiscard]] bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info == 8;
}
@@ -1556,7 +1572,7 @@ union Instruction {
BitField<35, 1, u64> aoffi_flag;
BitField<49, 1, u64> nodep_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::AOFFI:
return aoffi_flag != 0;
@@ -1580,7 +1596,7 @@ union Instruction {
BitField<20, 3, StoreType> store_data_layout;
BitField<20, 4, u64> component_mask_selector;
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
ASSERT(mode == SurfaceDataMode::P);
constexpr u8 R = 0b0001;
constexpr u8 G = 0b0010;
@@ -1593,7 +1609,7 @@ union Instruction {
return std::bitset<4>{mask.at(component_mask_selector)}.test(component);
}
- StoreType GetStoreDataLayout() const {
+ [[nodiscard]] StoreType GetStoreDataLayout() const {
ASSERT(mode == SurfaceDataMode::D_BA);
return store_data_layout;
}
@@ -1611,14 +1627,15 @@ union Instruction {
BitField<20, 24, u64> target;
BitField<5, 1, u64> constant_buffer;
- s32 GetBranchTarget() const {
+ [[nodiscard]] s32 GetBranchTarget() const {
// Sign extend the branch target offset
- u32 mask = 1U << (24 - 1);
- u32 value = static_cast<u32>(target);
+ const auto mask = 1U << (24 - 1);
+ const auto target_value = static_cast<u32>(target);
+ constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction));
+
// The branch offset is relative to the next instruction and is stored in bytes, so
// divide it by the size of an instruction and add 1 to it.
- return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) +
- 1;
+ return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1;
}
} bra;
@@ -1626,14 +1643,15 @@ union Instruction {
BitField<20, 24, u64> target;
BitField<5, 1, u64> constant_buffer;
- s32 GetBranchExtend() const {
+ [[nodiscard]] s32 GetBranchExtend() const {
// Sign extend the branch target offset
- u32 mask = 1U << (24 - 1);
- u32 value = static_cast<u32>(target);
+ const auto mask = 1U << (24 - 1);
+ const auto target_value = static_cast<u32>(target);
+ constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction));
+
// The branch offset is relative to the next instruction and is stored in bytes, so
// divide it by the size of an instruction and add 1 to it.
- return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) +
- 1;
+ return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1;
}
} brx;
@@ -1686,7 +1704,7 @@ union Instruction {
BitField<50, 1, u64> is_op_b_register;
BitField<51, 3, VmnmxOperation> operation;
- VmnmxType SourceFormatA() const {
+ [[nodiscard]] VmnmxType SourceFormatA() const {
switch (src_format_a) {
case 0b11:
return VmnmxType::Bits32;
@@ -1697,7 +1715,7 @@ union Instruction {
}
}
- VmnmxType SourceFormatB() const {
+ [[nodiscard]] VmnmxType SourceFormatB() const {
switch (src_format_b) {
case 0b11:
return VmnmxType::Bits32;
@@ -1728,7 +1746,7 @@ union Instruction {
BitField<20, 14, u64> shifted_offset;
BitField<34, 5, u64> index;
- u64 GetOffset() const {
+ [[nodiscard]] u64 GetOffset() const {
return shifted_offset * 4;
}
} cbuf34;
@@ -1737,7 +1755,7 @@ union Instruction {
BitField<20, 16, s64> offset;
BitField<36, 5, u64> index;
- s64 GetOffset() const {
+ [[nodiscard]] s64 GetOffset() const {
return offset;
}
} cbuf36;
@@ -1867,7 +1885,9 @@ public:
HSETP2_C,
HSETP2_R,
HSETP2_IMM,
+ HSET2_C,
HSET2_R,
+ HSET2_IMM,
POPC_C,
POPC_R,
POPC_IMM,
@@ -1880,6 +1900,7 @@ public:
ICMP_IMM,
FCMP_RR,
FCMP_RC,
+ FCMP_IMMR,
MUFU, // Multi-Function Operator
RRO_C, // Range Reduction Operator
RRO_R,
@@ -1983,29 +2004,29 @@ public:
/// Returns whether an opcode has an execution predicate field or not (ie, whether it can be
/// conditionally executed).
- static bool IsPredicatedInstruction(Id opcode) {
+ [[nodiscard]] static bool IsPredicatedInstruction(Id opcode) {
// TODO(Subv): Add the rest of unpredicated instructions.
return opcode != Id::SSY && opcode != Id::PBK;
}
class Matcher {
public:
- constexpr Matcher(const char* const name, u16 mask, u16 expected, Id id, Type type)
- : name{name}, mask{mask}, expected{expected}, id{id}, type{type} {}
+ constexpr Matcher(const char* const name_, u16 mask_, u16 expected_, Id id_, Type type_)
+ : name{name_}, mask{mask_}, expected{expected_}, id{id_}, type{type_} {}
- constexpr const char* GetName() const {
+ [[nodiscard]] constexpr const char* GetName() const {
return name;
}
- constexpr u16 GetMask() const {
+ [[nodiscard]] constexpr u16 GetMask() const {
return mask;
}
- constexpr Id GetId() const {
+ [[nodiscard]] constexpr Id GetId() const {
return id;
}
- constexpr Type GetType() const {
+ [[nodiscard]] constexpr Type GetType() const {
return type;
}
@@ -2014,7 +2035,7 @@ public:
* @param instruction The instruction to test
* @returns true if the given instruction matches.
*/
- constexpr bool Matches(u16 instruction) const {
+ [[nodiscard]] constexpr bool Matches(u16 instruction) const {
return (instruction & mask) == expected;
}
@@ -2026,7 +2047,8 @@ public:
Type type;
};
- static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) {
+ using DecodeResult = std::optional<std::reference_wrapper<const Matcher>>;
+ [[nodiscard]] static DecodeResult Decode(Instruction instr) {
static const auto table{GetDecodeTable()};
const auto matches_instruction = [instr](const auto& matcher) {
@@ -2048,7 +2070,7 @@ private:
* A '0' in a bitstring indicates that a zero must be present at that bit position.
* A '1' in a bitstring indicates that a one must be present at that bit position.
*/
- static constexpr auto GetMaskAndExpect(const char* const bitstring) {
+ [[nodiscard]] static constexpr auto GetMaskAndExpect(const char* const bitstring) {
u16 mask = 0, expect = 0;
for (std::size_t i = 0; i < opcode_bitsize; i++) {
const std::size_t bit_position = opcode_bitsize - i - 1;
@@ -2070,14 +2092,14 @@ private:
public:
/// Creates a matcher that can match and parse instructions based on bitstring.
- static constexpr auto GetMatcher(const char* const bitstring, Id op, Type type,
- const char* const name) {
+ [[nodiscard]] static constexpr auto GetMatcher(const char* const bitstring, Id op,
+ Type type, const char* const name) {
const auto [mask, expected] = GetMaskAndExpect(bitstring);
return Matcher(name, mask, expected, op, type);
}
};
- static std::vector<Matcher> GetDecodeTable() {
+ [[nodiscard]] static std::vector<Matcher> GetDecodeTable() {
std::vector<Matcher> table = {
#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
@@ -2187,9 +2209,12 @@ private:
INST("0111111-1-------", Id::HSETP2_C, Type::HalfSetPredicate, "HSETP2_C"),
INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP2_R"),
INST("0111111-0-------", Id::HSETP2_IMM, Type::HalfSetPredicate, "HSETP2_IMM"),
+ INST("0111110-1-------", Id::HSET2_C, Type::HalfSet, "HSET2_C"),
INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"),
+ INST("0111110-0-------", Id::HSET2_IMM, Type::HalfSet, "HSET2_IMM"),
INST("010110111010----", Id::FCMP_RR, Type::Arithmetic, "FCMP_RR"),
INST("010010111010----", Id::FCMP_RC, Type::Arithmetic, "FCMP_RC"),
+ INST("0011011-1010----", Id::FCMP_IMMR, Type::Arithmetic, "FCMP_IMMR"),
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h
index 72e2a33d5..ceec05459 100644
--- a/src/video_core/engines/shader_header.h
+++ b/src/video_core/engines/shader_header.h
@@ -41,30 +41,30 @@ struct Header {
BitField<26, 1, u32> does_load_or_store;
BitField<27, 1, u32> does_fp64;
BitField<28, 4, u32> stream_out_mask;
- } common0{};
+ } common0;
union {
BitField<0, 24, u32> shader_local_memory_low_size;
BitField<24, 8, u32> per_patch_attribute_count;
- } common1{};
+ } common1;
union {
BitField<0, 24, u32> shader_local_memory_high_size;
BitField<24, 8, u32> threads_per_input_primitive;
- } common2{};
+ } common2;
union {
BitField<0, 24, u32> shader_local_memory_crs_size;
BitField<24, 4, OutputTopology> output_topology;
BitField<28, 4, u32> reserved;
- } common3{};
+ } common3;
union {
BitField<0, 12, u32> max_output_vertices;
BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders.
BitField<20, 4, u32> reserved;
BitField<24, 8, u32> store_req_end; // NOTE: not used by geometry shaders.
- } common4{};
+ } common4;
union {
struct {
@@ -145,7 +145,7 @@ struct Header {
}
} ps;
- std::array<u32, 0xF> raw{};
+ std::array<u32, 0xF> raw;
};
u64 GetLocalMemorySize() const {
@@ -153,7 +153,6 @@ struct Header {
(common2.shader_local_memory_high_size << 24));
}
};
-
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");
} // namespace Tegra::Shader
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
new file mode 100644
index 000000000..de6991ef6
--- /dev/null
+++ b/src/video_core/fence_manager.h
@@ -0,0 +1,164 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <queue>
+
+#include "common/common_types.h"
+#include "core/core.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace VideoCommon {
+
+class FenceBase {
+public:
+ FenceBase(u32 payload, bool is_stubbed)
+ : address{}, payload{payload}, is_semaphore{false}, is_stubbed{is_stubbed} {}
+
+ FenceBase(GPUVAddr address, u32 payload, bool is_stubbed)
+ : address{address}, payload{payload}, is_semaphore{true}, is_stubbed{is_stubbed} {}
+
+ GPUVAddr GetAddress() const {
+ return address;
+ }
+
+ u32 GetPayload() const {
+ return payload;
+ }
+
+ bool IsSemaphore() const {
+ return is_semaphore;
+ }
+
+private:
+ GPUVAddr address;
+ u32 payload;
+ bool is_semaphore;
+
+protected:
+ bool is_stubbed;
+};
+
+template <typename TFence, typename TTextureCache, typename TTBufferCache, typename TQueryCache>
+class FenceManager {
+public:
+ void SignalSemaphore(GPUVAddr addr, u32 value) {
+ TryReleasePendingFences();
+ const bool should_flush = ShouldFlush();
+ CommitAsyncFlushes();
+ TFence new_fence = CreateFence(addr, value, !should_flush);
+ fences.push(new_fence);
+ QueueFence(new_fence);
+ if (should_flush) {
+ rasterizer.FlushCommands();
+ }
+ rasterizer.SyncGuestHost();
+ }
+
+ void SignalSyncPoint(u32 value) {
+ TryReleasePendingFences();
+ const bool should_flush = ShouldFlush();
+ CommitAsyncFlushes();
+ TFence new_fence = CreateFence(value, !should_flush);
+ fences.push(new_fence);
+ QueueFence(new_fence);
+ if (should_flush) {
+ rasterizer.FlushCommands();
+ }
+ rasterizer.SyncGuestHost();
+ }
+
+ void WaitPendingFences() {
+ while (!fences.empty()) {
+ TFence& current_fence = fences.front();
+ if (ShouldWait()) {
+ WaitFence(current_fence);
+ }
+ PopAsyncFlushes();
+ if (current_fence->IsSemaphore()) {
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
+ } else {
+ gpu.IncrementSyncPoint(current_fence->GetPayload());
+ }
+ fences.pop();
+ }
+ }
+
+protected:
+ explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
+ TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
+ TQueryCache& query_cache_)
+ : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
+ texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
+
+ virtual ~FenceManager() = default;
+
+ /// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
+ /// true
+ virtual TFence CreateFence(u32 value, bool is_stubbed) = 0;
+ /// Creates a Semaphore Fence Interface, does not create a backend fence if 'is_stubbed' is true
+ virtual TFence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) = 0;
+ /// Queues a fence into the backend if the fence isn't stubbed.
+ virtual void QueueFence(TFence& fence) = 0;
+ /// Notifies that the backend fence has been signaled/reached in host GPU.
+ virtual bool IsFenceSignaled(TFence& fence) const = 0;
+ /// Waits until a fence has been signalled by the host GPU.
+ virtual void WaitFence(TFence& fence) = 0;
+
+ VideoCore::RasterizerInterface& rasterizer;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ TTextureCache& texture_cache;
+ TTBufferCache& buffer_cache;
+ TQueryCache& query_cache;
+
+private:
+ void TryReleasePendingFences() {
+ while (!fences.empty()) {
+ TFence& current_fence = fences.front();
+ if (ShouldWait() && !IsFenceSignaled(current_fence)) {
+ return;
+ }
+ PopAsyncFlushes();
+ if (current_fence->IsSemaphore()) {
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
+ } else {
+ gpu.IncrementSyncPoint(current_fence->GetPayload());
+ }
+ fences.pop();
+ }
+ }
+
+ bool ShouldWait() const {
+ return texture_cache.ShouldWaitAsyncFlushes() || buffer_cache.ShouldWaitAsyncFlushes() ||
+ query_cache.ShouldWaitAsyncFlushes();
+ }
+
+ bool ShouldFlush() const {
+ return texture_cache.HasUncommittedFlushes() || buffer_cache.HasUncommittedFlushes() ||
+ query_cache.HasUncommittedFlushes();
+ }
+
+ void PopAsyncFlushes() {
+ texture_cache.PopAsyncFlushes();
+ buffer_cache.PopAsyncFlushes();
+ query_cache.PopAsyncFlushes();
+ }
+
+ void CommitAsyncFlushes() {
+ texture_cache.CommitAsyncFlushes();
+ buffer_cache.CommitAsyncFlushes();
+ query_cache.CommitAsyncFlushes();
+ }
+
+ std::queue<TFence> fences;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 8acf2eda2..ebd149c3a 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <chrono>
+
#include "common/assert.h"
#include "common/microprofile.h"
#include "core/core.h"
@@ -9,6 +11,7 @@
#include "core/core_timing_util.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
+#include "core/settings.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/kepler_memory.h"
@@ -17,26 +20,36 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
+#include "video_core/shader_notify.h"
#include "video_core/video_core.h"
namespace Tegra {
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
-GPU::GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_, bool is_async)
- : system{system}, renderer{std::move(renderer_)}, is_async{is_async} {
- auto& rasterizer{renderer->Rasterizer()};
- memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer);
- dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
- maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
- fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer);
- kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager);
- maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
- kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
-}
+GPU::GPU(Core::System& system_, bool is_async_, bool use_nvdec_)
+ : system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
+ dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
+ cdma_pusher{std::make_unique<Tegra::CDmaPusher>(*this)}, use_nvdec{use_nvdec_},
+ maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
+ fermi_2d{std::make_unique<Engines::Fermi2D>()},
+ kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
+ maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
+ kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
+ shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {}
GPU::~GPU() = default;
+void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
+ renderer = std::move(renderer_);
+
+ VideoCore::RasterizerInterface& rasterizer = renderer->Rasterizer();
+ memory_manager->BindRasterizer(rasterizer);
+ maxwell_3d->BindRasterizer(rasterizer);
+ fermi_2d->BindRasterizer(rasterizer);
+ kepler_compute->BindRasterizer(rasterizer);
+}
+
Engines::Maxwell3D& GPU::Maxwell3D() {
return *maxwell_3d;
}
@@ -65,10 +78,18 @@ DmaPusher& GPU::DmaPusher() {
return *dma_pusher;
}
+Tegra::CDmaPusher& GPU::CDmaPusher() {
+ return *cdma_pusher;
+}
+
const DmaPusher& GPU::DmaPusher() const {
return *dma_pusher;
}
+const Tegra::CDmaPusher& GPU::CDmaPusher() const {
+ return *cdma_pusher;
+}
+
void GPU::WaitFence(u32 syncpoint_id, u32 value) {
// Synced GPU, is always in sync
if (!is_async) {
@@ -76,7 +97,7 @@ void GPU::WaitFence(u32 syncpoint_id, u32 value) {
}
MICROPROFILE_SCOPE(GPU_wait);
std::unique_lock lock{sync_mutex};
- sync_cv.wait(lock, [=]() { return syncpoints[syncpoint_id].load() >= value; });
+ sync_cv.wait(lock, [=, this] { return syncpoints[syncpoint_id].load() >= value; });
}
void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
@@ -125,14 +146,38 @@ bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
return true;
}
+u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
+ std::unique_lock lck{flush_request_mutex};
+ const u64 fence = ++last_flush_fence;
+ flush_requests.emplace_back(fence, addr, size);
+ return fence;
+}
+
+void GPU::TickWork() {
+ std::unique_lock lck{flush_request_mutex};
+ while (!flush_requests.empty()) {
+ auto& request = flush_requests.front();
+ const u64 fence = request.fence;
+ const VAddr addr = request.addr;
+ const std::size_t size = request.size;
+ flush_requests.pop_front();
+ flush_request_mutex.unlock();
+ renderer->Rasterizer().FlushRegion(addr, size);
+ current_flush_fence.store(fence);
+ flush_request_mutex.lock();
+ }
+}
+
u64 GPU::GetTicks() const {
// This values were reversed engineered by fincs from NVN
// The gpu clock is reported in units of 385/625 nanoseconds
constexpr u64 gpu_ticks_num = 384;
constexpr u64 gpu_ticks_den = 625;
- const u64 cpu_ticks = system.CoreTiming().GetTicks();
- const u64 nanoseconds = Core::Timing::CyclesToNs(cpu_ticks).count();
+ u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count();
+ if (Settings::values.use_fast_gpu_time.GetValue()) {
+ nanoseconds /= 256;
+ }
const u64 nanoseconds_num = nanoseconds / gpu_ticks_den;
const u64 nanoseconds_rem = nanoseconds % gpu_ticks_den;
return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den;
@@ -142,30 +187,13 @@ void GPU::FlushCommands() {
renderer->Rasterizer().FlushCommands();
}
-// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
-// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
-// So the values you see in docs might be multiplied by 4.
-enum class BufferMethods {
- BindObject = 0x0,
- Nop = 0x2,
- SemaphoreAddressHigh = 0x4,
- SemaphoreAddressLow = 0x5,
- SemaphoreSequence = 0x6,
- SemaphoreTrigger = 0x7,
- NotifyIntr = 0x8,
- WrcacheFlush = 0x9,
- Unk28 = 0xA,
- UnkCacheFlush = 0xB,
- RefCnt = 0x14,
- SemaphoreAcquire = 0x1A,
- SemaphoreRelease = 0x1B,
- FenceValue = 0x1C,
- FenceAction = 0x1D,
- Unk78 = 0x1E,
- Unk7c = 0x1F,
- Yield = 0x20,
- NonPullerMethods = 0x40,
-};
+void GPU::SyncGuestHost() {
+ renderer->Rasterizer().SyncGuestHost();
+}
+
+void GPU::OnCommandListEnd() {
+ renderer->Rasterizer().ReleaseFences();
+}
enum class GpuSemaphoreOperation {
AcquireEqual = 0x1,
@@ -180,16 +208,32 @@ void GPU::CallMethod(const MethodCall& method_call) {
ASSERT(method_call.subchannel < bound_engines.size());
- if (ExecuteMethodOnEngine(method_call)) {
+ if (ExecuteMethodOnEngine(method_call.method)) {
CallEngineMethod(method_call);
} else {
CallPullerMethod(method_call);
}
}
-bool GPU::ExecuteMethodOnEngine(const MethodCall& method_call) {
- const auto method = static_cast<BufferMethods>(method_call.method);
- return method >= BufferMethods::NonPullerMethods;
+void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
+
+ ASSERT(subchannel < bound_engines.size());
+
+ if (ExecuteMethodOnEngine(method)) {
+ CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
+ } else {
+ for (std::size_t i = 0; i < amount; i++) {
+ CallPullerMethod(
+ {method, base_start[i], subchannel, methods_pending - static_cast<u32>(i)});
+ }
+ }
+}
+
+bool GPU::ExecuteMethodOnEngine(u32 method) {
+ const auto buffer_method = static_cast<BufferMethods>(method);
+ return buffer_method >= BufferMethods::NonPullerMethods;
}
void GPU::CallPullerMethod(const MethodCall& method_call) {
@@ -209,7 +253,12 @@ void GPU::CallPullerMethod(const MethodCall& method_call) {
case BufferMethods::UnkCacheFlush:
case BufferMethods::WrcacheFlush:
case BufferMethods::FenceValue:
+ break;
case BufferMethods::FenceAction:
+ ProcessFenceActionMethod();
+ break;
+ case BufferMethods::WaitForInterrupt:
+ ProcessWaitForInterruptMethod();
break;
case BufferMethods::SemaphoreTrigger: {
ProcessSemaphoreTriggerMethod();
@@ -250,19 +299,46 @@ void GPU::CallEngineMethod(const MethodCall& method_call) {
switch (engine) {
case EngineID::FERMI_TWOD_A:
- fermi_2d->CallMethod(method_call);
+ fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
+ break;
+ case EngineID::MAXWELL_B:
+ maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
+ break;
+ case EngineID::KEPLER_COMPUTE_B:
+ kepler_compute->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
+ break;
+ case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+ kepler_memory->CallMethod(method_call.method, method_call.argument,
+ method_call.IsLastCall());
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented engine");
+ }
+}
+
+void GPU::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending) {
+ const EngineID engine = bound_engines[subchannel];
+
+ switch (engine) {
+ case EngineID::FERMI_TWOD_A:
+ fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
break;
case EngineID::MAXWELL_B:
- maxwell_3d->CallMethod(method_call);
+ maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
break;
case EngineID::KEPLER_COMPUTE_B:
- kepler_compute->CallMethod(method_call);
+ kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
break;
case EngineID::MAXWELL_DMA_COPY_A:
- maxwell_dma->CallMethod(method_call);
+ maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
- kepler_memory->CallMethod(method_call);
+ kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented engine");
@@ -273,7 +349,46 @@ void GPU::ProcessBindMethod(const MethodCall& method_call) {
// Bind the current subchannel to the desired engine id.
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
method_call.argument);
- bound_engines[method_call.subchannel] = static_cast<EngineID>(method_call.argument);
+ const auto engine_id = static_cast<EngineID>(method_call.argument);
+ bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
+ switch (engine_id) {
+ case EngineID::FERMI_TWOD_A:
+ dma_pusher->BindSubchannel(fermi_2d.get(), method_call.subchannel);
+ break;
+ case EngineID::MAXWELL_B:
+ dma_pusher->BindSubchannel(maxwell_3d.get(), method_call.subchannel);
+ break;
+ case EngineID::KEPLER_COMPUTE_B:
+ dma_pusher->BindSubchannel(kepler_compute.get(), method_call.subchannel);
+ break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ dma_pusher->BindSubchannel(maxwell_dma.get(), method_call.subchannel);
+ break;
+ case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+ dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", static_cast<u32>(engine_id));
+ }
+}
+
+void GPU::ProcessFenceActionMethod() {
+ switch (regs.fence_action.op) {
+ case FenceOperation::Acquire:
+ WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
+ break;
+ case FenceOperation::Increment:
+ IncrementSyncPoint(regs.fence_action.syncpoint_id);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented operation {}",
+ static_cast<u32>(regs.fence_action.op.Value()));
+ }
+}
+
+void GPU::ProcessWaitForInterruptMethod() {
+ // TODO(bunnei) ImplementMe
+ LOG_WARNING(HW_GPU, "(STUBBED) called");
}
void GPU::ProcessSemaphoreTriggerMethod() {
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 1a2d747be..21410e125 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -13,14 +13,15 @@
#include "common/common_types.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "video_core/cdma_pusher.h"
#include "video_core/dma_pusher.h"
using CacheAddr = std::uintptr_t;
-inline CacheAddr ToCacheAddr(const void* host_ptr) {
+[[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) {
return reinterpret_cast<CacheAddr>(host_ptr);
}
-inline u8* FromCacheAddr(CacheAddr cache_addr) {
+[[nodiscard]] inline u8* FromCacheAddr(CacheAddr cache_addr) {
return reinterpret_cast<u8*>(cache_addr);
}
@@ -33,58 +34,68 @@ class System;
namespace VideoCore {
class RendererBase;
+class ShaderNotify;
} // namespace VideoCore
namespace Tegra {
enum class RenderTargetFormat : u32 {
NONE = 0x0,
- RGBA32_FLOAT = 0xC0,
- RGBA32_UINT = 0xC2,
- RGBA16_UNORM = 0xC6,
- RGBA16_SNORM = 0xC7,
- RGBA16_UINT = 0xC9,
- RGBA16_FLOAT = 0xCA,
- RG32_FLOAT = 0xCB,
- RG32_UINT = 0xCD,
- RGBX16_FLOAT = 0xCE,
- BGRA8_UNORM = 0xCF,
- BGRA8_SRGB = 0xD0,
- RGB10_A2_UNORM = 0xD1,
- RGBA8_UNORM = 0xD5,
- RGBA8_SRGB = 0xD6,
- RGBA8_SNORM = 0xD7,
- RGBA8_UINT = 0xD9,
- RG16_UNORM = 0xDA,
- RG16_SNORM = 0xDB,
- RG16_SINT = 0xDC,
- RG16_UINT = 0xDD,
- RG16_FLOAT = 0xDE,
- R11G11B10_FLOAT = 0xE0,
+ R32B32G32A32_FLOAT = 0xC0,
+ R32G32B32A32_SINT = 0xC1,
+ R32G32B32A32_UINT = 0xC2,
+ R16G16B16A16_UNORM = 0xC6,
+ R16G16B16A16_SNORM = 0xC7,
+ R16G16B16A16_SINT = 0xC8,
+ R16G16B16A16_UINT = 0xC9,
+ R16G16B16A16_FLOAT = 0xCA,
+ R32G32_FLOAT = 0xCB,
+ R32G32_SINT = 0xCC,
+ R32G32_UINT = 0xCD,
+ R16G16B16X16_FLOAT = 0xCE,
+ B8G8R8A8_UNORM = 0xCF,
+ B8G8R8A8_SRGB = 0xD0,
+ A2B10G10R10_UNORM = 0xD1,
+ A2B10G10R10_UINT = 0xD2,
+ A8B8G8R8_UNORM = 0xD5,
+ A8B8G8R8_SRGB = 0xD6,
+ A8B8G8R8_SNORM = 0xD7,
+ A8B8G8R8_SINT = 0xD8,
+ A8B8G8R8_UINT = 0xD9,
+ R16G16_UNORM = 0xDA,
+ R16G16_SNORM = 0xDB,
+ R16G16_SINT = 0xDC,
+ R16G16_UINT = 0xDD,
+ R16G16_FLOAT = 0xDE,
+ B10G11R11_FLOAT = 0xE0,
R32_SINT = 0xE3,
R32_UINT = 0xE4,
R32_FLOAT = 0xE5,
- B5G6R5_UNORM = 0xE8,
- BGR5A1_UNORM = 0xE9,
- RG8_UNORM = 0xEA,
- RG8_SNORM = 0xEB,
+ R5G6B5_UNORM = 0xE8,
+ A1R5G5B5_UNORM = 0xE9,
+ R8G8_UNORM = 0xEA,
+ R8G8_SNORM = 0xEB,
+ R8G8_SINT = 0xEC,
+ R8G8_UINT = 0xED,
R16_UNORM = 0xEE,
R16_SNORM = 0xEF,
R16_SINT = 0xF0,
R16_UINT = 0xF1,
R16_FLOAT = 0xF2,
R8_UNORM = 0xF3,
+ R8_SNORM = 0xF4,
+ R8_SINT = 0xF5,
R8_UINT = 0xF6,
};
enum class DepthFormat : u32 {
- Z32_FLOAT = 0xA,
- Z16_UNORM = 0x13,
- S8_Z24_UNORM = 0x14,
- Z24_X8_UNORM = 0x15,
- Z24_S8_UNORM = 0x16,
- Z24_C8_UNORM = 0x18,
- Z32_S8_X24_FLOAT = 0x19,
+ D32_FLOAT = 0xA,
+ D16_UNORM = 0x13,
+ S8_UINT_Z24_UNORM = 0x14,
+ D24X8_UNORM = 0x15,
+ D24S8_UNORM = 0x16,
+ D24C8_UNORM = 0x18,
+ D32_FLOAT_S8X24_UINT = 0x19,
};
struct CommandListHeader;
@@ -95,9 +106,9 @@ class DebugContext;
*/
struct FramebufferConfig {
enum class PixelFormat : u32 {
- ABGR8 = 1,
- RGB565 = 4,
- BGRA8 = 5,
+ A8B8G8R8_UNORM = 1,
+ RGB565_UNORM = 4,
+ B8G8R8A8_UNORM = 5,
};
VAddr address;
@@ -132,60 +143,102 @@ class MemoryManager;
class GPU {
public:
- explicit GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- bool is_async);
-
- virtual ~GPU();
-
struct MethodCall {
u32 method{};
u32 argument{};
u32 subchannel{};
u32 method_count{};
- bool IsLastCall() const {
- return method_count <= 1;
- }
-
MethodCall(u32 method, u32 argument, u32 subchannel = 0, u32 method_count = 0)
: method(method), argument(argument), subchannel(subchannel),
method_count(method_count) {}
+
+ [[nodiscard]] bool IsLastCall() const {
+ return method_count <= 1;
+ }
};
+ explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
+ virtual ~GPU();
+
+ /// Binds a renderer to the GPU.
+ void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
+
/// Calls a GPU method.
void CallMethod(const MethodCall& method_call);
+ /// Calls a GPU multivalue method.
+ void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending);
+
+ /// Flush all current written commands into the host GPU for execution.
void FlushCommands();
+ /// Synchronizes CPU writes with Host GPU memory.
+ void SyncGuestHost();
+ /// Signal the ending of command list.
+ virtual void OnCommandListEnd();
+
+ /// Request a host GPU memory flush from the CPU.
+ [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
+
+ /// Obtains current flush request fence id.
+ [[nodiscard]] u64 CurrentFlushRequestFence() const {
+ return current_flush_fence.load(std::memory_order_relaxed);
+ }
+
+ /// Tick pending requests within the GPU.
+ void TickWork();
/// Returns a reference to the Maxwell3D GPU engine.
- Engines::Maxwell3D& Maxwell3D();
+ [[nodiscard]] Engines::Maxwell3D& Maxwell3D();
/// Returns a const reference to the Maxwell3D GPU engine.
- const Engines::Maxwell3D& Maxwell3D() const;
+ [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const;
/// Returns a reference to the KeplerCompute GPU engine.
- Engines::KeplerCompute& KeplerCompute();
+ [[nodiscard]] Engines::KeplerCompute& KeplerCompute();
/// Returns a reference to the KeplerCompute GPU engine.
- const Engines::KeplerCompute& KeplerCompute() const;
+ [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
/// Returns a reference to the GPU memory manager.
- Tegra::MemoryManager& MemoryManager();
+ [[nodiscard]] Tegra::MemoryManager& MemoryManager();
/// Returns a const reference to the GPU memory manager.
- const Tegra::MemoryManager& MemoryManager() const;
+ [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const;
/// Returns a reference to the GPU DMA pusher.
- Tegra::DmaPusher& DmaPusher();
+ [[nodiscard]] Tegra::DmaPusher& DmaPusher();
- VideoCore::RendererBase& Renderer() {
+ /// Returns a const reference to the GPU DMA pusher.
+ [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const;
+
+ /// Returns a reference to the GPU CDMA pusher.
+ [[nodiscard]] Tegra::CDmaPusher& CDmaPusher();
+
+ /// Returns a const reference to the GPU CDMA pusher.
+ [[nodiscard]] const Tegra::CDmaPusher& CDmaPusher() const;
+
+ /// Returns a reference to the underlying renderer.
+ [[nodiscard]] VideoCore::RendererBase& Renderer() {
return *renderer;
}
- const VideoCore::RendererBase& Renderer() const {
+ /// Returns a const reference to the underlying renderer.
+ [[nodiscard]] const VideoCore::RendererBase& Renderer() const {
return *renderer;
}
+ /// Returns a reference to the shader notifier.
+ [[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() {
+ return *shader_notify;
+ }
+
+ /// Returns a const reference to the shader notifier.
+ [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const {
+ return *shader_notify;
+ }
+
// Waits for the GPU to finish working
virtual void WaitIdle() const = 0;
@@ -194,27 +247,46 @@ public:
void IncrementSyncPoint(u32 syncpoint_id);
- u32 GetSyncpointValue(u32 syncpoint_id) const;
+ [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const;
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
- bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
+ [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
- u64 GetTicks() const;
+ [[nodiscard]] u64 GetTicks() const;
- std::unique_lock<std::mutex> LockSync() {
+ [[nodiscard]] std::unique_lock<std::mutex> LockSync() {
return std::unique_lock{sync_mutex};
}
- bool IsAsync() const {
+ [[nodiscard]] bool IsAsync() const {
return is_async;
}
- /// Returns a const reference to the GPU DMA pusher.
- const Tegra::DmaPusher& DmaPusher() const;
+ [[nodiscard]] bool UseNvdec() const {
+ return use_nvdec;
+ }
+
+ enum class FenceOperation : u32 {
+ Acquire = 0,
+ Increment = 1,
+ };
+
+ union FenceAction {
+ u32 raw;
+ BitField<0, 1, FenceOperation> op;
+ BitField<8, 24, u32> syncpoint_id;
+
+ [[nodiscard]] static CommandHeader Build(FenceOperation op, u32 syncpoint_id) {
+ FenceAction result{};
+ result.op.Assign(op);
+ result.syncpoint_id.Assign(syncpoint_id);
+ return {result.raw};
+ }
+ };
struct Regs {
- static constexpr size_t NUM_REGS = 0x100;
+ static constexpr size_t NUM_REGS = 0x40;
union {
struct {
@@ -223,7 +295,7 @@ public:
u32 address_high;
u32 address_low;
- GPUVAddr SemaphoreAddress() const {
+ [[nodiscard]] GPUVAddr SemaphoreAddress() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
address_low);
}
@@ -233,7 +305,7 @@ public:
u32 semaphore_trigger;
INSERT_UNION_PADDING_WORDS(0xC);
- // The puser and the puller share the reference counter, the pusher only has read
+ // The pusher and the puller share the reference counter, the pusher only has read
// access
u32 reference_count;
INSERT_UNION_PADDING_WORDS(0x5);
@@ -241,10 +313,7 @@ public:
u32 semaphore_acquire;
u32 semaphore_release;
u32 fence_value;
- union {
- BitField<4, 4, u32> operation;
- BitField<8, 8, u32> id;
- } fence_action;
+ FenceAction fence_action;
INSERT_UNION_PADDING_WORDS(0xE2);
// Puller state
@@ -263,9 +332,18 @@ public:
/// core timing events.
virtual void Start() = 0;
+ /// Obtain the CPU Context
+ virtual void ObtainContext() = 0;
+
+ /// Release the CPU Context
+ virtual void ReleaseContext() = 0;
+
/// Push GPU command entries to be processed
virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0;
+ /// Push GPU command buffer entries to be processed
+ virtual void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) = 0;
+
/// Swap buffers (render frame)
virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
@@ -283,6 +361,8 @@ protected:
private:
void ProcessBindMethod(const MethodCall& method_call);
+ void ProcessFenceActionMethod();
+ void ProcessWaitForInterruptMethod();
void ProcessSemaphoreTriggerMethod();
void ProcessSemaphoreRelease();
void ProcessSemaphoreAcquire();
@@ -293,17 +373,22 @@ private:
/// Calls a GPU engine method.
void CallEngineMethod(const MethodCall& method_call);
+ /// Calls a GPU engine multivalue method.
+ void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+ u32 methods_pending);
+
/// Determines where the method should be executed.
- bool ExecuteMethodOnEngine(const MethodCall& method_call);
+ [[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
protected:
- std::unique_ptr<Tegra::DmaPusher> dma_pusher;
Core::System& system;
+ std::unique_ptr<Tegra::MemoryManager> memory_manager;
+ std::unique_ptr<Tegra::DmaPusher> dma_pusher;
+ std::unique_ptr<Tegra::CDmaPusher> cdma_pusher;
std::unique_ptr<VideoCore::RendererBase> renderer;
+ const bool use_nvdec;
private:
- std::unique_ptr<Tegra::MemoryManager> memory_manager;
-
/// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines = {};
/// 3D engine
@@ -316,15 +401,31 @@ private:
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
/// Inline memory engine
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
+ /// Shader build notifier
+ std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
std::array<std::atomic<u32>, Service::Nvidia::MaxSyncPoints> syncpoints{};
std::array<std::list<u32>, Service::Nvidia::MaxSyncPoints> syncpt_interrupts;
std::mutex sync_mutex;
+ std::mutex device_mutex;
std::condition_variable sync_cv;
+ struct FlushRequest {
+ FlushRequest(u64 fence, VAddr addr, std::size_t size)
+ : fence{fence}, addr{addr}, size{size} {}
+ u64 fence;
+ VAddr addr;
+ std::size_t size;
+ };
+
+ std::list<FlushRequest> flush_requests;
+ std::atomic<u64> current_flush_fence{};
+ u64 last_flush_fence{};
+ std::mutex flush_request_mutex;
+
const bool is_async;
};
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index 20e73a37e..a9baaf7ef 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -10,23 +10,50 @@
namespace VideoCommon {
-GPUAsynch::GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context)
- : GPU(system, std::move(renderer_), true), gpu_thread{system},
- cpu_context(renderer->GetRenderWindow().CreateSharedContext()),
- gpu_context(std::move(context)) {}
+GPUAsynch::GPUAsynch(Core::System& system, bool use_nvdec)
+ : GPU{system, true, use_nvdec}, gpu_thread{system} {}
GPUAsynch::~GPUAsynch() = default;
void GPUAsynch::Start() {
+ gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher, *cdma_pusher);
+ cpu_context = renderer->GetRenderWindow().CreateSharedContext();
cpu_context->MakeCurrent();
- gpu_thread.StartThread(*renderer, *gpu_context, *dma_pusher);
+}
+
+void GPUAsynch::ObtainContext() {
+ cpu_context->MakeCurrent();
+}
+
+void GPUAsynch::ReleaseContext() {
+ cpu_context->DoneCurrent();
}
void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
gpu_thread.SubmitList(std::move(entries));
}
+void GPUAsynch::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
+ if (!use_nvdec) {
+ return;
+ }
+ // This condition fires when a video stream ends, clear all intermediary data
+ if (entries[0].raw == 0xDEADB33F) {
+ cdma_pusher.reset();
+ return;
+ }
+ if (!cdma_pusher) {
+ cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
+ }
+
+ // SubmitCommandBuffer would make the nvdec operations async, this is not currently working
+ // TODO(ameerj): RE proper async nvdec operation
+ // gpu_thread.SubmitCommandBuffer(std::move(entries));
+
+ cdma_pusher->Push(std::move(entries));
+ cdma_pusher->DispatchCalls();
+}
+
void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
gpu_thread.SwapBuffers(framebuffer);
}
@@ -52,4 +79,8 @@ void GPUAsynch::WaitIdle() const {
gpu_thread.WaitIdle();
}
+void GPUAsynch::OnCommandListEnd() {
+ gpu_thread.OnCommandListEnd();
+}
+
} // namespace VideoCommon
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index 03fd0eef0..0c0872e73 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -20,25 +20,28 @@ namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU asynchronously
class GPUAsynch final : public Tegra::GPU {
public:
- explicit GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context);
+ explicit GPUAsynch(Core::System& system, bool use_nvdec);
~GPUAsynch() override;
void Start() override;
+ void ObtainContext() override;
+ void ReleaseContext() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
+ void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
void WaitIdle() const override;
+ void OnCommandListEnd() override;
+
protected:
void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override;
private:
GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
- std::unique_ptr<Core::Frontend::GraphicsContext> gpu_context;
};
} // namespace VideoCommon
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index 6f38a672a..ecf7bbdf3 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -7,14 +7,18 @@
namespace VideoCommon {
-GPUSynch::GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context)
- : GPU(system, std::move(renderer), false), context{std::move(context)} {}
+GPUSynch::GPUSynch(Core::System& system, bool use_nvdec) : GPU{system, false, use_nvdec} {}
GPUSynch::~GPUSynch() = default;
-void GPUSynch::Start() {
- context->MakeCurrent();
+void GPUSynch::Start() {}
+
+void GPUSynch::ObtainContext() {
+ renderer->Context().MakeCurrent();
+}
+
+void GPUSynch::ReleaseContext() {
+ renderer->Context().DoneCurrent();
}
void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
@@ -22,6 +26,22 @@ void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
dma_pusher->DispatchCalls();
}
+void GPUSynch::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
+ if (!use_nvdec) {
+ return;
+ }
+ // This condition fires when a video stream ends, clears all intermediary data
+ if (entries[0].raw == 0xDEADB33F) {
+ cdma_pusher.reset();
+ return;
+ }
+ if (!cdma_pusher) {
+ cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
+ }
+ cdma_pusher->Push(std::move(entries));
+ cdma_pusher->DispatchCalls();
+}
+
void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
renderer->SwapBuffers(framebuffer);
}
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 4a6e9a01d..9d778c71a 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -19,12 +19,14 @@ namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU synchronously
class GPUSynch final : public Tegra::GPU {
public:
- explicit GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context);
+ explicit GPUSynch(Core::System& system, bool use_nvdec);
~GPUSynch() override;
void Start() override;
+ void ObtainContext() override;
+ void ReleaseContext() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
+ void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
@@ -34,9 +36,6 @@ public:
protected:
void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
[[maybe_unused]] u32 value) const override {}
-
-private:
- std::unique_ptr<Core::Frontend::GraphicsContext> context;
};
} // namespace VideoCommon
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 10cda686b..4b8f58283 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -4,8 +4,10 @@
#include "common/assert.h"
#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
+#include "core/settings.h"
#include "video_core/dma_pusher.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
@@ -14,9 +16,14 @@
namespace VideoCommon::GPUThread {
/// Runs the GPU thread
-static void RunThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher, SynchState& state) {
- MicroProfileOnThreadCreate("GpuThread");
+static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
+ Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher,
+ SynchState& state, Tegra::CDmaPusher& cdma_pusher) {
+ std::string name = "yuzu:GPU";
+ MicroProfileOnThreadCreate(name.c_str());
+ Common::SetCurrentThreadName(name.c_str());
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ system.RegisterHostThread();
// Wait for first GPU command before acquiring the window context
while (state.queue.Empty())
@@ -35,12 +42,20 @@ static void RunThread(VideoCore::RendererBase& renderer, Core::Frontend::Graphic
if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
dma_pusher.Push(std::move(submit_list->entries));
dma_pusher.DispatchCalls();
+ } else if (const auto command_list = std::get_if<SubmitChCommandEntries>(&next.data)) {
+ // NVDEC
+ cdma_pusher.Push(std::move(command_list->entries));
+ cdma_pusher.DispatchCalls();
} else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
+ } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
+ renderer.Rasterizer().ReleaseFences();
+ } else if (std::holds_alternative<GPUTickCommand>(next.data)) {
+ system.GPU().TickWork();
} else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
renderer.Rasterizer().FlushRegion(data->addr, data->size);
} else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
- renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
+ renderer.Rasterizer().OnCPUWrite(data->addr, data->size);
} else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
return;
} else {
@@ -64,30 +79,47 @@ ThreadManager::~ThreadManager() {
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher) {
- thread = std::thread{RunThread, std::ref(renderer), std::ref(context), std::ref(dma_pusher),
- std::ref(state)};
+ Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher) {
+ thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
+ std::ref(dma_pusher), std::ref(state), std::ref(cdma_pusher));
}
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
PushCommand(SubmitListCommand(std::move(entries)));
}
+void ThreadManager::SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries) {
+ PushCommand(SubmitChCommandEntries(std::move(entries)));
+}
+
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
PushCommand(SwapBuffersCommand(framebuffer ? std::make_optional(*framebuffer) : std::nullopt));
}
void ThreadManager::FlushRegion(VAddr addr, u64 size) {
- PushCommand(FlushRegionCommand(addr, size));
+ if (!Settings::IsGPULevelHigh()) {
+ PushCommand(FlushRegionCommand(addr, size));
+ return;
+ }
+ if (!Settings::IsGPULevelExtreme()) {
+ return;
+ }
+ if (system.Renderer().Rasterizer().MustFlushRegion(addr, size)) {
+ auto& gpu = system.GPU();
+ u64 fence = gpu.RequestFlush(addr, size);
+ PushCommand(GPUTickCommand());
+ while (fence > gpu.CurrentFlushRequestFence()) {
+ }
+ }
}
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
- system.Renderer().Rasterizer().InvalidateRegion(addr, size);
+ system.Renderer().Rasterizer().OnCPUWrite(addr, size);
}
void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
// Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
- InvalidateRegion(addr, size);
+ system.Renderer().Rasterizer().OnCPUWrite(addr, size);
}
void ThreadManager::WaitIdle() const {
@@ -95,6 +127,10 @@ void ThreadManager::WaitIdle() const {
}
}
+void ThreadManager::OnCommandListEnd() {
+ PushCommand(OnCommandListEndCommand());
+}
+
u64 ThreadManager::PushCommand(CommandData&& command_data) {
const u64 fence{++state.last_fence};
state.queue.Push(CommandDataContainer(std::move(command_data), fence));
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index cd74ad330..32a34e3a7 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -37,6 +37,14 @@ struct SubmitListCommand final {
Tegra::CommandList entries;
};
+/// Command to signal to the GPU thread that a cdma command list is ready for processing
+struct SubmitChCommandEntries final {
+ explicit SubmitChCommandEntries(Tegra::ChCommandHeaderList&& entries)
+ : entries{std::move(entries)} {}
+
+ Tegra::ChCommandHeaderList entries;
+};
+
/// Command to signal to the GPU thread that a swap buffers is pending
struct SwapBuffersCommand final {
explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer)
@@ -70,9 +78,16 @@ struct FlushAndInvalidateRegionCommand final {
u64 size;
};
+/// Command called within the gpu, to schedule actions after a command list end
+struct OnCommandListEndCommand final {};
+
+/// Command to make the gpu look into pending requests
+struct GPUTickCommand final {};
+
using CommandData =
- std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
- InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
+ std::variant<EndProcessingCommand, SubmitListCommand, SubmitChCommandEntries,
+ SwapBuffersCommand, FlushRegionCommand, InvalidateRegionCommand,
+ FlushAndInvalidateRegionCommand, OnCommandListEndCommand, GPUTickCommand>;
struct CommandDataContainer {
CommandDataContainer() = default;
@@ -102,11 +117,14 @@ public:
/// Creates and starts the GPU thread.
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher);
+ Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher);
/// Push GPU command entries to be processed
void SubmitList(Tegra::CommandList&& entries);
+ /// Push GPU CDMA command buffer entries to be processed
+ void SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries);
+
/// Swap buffers (render frame)
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
@@ -122,6 +140,8 @@ public:
// Wait until the gpu thread is idle.
void WaitIdle() const;
+ void OnCommandListEnd();
+
private:
/// Pushes a command to be executed by the GPU thread
u64 PushCommand(CommandData&& command_data);
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
new file mode 100644
index 000000000..c157724a9
--- /dev/null
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -0,0 +1,36 @@
+set(SHADER_SOURCES
+ opengl_present.frag
+ opengl_present.vert
+)
+
+set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
+set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
+set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
+
+set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
+set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
+
+foreach(FILENAME IN ITEMS ${SHADER_SOURCES})
+ string(REPLACE "." "_" SHADER_NAME ${FILENAME})
+ set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
+ set(HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
+ add_custom_command(
+ OUTPUT
+ ${HEADER_FILE}
+ COMMAND
+ ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${HEADER_FILE} ${INPUT_FILE}
+ MAIN_DEPENDENCY
+ ${SOURCE_FILE}
+ DEPENDS
+ ${INPUT_FILE}
+ # HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
+ )
+ set(SHADER_HEADERS ${SHADER_HEADERS} ${HEADER_FILE})
+endforeach()
+
+add_custom_target(host_shaders
+ DEPENDS
+ ${SHADER_HEADERS}
+ SOURCES
+ ${SHADER_SOURCES}
+)
diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake
new file mode 100644
index 000000000..c0fc49768
--- /dev/null
+++ b/src/video_core/host_shaders/StringShaderHeader.cmake
@@ -0,0 +1,13 @@
+set(SOURCE_FILE ${CMAKE_ARGV3})
+set(HEADER_FILE ${CMAKE_ARGV4})
+set(INPUT_FILE ${CMAKE_ARGV5})
+
+get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
+string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
+string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
+
+file(READ ${SOURCE_FILE} CONTENTS)
+
+get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
+make_directory(${OUTPUT_DIR})
+configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
new file mode 100644
index 000000000..8a4cb024b
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -0,0 +1,10 @@
+#version 430 core
+
+layout (location = 0) in vec2 frag_tex_coord;
+layout (location = 0) out vec4 color;
+
+layout (binding = 0) uniform sampler2D color_texture;
+
+void main() {
+ color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
+}
diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert
new file mode 100644
index 000000000..2235d31a4
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present.vert
@@ -0,0 +1,24 @@
+#version 430 core
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+layout (location = 0) in vec2 vert_position;
+layout (location = 1) in vec2 vert_tex_coord;
+layout (location = 0) out vec2 frag_tex_coord;
+
+// This is a truncated 3x3 matrix for 2D transformations:
+// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
+// The third column performs translation.
+// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
+// implicitly be [0, 0, 1]
+layout (location = 0) uniform mat3x2 modelview_matrix;
+
+void main() {
+ // Multiply input position by the rotscale part of the matrix and then manually translate by
+ // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
+ // to `vec3(vert_position.xy, 1.0)`
+ gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
+ frag_tex_coord = vert_tex_coord;
+}
diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in
new file mode 100644
index 000000000..ccdb0d2a9
--- /dev/null
+++ b/src/video_core/host_shaders/source_shader.h.in
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <string_view>
+
+namespace HostShaders {
+
+constexpr std::string_view @CONTENTS_NAME@ = R"(@CONTENTS@)";
+
+} // namespace HostShaders
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
new file mode 100644
index 000000000..cd21a2112
--- /dev/null
+++ b/src/video_core/macro/macro.cpp
@@ -0,0 +1,91 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <optional>
+#include <boost/container_hash/hash.hpp>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/settings.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/macro/macro.h"
+#include "video_core/macro/macro_hle.h"
+#include "video_core/macro/macro_interpreter.h"
+#include "video_core/macro/macro_jit_x64.h"
+
+namespace Tegra {
+
+MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d)
+ : hle_macros{std::make_unique<Tegra::HLEMacro>(maxwell3d)} {}
+
+MacroEngine::~MacroEngine() = default;
+
+void MacroEngine::AddCode(u32 method, u32 data) {
+ uploaded_macro_code[method].push_back(data);
+}
+
+void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method,
+ const std::vector<u32>& parameters) {
+ auto compiled_macro = macro_cache.find(method);
+ if (compiled_macro != macro_cache.end()) {
+ const auto& cache_info = compiled_macro->second;
+ if (cache_info.has_hle_program) {
+ cache_info.hle_program->Execute(parameters, method);
+ } else {
+ cache_info.lle_program->Execute(parameters, method);
+ }
+ } else {
+ // Macro not compiled, check if it's uploaded and if so, compile it
+ std::optional<u32> mid_method;
+ const auto macro_code = uploaded_macro_code.find(method);
+ if (macro_code == uploaded_macro_code.end()) {
+ for (const auto& [method_base, code] : uploaded_macro_code) {
+ if (method >= method_base && (method - method_base) < code.size()) {
+ mid_method = method_base;
+ break;
+ }
+ }
+ if (!mid_method.has_value()) {
+ UNREACHABLE_MSG("Macro 0x{0:x} was not uploaded", method);
+ return;
+ }
+ }
+ auto& cache_info = macro_cache[method];
+
+ if (!mid_method.has_value()) {
+ cache_info.lle_program = Compile(macro_code->second);
+ cache_info.hash = boost::hash_value(macro_code->second);
+ } else {
+ const auto& macro_cached = uploaded_macro_code[mid_method.value()];
+ const auto rebased_method = method - mid_method.value();
+ auto& code = uploaded_macro_code[method];
+ code.resize(macro_cached.size() - rebased_method);
+ std::memcpy(code.data(), macro_cached.data() + rebased_method,
+ code.size() * sizeof(u32));
+ cache_info.hash = boost::hash_value(code);
+ cache_info.lle_program = Compile(code);
+ }
+
+ auto hle_program = hle_macros->GetHLEProgram(cache_info.hash);
+ if (hle_program.has_value()) {
+ cache_info.has_hle_program = true;
+ cache_info.hle_program = std::move(hle_program.value());
+ cache_info.hle_program->Execute(parameters, method);
+ } else {
+ cache_info.lle_program->Execute(parameters, method);
+ }
+ }
+}
+
+std::unique_ptr<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d) {
+ if (Settings::values.disable_macro_jit) {
+ return std::make_unique<MacroInterpreter>(maxwell3d);
+ }
+#ifdef ARCHITECTURE_x86_64
+ return std::make_unique<MacroJITx64>(maxwell3d);
+#else
+ return std::make_unique<MacroInterpreter>(maxwell3d);
+#endif
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro.h b/src/video_core/macro/macro.h
new file mode 100644
index 000000000..31ee3440a
--- /dev/null
+++ b/src/video_core/macro/macro.h
@@ -0,0 +1,142 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+
+namespace Engines {
+class Maxwell3D;
+}
+
+namespace Macro {
+constexpr std::size_t NUM_MACRO_REGISTERS = 8;
+enum class Operation : u32 {
+ ALU = 0,
+ AddImmediate = 1,
+ ExtractInsert = 2,
+ ExtractShiftLeftImmediate = 3,
+ ExtractShiftLeftRegister = 4,
+ Read = 5,
+ Unused = 6, // This operation doesn't seem to be a valid encoding.
+ Branch = 7,
+};
+
+enum class ALUOperation : u32 {
+ Add = 0,
+ AddWithCarry = 1,
+ Subtract = 2,
+ SubtractWithBorrow = 3,
+ // Operations 4-7 don't seem to be valid encodings.
+ Xor = 8,
+ Or = 9,
+ And = 10,
+ AndNot = 11,
+ Nand = 12
+};
+
+enum class ResultOperation : u32 {
+ IgnoreAndFetch = 0,
+ Move = 1,
+ MoveAndSetMethod = 2,
+ FetchAndSend = 3,
+ MoveAndSend = 4,
+ FetchAndSetMethod = 5,
+ MoveAndSetMethodFetchAndSend = 6,
+ MoveAndSetMethodSend = 7
+};
+
+enum class BranchCondition : u32 {
+ Zero = 0,
+ NotZero = 1,
+};
+
+union Opcode {
+ u32 raw;
+ BitField<0, 3, Operation> operation;
+ BitField<4, 3, ResultOperation> result_operation;
+ BitField<4, 1, BranchCondition> branch_condition;
+ // If set on a branch, then the branch doesn't have a delay slot.
+ BitField<5, 1, u32> branch_annul;
+ BitField<7, 1, u32> is_exit;
+ BitField<8, 3, u32> dst;
+ BitField<11, 3, u32> src_a;
+ BitField<14, 3, u32> src_b;
+ // The signed immediate overlaps the second source operand and the alu operation.
+ BitField<14, 18, s32> immediate;
+
+ BitField<17, 5, ALUOperation> alu_operation;
+
+ // Bitfield instructions data
+ BitField<17, 5, u32> bf_src_bit;
+ BitField<22, 5, u32> bf_size;
+ BitField<27, 5, u32> bf_dst_bit;
+
+ u32 GetBitfieldMask() const {
+ return (1 << bf_size) - 1;
+ }
+
+ s32 GetBranchTarget() const {
+ return static_cast<s32>(immediate * sizeof(u32));
+ }
+};
+
+union MethodAddress {
+ u32 raw;
+ BitField<0, 12, u32> address;
+ BitField<12, 6, u32> increment;
+};
+
+} // namespace Macro
+
+class HLEMacro;
+
+class CachedMacro {
+public:
+ virtual ~CachedMacro() = default;
+ /**
+ * Executes the macro code with the specified input parameters.
+ *
+ * @param parameters The parameters of the macro
+ * @param method The method to execute
+ */
+ virtual void Execute(const std::vector<u32>& parameters, u32 method) = 0;
+};
+
+class MacroEngine {
+public:
+ explicit MacroEngine(Engines::Maxwell3D& maxwell3d);
+ virtual ~MacroEngine();
+
+ // Store the uploaded macro code to compile them when they're called.
+ void AddCode(u32 method, u32 data);
+
+ // Compiles the macro if its not in the cache, and executes the compiled macro
+ void Execute(Engines::Maxwell3D& maxwell3d, u32 method, const std::vector<u32>& parameters);
+
+protected:
+ virtual std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) = 0;
+
+private:
+ struct CacheInfo {
+ std::unique_ptr<CachedMacro> lle_program{};
+ std::unique_ptr<CachedMacro> hle_program{};
+ u64 hash{};
+ bool has_hle_program{};
+ };
+
+ std::unordered_map<u32, CacheInfo> macro_cache;
+ std::unordered_map<u32, std::vector<u32>> uploaded_macro_code;
+ std::unique_ptr<HLEMacro> hle_macros;
+};
+
+std::unique_ptr<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d);
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
new file mode 100644
index 000000000..df00b57df
--- /dev/null
+++ b/src/video_core/macro/macro_hle.cpp
@@ -0,0 +1,109 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <vector>
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/macro/macro_hle.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace Tegra {
+
+namespace {
+// HLE'd functions
+void HLE_771BB18C62444DA0(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
+ const u32 instance_count = parameters[2] & maxwell3d.GetRegisterValue(0xD1B);
+
+ maxwell3d.regs.draw.topology.Assign(
+ static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0x3ffffff));
+ maxwell3d.regs.vb_base_instance = parameters[5];
+ maxwell3d.mme_draw.instance_count = instance_count;
+ maxwell3d.regs.vb_element_base = parameters[3];
+ maxwell3d.regs.index_array.count = parameters[1];
+ maxwell3d.regs.index_array.first = parameters[4];
+
+ if (maxwell3d.ShouldExecute()) {
+ maxwell3d.Rasterizer().Draw(true, true);
+ }
+ maxwell3d.regs.index_array.count = 0;
+ maxwell3d.mme_draw.instance_count = 0;
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+}
+
+void HLE_0D61FC9FAAC9FCAD(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
+ const u32 count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
+
+ maxwell3d.regs.vertex_buffer.first = parameters[3];
+ maxwell3d.regs.vertex_buffer.count = parameters[1];
+ maxwell3d.regs.vb_base_instance = parameters[4];
+ maxwell3d.regs.draw.topology.Assign(
+ static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0]));
+ maxwell3d.mme_draw.instance_count = count;
+
+ if (maxwell3d.ShouldExecute()) {
+ maxwell3d.Rasterizer().Draw(false, true);
+ }
+ maxwell3d.regs.vertex_buffer.count = 0;
+ maxwell3d.mme_draw.instance_count = 0;
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+}
+
+void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
+ const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
+ const u32 element_base = parameters[4];
+ const u32 base_instance = parameters[5];
+ maxwell3d.regs.index_array.first = parameters[3];
+ maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
+ maxwell3d.regs.index_array.count = parameters[1];
+ maxwell3d.regs.vb_element_base = element_base;
+ maxwell3d.regs.vb_base_instance = base_instance;
+ maxwell3d.mme_draw.instance_count = instance_count;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, element_base);
+ maxwell3d.CallMethodFromMME(0x8e5, base_instance);
+ maxwell3d.regs.draw.topology.Assign(
+ static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0]));
+ if (maxwell3d.ShouldExecute()) {
+ maxwell3d.Rasterizer().Draw(true, true);
+ }
+ maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
+ maxwell3d.regs.index_array.count = 0;
+ maxwell3d.regs.vb_element_base = 0x0;
+ maxwell3d.regs.vb_base_instance = 0x0;
+ maxwell3d.mme_draw.instance_count = 0;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, 0x0);
+ maxwell3d.CallMethodFromMME(0x8e5, 0x0);
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+}
+} // Anonymous namespace
+
+constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
+ {0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
+ {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
+ {0x0217920100488FF7, &HLE_0217920100488FF7},
+}};
+
+HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
+HLEMacro::~HLEMacro() = default;
+
+std::optional<std::unique_ptr<CachedMacro>> HLEMacro::GetHLEProgram(u64 hash) const {
+ const auto it = std::find_if(hle_funcs.cbegin(), hle_funcs.cend(),
+ [hash](const auto& pair) { return pair.first == hash; });
+ if (it == hle_funcs.end()) {
+ return std::nullopt;
+ }
+ return std::make_unique<HLEMacroImpl>(maxwell3d, it->second);
+}
+
+HLEMacroImpl::~HLEMacroImpl() = default;
+
+HLEMacroImpl::HLEMacroImpl(Engines::Maxwell3D& maxwell3d, HLEFunction func)
+ : maxwell3d(maxwell3d), func(func) {}
+
+void HLEMacroImpl::Execute(const std::vector<u32>& parameters, u32 method) {
+ func(maxwell3d, parameters);
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_hle.h b/src/video_core/macro/macro_hle.h
new file mode 100644
index 000000000..37af875a0
--- /dev/null
+++ b/src/video_core/macro/macro_hle.h
@@ -0,0 +1,44 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/macro/macro.h"
+
+namespace Tegra {
+
+namespace Engines {
+class Maxwell3D;
+}
+
+using HLEFunction = void (*)(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters);
+
+class HLEMacro {
+public:
+ explicit HLEMacro(Engines::Maxwell3D& maxwell3d);
+ ~HLEMacro();
+
+ std::optional<std::unique_ptr<CachedMacro>> GetHLEProgram(u64 hash) const;
+
+private:
+ Engines::Maxwell3D& maxwell3d;
+};
+
+class HLEMacroImpl : public CachedMacro {
+public:
+ explicit HLEMacroImpl(Engines::Maxwell3D& maxwell3d, HLEFunction func);
+ ~HLEMacroImpl();
+
+ void Execute(const std::vector<u32>& parameters, u32 method) override;
+
+private:
+ Engines::Maxwell3D& maxwell3d;
+ HLEFunction func;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
new file mode 100644
index 000000000..bd01fd1f2
--- /dev/null
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -0,0 +1,288 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/macro/macro_interpreter.h"
+
+MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192));
+
+namespace Tegra {
+MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d)
+ : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {}
+
+std::unique_ptr<CachedMacro> MacroInterpreter::Compile(const std::vector<u32>& code) {
+ return std::make_unique<MacroInterpreterImpl>(maxwell3d, code);
+}
+
+MacroInterpreterImpl::MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d,
+ const std::vector<u32>& code)
+ : maxwell3d(maxwell3d), code(code) {}
+
+void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 method) {
+ MICROPROFILE_SCOPE(MacroInterp);
+ Reset();
+
+ registers[1] = parameters[0];
+ num_parameters = parameters.size();
+
+ if (num_parameters > parameters_capacity) {
+ parameters_capacity = num_parameters;
+ this->parameters = std::make_unique<u32[]>(num_parameters);
+ }
+ std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32));
+
+ // Execute the code until we hit an exit condition.
+ bool keep_executing = true;
+ while (keep_executing) {
+ keep_executing = Step(false);
+ }
+
+ // Assert the the macro used all the input parameters
+ ASSERT(next_parameter_index == num_parameters);
+}
+
+void MacroInterpreterImpl::Reset() {
+ registers = {};
+ pc = 0;
+ delayed_pc = {};
+ method_address.raw = 0;
+ num_parameters = 0;
+ // The next parameter index starts at 1, because $r1 already has the value of the first
+ // parameter.
+ next_parameter_index = 1;
+ carry_flag = false;
+}
+
+bool MacroInterpreterImpl::Step(bool is_delay_slot) {
+ u32 base_address = pc;
+
+ Macro::Opcode opcode = GetOpcode();
+ pc += 4;
+
+ // Update the program counter if we were delayed
+ if (delayed_pc) {
+ ASSERT(is_delay_slot);
+ pc = *delayed_pc;
+ delayed_pc = {};
+ }
+
+ switch (opcode.operation) {
+ case Macro::Operation::ALU: {
+ u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a),
+ GetRegister(opcode.src_b));
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Macro::Operation::AddImmediate: {
+ ProcessResult(opcode.result_operation, opcode.dst,
+ GetRegister(opcode.src_a) + opcode.immediate);
+ break;
+ }
+ case Macro::Operation::ExtractInsert: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
+ dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
+ dst |= src << opcode.bf_dst_bit;
+ ProcessResult(opcode.result_operation, opcode.dst, dst);
+ break;
+ }
+ case Macro::Operation::ExtractShiftLeftImmediate: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Macro::Operation::ExtractShiftLeftRegister: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Macro::Operation::Read: {
+ u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate);
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Macro::Operation::Branch: {
+ ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
+ u32 value = GetRegister(opcode.src_a);
+ bool taken = EvaluateBranchCondition(opcode.branch_condition, value);
+ if (taken) {
+ // Ignore the delay slot if the branch has the annul bit.
+ if (opcode.branch_annul) {
+ pc = base_address + opcode.GetBranchTarget();
+ return true;
+ }
+
+ delayed_pc = base_address + opcode.GetBranchTarget();
+ // Execute one more instruction due to the delay slot.
+ return Step(true);
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented macro operation {}",
+ static_cast<u32>(opcode.operation.Value()));
+ }
+
+ // An instruction with the Exit flag will not actually
+ // cause an exit if it's executed inside a delay slot.
+ if (opcode.is_exit && !is_delay_slot) {
+ // Exit has a delay slot, execute the next instruction
+ Step(true);
+ return false;
+ }
+
+ return true;
+}
+
+u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) {
+ switch (operation) {
+ case Macro::ALUOperation::Add: {
+ const u64 result{static_cast<u64>(src_a) + src_b};
+ carry_flag = result > 0xffffffff;
+ return static_cast<u32>(result);
+ }
+ case Macro::ALUOperation::AddWithCarry: {
+ const u64 result{static_cast<u64>(src_a) + src_b + (carry_flag ? 1ULL : 0ULL)};
+ carry_flag = result > 0xffffffff;
+ return static_cast<u32>(result);
+ }
+ case Macro::ALUOperation::Subtract: {
+ const u64 result{static_cast<u64>(src_a) - src_b};
+ carry_flag = result < 0x100000000;
+ return static_cast<u32>(result);
+ }
+ case Macro::ALUOperation::SubtractWithBorrow: {
+ const u64 result{static_cast<u64>(src_a) - src_b - (carry_flag ? 0ULL : 1ULL)};
+ carry_flag = result < 0x100000000;
+ return static_cast<u32>(result);
+ }
+ case Macro::ALUOperation::Xor:
+ return src_a ^ src_b;
+ case Macro::ALUOperation::Or:
+ return src_a | src_b;
+ case Macro::ALUOperation::And:
+ return src_a & src_b;
+ case Macro::ALUOperation::AndNot:
+ return src_a & ~src_b;
+ case Macro::ALUOperation::Nand:
+ return ~(src_a & src_b);
+
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
+ return 0;
+ }
+}
+
+void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) {
+ switch (operation) {
+ case Macro::ResultOperation::IgnoreAndFetch:
+ // Fetch parameter and ignore result.
+ SetRegister(reg, FetchParameter());
+ break;
+ case Macro::ResultOperation::Move:
+ // Move result.
+ SetRegister(reg, result);
+ break;
+ case Macro::ResultOperation::MoveAndSetMethod:
+ // Move result and use as Method Address.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ break;
+ case Macro::ResultOperation::FetchAndSend:
+ // Fetch parameter and send result.
+ SetRegister(reg, FetchParameter());
+ Send(result);
+ break;
+ case Macro::ResultOperation::MoveAndSend:
+ // Move and send result.
+ SetRegister(reg, result);
+ Send(result);
+ break;
+ case Macro::ResultOperation::FetchAndSetMethod:
+ // Fetch parameter and use result as Method Address.
+ SetRegister(reg, FetchParameter());
+ SetMethodAddress(result);
+ break;
+ case Macro::ResultOperation::MoveAndSetMethodFetchAndSend:
+ // Move result and use as Method Address, then fetch and send parameter.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send(FetchParameter());
+ break;
+ case Macro::ResultOperation::MoveAndSetMethodSend:
+ // Move result and use as Method Address, then send bits 12:17 of result.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send((result >> 12) & 0b111111);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation));
+ }
+}
+
+bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const {
+ switch (cond) {
+ case Macro::BranchCondition::Zero:
+ return value == 0;
+ case Macro::BranchCondition::NotZero:
+ return value != 0;
+ }
+ UNREACHABLE();
+ return true;
+}
+
+Macro::Opcode MacroInterpreterImpl::GetOpcode() const {
+ ASSERT((pc % sizeof(u32)) == 0);
+ ASSERT(pc < code.size() * sizeof(u32));
+ return {code[pc / sizeof(u32)]};
+}
+
+u32 MacroInterpreterImpl::GetRegister(u32 register_id) const {
+ return registers.at(register_id);
+}
+
+void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) {
+ // Register 0 is hardwired as the zero register.
+ // Ensure no writes to it actually occur.
+ if (register_id == 0) {
+ return;
+ }
+
+ registers.at(register_id) = value;
+}
+
+void MacroInterpreterImpl::SetMethodAddress(u32 address) {
+ method_address.raw = address;
+}
+
+void MacroInterpreterImpl::Send(u32 value) {
+ maxwell3d.CallMethodFromMME(method_address.address, value);
+ // Increment the method address by the method increment.
+ method_address.address.Assign(method_address.address.Value() +
+ method_address.increment.Value());
+}
+
+u32 MacroInterpreterImpl::Read(u32 method) const {
+ return maxwell3d.GetRegisterValue(method);
+}
+
+u32 MacroInterpreterImpl::FetchParameter() {
+ ASSERT(next_parameter_index < num_parameters);
+ return parameters[next_parameter_index++];
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_interpreter.h b/src/video_core/macro/macro_interpreter.h
new file mode 100644
index 000000000..90217fc89
--- /dev/null
+++ b/src/video_core/macro/macro_interpreter.h
@@ -0,0 +1,102 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include <array>
+#include <optional>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "video_core/macro/macro.h"
+
+namespace Tegra {
+namespace Engines {
+class Maxwell3D;
+}
+
+class MacroInterpreter final : public MacroEngine {
+public:
+ explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d);
+
+protected:
+ std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override;
+
+private:
+ Engines::Maxwell3D& maxwell3d;
+};
+
+class MacroInterpreterImpl : public CachedMacro {
+public:
+ MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code);
+ void Execute(const std::vector<u32>& parameters, u32 method) override;
+
+private:
+ /// Resets the execution engine state, zeroing registers, etc.
+ void Reset();
+
+ /**
+ * Executes a single macro instruction located at the current program counter. Returns whether
+ * the interpreter should keep running.
+ * @param offset Offset to start execution at.
+ * @param is_delay_slot Whether the current step is being executed due to a delay slot in a
+ * previous instruction.
+ */
+ bool Step(bool is_delay_slot);
+
+ /// Calculates the result of an ALU operation. src_a OP src_b;
+ u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
+
+ /// Performs the result operation on the input result and stores it in the specified register
+ /// (if necessary).
+ void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result);
+
+ /// Evaluates the branch condition and returns whether the branch should be taken or not.
+ bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
+
+ /// Reads an opcode at the current program counter location.
+ Macro::Opcode GetOpcode() const;
+
+ /// Returns the specified register's value. Register 0 is hardcoded to always return 0.
+ u32 GetRegister(u32 register_id) const;
+
+ /// Sets the register to the input value.
+ void SetRegister(u32 register_id, u32 value);
+
+ /// Sets the method address to use for the next Send instruction.
+ void SetMethodAddress(u32 address);
+
+ /// Calls a GPU Engine method with the input parameter.
+ void Send(u32 value);
+
+ /// Reads a GPU register located at the method address.
+ u32 Read(u32 method) const;
+
+ /// Returns the next parameter in the parameter queue.
+ u32 FetchParameter();
+
+ Engines::Maxwell3D& maxwell3d;
+
+ /// Current program counter
+ u32 pc;
+ /// Program counter to execute at after the delay slot is executed.
+ std::optional<u32> delayed_pc;
+
+ /// General purpose macro registers.
+ std::array<u32, Macro::NUM_MACRO_REGISTERS> registers = {};
+
+ /// Method address to use for the next Send instruction.
+ Macro::MethodAddress method_address = {};
+
+ /// Input parameters of the current macro.
+ std::unique_ptr<u32[]> parameters;
+ std::size_t num_parameters = 0;
+ std::size_t parameters_capacity = 0;
+ /// Index of the next parameter that will be fetched by the 'parm' instruction.
+ u32 next_parameter_index = 0;
+
+ bool carry_flag = false;
+ const std::vector<u32>& code;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
new file mode 100644
index 000000000..954b87515
--- /dev/null
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -0,0 +1,620 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/x64/xbyak_util.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/macro/macro_interpreter.h"
+#include "video_core/macro/macro_jit_x64.h"
+
+MICROPROFILE_DEFINE(MacroJitCompile, "GPU", "Compile macro JIT", MP_RGB(173, 255, 47));
+MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255, 0));
+
+namespace Tegra {
+constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx;
+constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp;
+constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
+constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
+constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
+
+static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({
+ STATE,
+ RESULT,
+ PARAMETERS,
+ METHOD_ADDRESS,
+ BRANCH_HOLDER,
+});
+
+MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d)
+ : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {}
+
+std::unique_ptr<CachedMacro> MacroJITx64::Compile(const std::vector<u32>& code) {
+ return std::make_unique<MacroJITx64Impl>(maxwell3d, code);
+}
+
+MacroJITx64Impl::MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code)
+ : Xbyak::CodeGenerator(MAX_CODE_SIZE), code(code), maxwell3d(maxwell3d) {
+ Compile();
+}
+
+MacroJITx64Impl::~MacroJITx64Impl() = default;
+
+void MacroJITx64Impl::Execute(const std::vector<u32>& parameters, u32 method) {
+ MICROPROFILE_SCOPE(MacroJitExecute);
+ ASSERT_OR_EXECUTE(program != nullptr, { return; });
+ JITState state{};
+ state.maxwell3d = &maxwell3d;
+ state.registers = {};
+ program(&state, parameters.data());
+}
+
+void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) {
+ const bool is_a_zero = opcode.src_a == 0;
+ const bool is_b_zero = opcode.src_b == 0;
+ const bool valid_operation = !is_a_zero && !is_b_zero;
+ [[maybe_unused]] const bool is_move_operation = !is_a_zero && is_b_zero;
+ const bool has_zero_register = is_a_zero || is_b_zero;
+ const bool no_zero_reg_skip = opcode.alu_operation == Macro::ALUOperation::AddWithCarry ||
+ opcode.alu_operation == Macro::ALUOperation::SubtractWithBorrow;
+
+ Xbyak::Reg32 src_a;
+ Xbyak::Reg32 src_b;
+
+ if (!optimizer.zero_reg_skip || no_zero_reg_skip) {
+ src_a = Compile_GetRegister(opcode.src_a, RESULT);
+ src_b = Compile_GetRegister(opcode.src_b, eax);
+ } else {
+ if (!is_a_zero) {
+ src_a = Compile_GetRegister(opcode.src_a, RESULT);
+ }
+ if (!is_b_zero) {
+ src_b = Compile_GetRegister(opcode.src_b, eax);
+ }
+ }
+
+ bool has_emitted = false;
+
+ switch (opcode.alu_operation) {
+ case Macro::ALUOperation::Add:
+ if (optimizer.zero_reg_skip) {
+ if (valid_operation) {
+ add(src_a, src_b);
+ }
+ } else {
+ add(src_a, src_b);
+ }
+
+ if (!optimizer.can_skip_carry) {
+ setc(byte[STATE + offsetof(JITState, carry_flag)]);
+ }
+ break;
+ case Macro::ALUOperation::AddWithCarry:
+ bt(dword[STATE + offsetof(JITState, carry_flag)], 0);
+ adc(src_a, src_b);
+ setc(byte[STATE + offsetof(JITState, carry_flag)]);
+ break;
+ case Macro::ALUOperation::Subtract:
+ if (optimizer.zero_reg_skip) {
+ if (valid_operation) {
+ sub(src_a, src_b);
+ has_emitted = true;
+ }
+ } else {
+ sub(src_a, src_b);
+ has_emitted = true;
+ }
+ if (!optimizer.can_skip_carry && has_emitted) {
+ setc(byte[STATE + offsetof(JITState, carry_flag)]);
+ }
+ break;
+ case Macro::ALUOperation::SubtractWithBorrow:
+ bt(dword[STATE + offsetof(JITState, carry_flag)], 0);
+ sbb(src_a, src_b);
+ setc(byte[STATE + offsetof(JITState, carry_flag)]);
+ break;
+ case Macro::ALUOperation::Xor:
+ if (optimizer.zero_reg_skip) {
+ if (valid_operation) {
+ xor_(src_a, src_b);
+ }
+ } else {
+ xor_(src_a, src_b);
+ }
+ break;
+ case Macro::ALUOperation::Or:
+ if (optimizer.zero_reg_skip) {
+ if (valid_operation) {
+ or_(src_a, src_b);
+ }
+ } else {
+ or_(src_a, src_b);
+ }
+ break;
+ case Macro::ALUOperation::And:
+ if (optimizer.zero_reg_skip) {
+ if (!has_zero_register) {
+ and_(src_a, src_b);
+ }
+ } else {
+ and_(src_a, src_b);
+ }
+ break;
+ case Macro::ALUOperation::AndNot:
+ if (optimizer.zero_reg_skip) {
+ if (!is_a_zero) {
+ not_(src_b);
+ and_(src_a, src_b);
+ }
+ } else {
+ not_(src_b);
+ and_(src_a, src_b);
+ }
+ break;
+ case Macro::ALUOperation::Nand:
+ if (optimizer.zero_reg_skip) {
+ if (!is_a_zero) {
+ and_(src_a, src_b);
+ not_(src_a);
+ }
+ } else {
+ and_(src_a, src_b);
+ not_(src_a);
+ }
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation {}",
+ static_cast<std::size_t>(opcode.alu_operation.Value()));
+ break;
+ }
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) {
+ if (optimizer.skip_dummy_addimmediate) {
+ // Games tend to use this as an exit instruction placeholder. It's to encode an instruction
+ // without doing anything. In our case we can just not emit anything.
+ if (opcode.result_operation == Macro::ResultOperation::Move && opcode.dst == 0) {
+ return;
+ }
+ }
+ // Check for redundant moves
+ if (optimizer.optimize_for_method_move &&
+ opcode.result_operation == Macro::ResultOperation::MoveAndSetMethod) {
+ if (next_opcode.has_value()) {
+ const auto next = *next_opcode;
+ if (next.result_operation == Macro::ResultOperation::MoveAndSetMethod &&
+ opcode.dst == next.dst) {
+ return;
+ }
+ }
+ }
+ if (optimizer.zero_reg_skip && opcode.src_a == 0) {
+ if (opcode.immediate == 0) {
+ xor_(RESULT, RESULT);
+ } else {
+ mov(RESULT, opcode.immediate);
+ }
+ } else {
+ auto result = Compile_GetRegister(opcode.src_a, RESULT);
+ if (opcode.immediate > 2) {
+ add(result, opcode.immediate);
+ } else if (opcode.immediate == 1) {
+ inc(result);
+ } else if (opcode.immediate < 0) {
+ sub(result, opcode.immediate * -1);
+ }
+ }
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) {
+ auto dst = Compile_GetRegister(opcode.src_a, RESULT);
+ auto src = Compile_GetRegister(opcode.src_b, eax);
+
+ if (opcode.bf_src_bit != 0 && opcode.bf_src_bit != 31) {
+ shr(src, opcode.bf_src_bit);
+ } else if (opcode.bf_src_bit == 31) {
+ xor_(src, src);
+ }
+ // Don't bother masking the whole register since we're using a 32 bit register
+ if (opcode.bf_size != 31 && opcode.bf_size != 0) {
+ and_(src, opcode.GetBitfieldMask());
+ } else if (opcode.bf_size == 0) {
+ xor_(src, src);
+ }
+ if (opcode.bf_dst_bit != 31 && opcode.bf_dst_bit != 0) {
+ shl(src, opcode.bf_dst_bit);
+ } else if (opcode.bf_dst_bit == 31) {
+ xor_(src, src);
+ }
+
+ const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
+ if (mask != 0xffffffff) {
+ and_(dst, mask);
+ }
+ or_(dst, src);
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) {
+ const auto dst = Compile_GetRegister(opcode.src_a, ecx);
+ const auto src = Compile_GetRegister(opcode.src_b, RESULT);
+
+ shr(src, dst.cvt8());
+ if (opcode.bf_size != 0 && opcode.bf_size != 31) {
+ and_(src, opcode.GetBitfieldMask());
+ } else if (opcode.bf_size == 0) {
+ xor_(src, src);
+ }
+
+ if (opcode.bf_dst_bit != 0 && opcode.bf_dst_bit != 31) {
+ shl(src, opcode.bf_dst_bit);
+ } else if (opcode.bf_dst_bit == 31) {
+ xor_(src, src);
+ }
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) {
+ const auto dst = Compile_GetRegister(opcode.src_a, ecx);
+ const auto src = Compile_GetRegister(opcode.src_b, RESULT);
+
+ if (opcode.bf_src_bit != 0) {
+ shr(src, opcode.bf_src_bit);
+ }
+
+ if (opcode.bf_size != 31) {
+ and_(src, opcode.GetBitfieldMask());
+ }
+ shl(src, dst.cvt8());
+
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) {
+ if (optimizer.zero_reg_skip && opcode.src_a == 0) {
+ if (opcode.immediate == 0) {
+ xor_(RESULT, RESULT);
+ } else {
+ mov(RESULT, opcode.immediate);
+ }
+ } else {
+ auto result = Compile_GetRegister(opcode.src_a, RESULT);
+ if (opcode.immediate > 2) {
+ add(result, opcode.immediate);
+ } else if (opcode.immediate == 1) {
+ inc(result);
+ } else if (opcode.immediate < 0) {
+ sub(result, opcode.immediate * -1);
+ }
+ }
+
+ // Equivalent to Engines::Maxwell3D::GetRegisterValue:
+ if (optimizer.enable_asserts) {
+ Xbyak::Label pass_range_check;
+ cmp(RESULT, static_cast<u32>(Engines::Maxwell3D::Regs::NUM_REGS));
+ jb(pass_range_check);
+ int3();
+ L(pass_range_check);
+ }
+ mov(rax, qword[STATE]);
+ mov(RESULT,
+ dword[rax + offsetof(Engines::Maxwell3D, regs) +
+ offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]);
+
+ Compile_ProcessResult(opcode.result_operation, opcode.dst);
+}
+
+static void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) {
+ maxwell3d->CallMethodFromMME(method_address.address, value);
+}
+
+void Tegra::MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) {
+ Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ mov(Common::X64::ABI_PARAM1, qword[STATE]);
+ mov(Common::X64::ABI_PARAM2, METHOD_ADDRESS);
+ mov(Common::X64::ABI_PARAM3, value);
+ Common::X64::CallFarFunction(*this, &Send);
+ Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+
+ Xbyak::Label dont_process{};
+ // Get increment
+ test(METHOD_ADDRESS, 0x3f000);
+ // If zero, method address doesn't update
+ je(dont_process);
+
+ mov(ecx, METHOD_ADDRESS);
+ and_(METHOD_ADDRESS, 0xfff);
+ shr(ecx, 12);
+ and_(ecx, 0x3f);
+ lea(eax, ptr[rcx + METHOD_ADDRESS.cvt64()]);
+ sal(ecx, 12);
+ or_(eax, ecx);
+
+ mov(METHOD_ADDRESS, eax);
+
+ L(dont_process);
+}
+
+void Tegra::MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) {
+ ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
+ const s32 jump_address =
+ static_cast<s32>(pc) + static_cast<s32>(opcode.GetBranchTarget() / sizeof(s32));
+
+ Xbyak::Label end;
+ auto value = Compile_GetRegister(opcode.src_a, eax);
+ test(value, value);
+ if (optimizer.has_delayed_pc) {
+ switch (opcode.branch_condition) {
+ case Macro::BranchCondition::Zero:
+ jne(end, T_NEAR);
+ break;
+ case Macro::BranchCondition::NotZero:
+ je(end, T_NEAR);
+ break;
+ }
+
+ if (opcode.branch_annul) {
+ xor_(BRANCH_HOLDER, BRANCH_HOLDER);
+ jmp(labels[jump_address], T_NEAR);
+ } else {
+ Xbyak::Label handle_post_exit{};
+ Xbyak::Label skip{};
+ jmp(skip, T_NEAR);
+ if (opcode.is_exit) {
+ L(handle_post_exit);
+ // Execute 1 instruction
+ mov(BRANCH_HOLDER, end_of_code);
+ // Jump to next instruction to skip delay slot check
+ jmp(labels[jump_address], T_NEAR);
+ } else {
+ L(handle_post_exit);
+ xor_(BRANCH_HOLDER, BRANCH_HOLDER);
+ jmp(labels[jump_address], T_NEAR);
+ }
+ L(skip);
+ mov(BRANCH_HOLDER, handle_post_exit);
+ jmp(delay_skip[pc], T_NEAR);
+ }
+ } else {
+ switch (opcode.branch_condition) {
+ case Macro::BranchCondition::Zero:
+ je(labels[jump_address], T_NEAR);
+ break;
+ case Macro::BranchCondition::NotZero:
+ jne(labels[jump_address], T_NEAR);
+ break;
+ }
+ }
+
+ L(end);
+}
+
+void Tegra::MacroJITx64Impl::Optimizer_ScanFlags() {
+ optimizer.can_skip_carry = true;
+ optimizer.has_delayed_pc = false;
+ for (auto raw_op : code) {
+ Macro::Opcode op{};
+ op.raw = raw_op;
+
+ if (op.operation == Macro::Operation::ALU) {
+ // Scan for any ALU operations which actually use the carry flag, if they don't exist in
+ // our current code we can skip emitting the carry flag handling operations
+ if (op.alu_operation == Macro::ALUOperation::AddWithCarry ||
+ op.alu_operation == Macro::ALUOperation::SubtractWithBorrow) {
+ optimizer.can_skip_carry = false;
+ }
+ }
+
+ if (op.operation == Macro::Operation::Branch) {
+ if (!op.branch_annul) {
+ optimizer.has_delayed_pc = true;
+ }
+ }
+ }
+}
+
+void MacroJITx64Impl::Compile() {
+ MICROPROFILE_SCOPE(MacroJitCompile);
+ labels.fill(Xbyak::Label());
+
+ Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8);
+ // JIT state
+ mov(STATE, Common::X64::ABI_PARAM1);
+ mov(PARAMETERS, Common::X64::ABI_PARAM2);
+ xor_(RESULT, RESULT);
+ xor_(METHOD_ADDRESS, METHOD_ADDRESS);
+ xor_(BRANCH_HOLDER, BRANCH_HOLDER);
+
+ mov(dword[STATE + offsetof(JITState, registers) + 4], Compile_FetchParameter());
+
+ // Track get register for zero registers and mark it as no-op
+ optimizer.zero_reg_skip = true;
+
+ // AddImmediate tends to be used as a NOP instruction, if we detect this we can
+ // completely skip the entire code path and no emit anything
+ optimizer.skip_dummy_addimmediate = true;
+
+ // SMO tends to emit a lot of unnecessary method moves, we can mitigate this by only emitting
+ // one if our register isn't "dirty"
+ optimizer.optimize_for_method_move = true;
+
+ // Enable run-time assertions in JITted code
+ optimizer.enable_asserts = false;
+
+ // Check to see if we can skip emitting certain instructions
+ Optimizer_ScanFlags();
+
+ const u32 op_count = static_cast<u32>(code.size());
+ for (u32 i = 0; i < op_count; i++) {
+ if (i < op_count - 1) {
+ pc = i + 1;
+ next_opcode = GetOpCode();
+ } else {
+ next_opcode = {};
+ }
+ pc = i;
+ Compile_NextInstruction();
+ }
+
+ L(end_of_code);
+
+ Common::X64::ABI_PopRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8);
+ ret();
+ ready();
+ program = getCode<ProgramType>();
+}
+
+bool MacroJITx64Impl::Compile_NextInstruction() {
+ const auto opcode = GetOpCode();
+ if (labels[pc].getAddress()) {
+ return false;
+ }
+
+ L(labels[pc]);
+
+ switch (opcode.operation) {
+ case Macro::Operation::ALU:
+ Compile_ALU(opcode);
+ break;
+ case Macro::Operation::AddImmediate:
+ Compile_AddImmediate(opcode);
+ break;
+ case Macro::Operation::ExtractInsert:
+ Compile_ExtractInsert(opcode);
+ break;
+ case Macro::Operation::ExtractShiftLeftImmediate:
+ Compile_ExtractShiftLeftImmediate(opcode);
+ break;
+ case Macro::Operation::ExtractShiftLeftRegister:
+ Compile_ExtractShiftLeftRegister(opcode);
+ break;
+ case Macro::Operation::Read:
+ Compile_Read(opcode);
+ break;
+ case Macro::Operation::Branch:
+ Compile_Branch(opcode);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented opcode {}", opcode.operation.Value());
+ break;
+ }
+
+ if (optimizer.has_delayed_pc) {
+ if (opcode.is_exit) {
+ mov(rax, end_of_code);
+ test(BRANCH_HOLDER, BRANCH_HOLDER);
+ cmove(BRANCH_HOLDER, rax);
+ // Jump to next instruction to skip delay slot check
+ je(labels[pc + 1], T_NEAR);
+ } else {
+ // TODO(ogniK): Optimize delay slot branching
+ Xbyak::Label no_delay_slot{};
+ test(BRANCH_HOLDER, BRANCH_HOLDER);
+ je(no_delay_slot, T_NEAR);
+ mov(rax, BRANCH_HOLDER);
+ xor_(BRANCH_HOLDER, BRANCH_HOLDER);
+ jmp(rax);
+ L(no_delay_slot);
+ }
+ L(delay_skip[pc]);
+ if (opcode.is_exit) {
+ return false;
+ }
+ } else {
+ test(BRANCH_HOLDER, BRANCH_HOLDER);
+ jne(end_of_code, T_NEAR);
+ if (opcode.is_exit) {
+ inc(BRANCH_HOLDER);
+ return false;
+ }
+ }
+ return true;
+}
+
+Xbyak::Reg32 Tegra::MacroJITx64Impl::Compile_FetchParameter() {
+ mov(eax, dword[PARAMETERS]);
+ add(PARAMETERS, sizeof(u32));
+ return eax;
+}
+
+Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) {
+ if (index == 0) {
+ // Register 0 is always zero
+ xor_(dst, dst);
+ } else {
+ mov(dst, dword[STATE + offsetof(JITState, registers) + index * sizeof(u32)]);
+ }
+
+ return dst;
+}
+
+void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) {
+ const auto SetRegister = [this](u32 reg, const Xbyak::Reg32& result) {
+ // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero
+ // register.
+ if (reg == 0) {
+ return;
+ }
+ mov(dword[STATE + offsetof(JITState, registers) + reg * sizeof(u32)], result);
+ };
+ const auto SetMethodAddress = [this](const Xbyak::Reg32& reg) { mov(METHOD_ADDRESS, reg); };
+
+ switch (operation) {
+ case Macro::ResultOperation::IgnoreAndFetch:
+ SetRegister(reg, Compile_FetchParameter());
+ break;
+ case Macro::ResultOperation::Move:
+ SetRegister(reg, RESULT);
+ break;
+ case Macro::ResultOperation::MoveAndSetMethod:
+ SetRegister(reg, RESULT);
+ SetMethodAddress(RESULT);
+ break;
+ case Macro::ResultOperation::FetchAndSend:
+ // Fetch parameter and send result.
+ SetRegister(reg, Compile_FetchParameter());
+ Compile_Send(RESULT);
+ break;
+ case Macro::ResultOperation::MoveAndSend:
+ // Move and send result.
+ SetRegister(reg, RESULT);
+ Compile_Send(RESULT);
+ break;
+ case Macro::ResultOperation::FetchAndSetMethod:
+ // Fetch parameter and use result as Method Address.
+ SetRegister(reg, Compile_FetchParameter());
+ SetMethodAddress(RESULT);
+ break;
+ case Macro::ResultOperation::MoveAndSetMethodFetchAndSend:
+ // Move result and use as Method Address, then fetch and send parameter.
+ SetRegister(reg, RESULT);
+ SetMethodAddress(RESULT);
+ Compile_Send(Compile_FetchParameter());
+ break;
+ case Macro::ResultOperation::MoveAndSetMethodSend:
+ // Move result and use as Method Address, then send bits 12:17 of result.
+ SetRegister(reg, RESULT);
+ SetMethodAddress(RESULT);
+ shr(RESULT, 12);
+ and_(RESULT, 0b111111);
+ Compile_Send(RESULT);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented macro operation {}", static_cast<std::size_t>(operation));
+ }
+}
+
+Macro::Opcode MacroJITx64Impl::GetOpCode() const {
+ ASSERT(pc < code.size());
+ return {code[pc]};
+}
+
+std::bitset<32> MacroJITx64Impl::PersistentCallerSavedRegs() const {
+ return PERSISTENT_REGISTERS & Common::X64::ABI_ALL_CALLER_SAVED;
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h
new file mode 100644
index 000000000..a180e7428
--- /dev/null
+++ b/src/video_core/macro/macro_jit_x64.h
@@ -0,0 +1,98 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <bitset>
+#include <xbyak.h>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/x64/xbyak_abi.h"
+#include "video_core/macro/macro.h"
+
+namespace Tegra {
+
+namespace Engines {
+class Maxwell3D;
+}
+
+/// MAX_CODE_SIZE is arbitrarily chosen based on current booting games
+constexpr size_t MAX_CODE_SIZE = 0x10000;
+
+class MacroJITx64 final : public MacroEngine {
+public:
+ explicit MacroJITx64(Engines::Maxwell3D& maxwell3d);
+
+protected:
+ std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override;
+
+private:
+ Engines::Maxwell3D& maxwell3d;
+};
+
+class MacroJITx64Impl : public Xbyak::CodeGenerator, public CachedMacro {
+public:
+ MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code);
+ ~MacroJITx64Impl();
+
+ void Execute(const std::vector<u32>& parameters, u32 method) override;
+
+ void Compile_ALU(Macro::Opcode opcode);
+ void Compile_AddImmediate(Macro::Opcode opcode);
+ void Compile_ExtractInsert(Macro::Opcode opcode);
+ void Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode);
+ void Compile_ExtractShiftLeftRegister(Macro::Opcode opcode);
+ void Compile_Read(Macro::Opcode opcode);
+ void Compile_Branch(Macro::Opcode opcode);
+
+private:
+ void Optimizer_ScanFlags();
+
+ void Compile();
+ bool Compile_NextInstruction();
+
+ Xbyak::Reg32 Compile_FetchParameter();
+ Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst);
+
+ void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg);
+ void Compile_Send(Xbyak::Reg32 value);
+
+ Macro::Opcode GetOpCode() const;
+ std::bitset<32> PersistentCallerSavedRegs() const;
+
+ struct JITState {
+ Engines::Maxwell3D* maxwell3d{};
+ std::array<u32, Macro::NUM_MACRO_REGISTERS> registers{};
+ u32 carry_flag{};
+ };
+ static_assert(offsetof(JITState, maxwell3d) == 0, "Maxwell3D is not at 0x0");
+ using ProgramType = void (*)(JITState*, const u32*);
+
+ struct OptimizerState {
+ bool can_skip_carry{};
+ bool has_delayed_pc{};
+ bool zero_reg_skip{};
+ bool skip_dummy_addimmediate{};
+ bool optimize_for_method_move{};
+ bool enable_asserts{};
+ };
+ OptimizerState optimizer{};
+
+ std::optional<Macro::Opcode> next_opcode{};
+ ProgramType program{nullptr};
+
+ std::array<Xbyak::Label, MAX_CODE_SIZE> labels;
+ std::array<Xbyak::Label, MAX_CODE_SIZE> delay_skip;
+ Xbyak::Label end_of_code{};
+
+ bool is_delay_slot{};
+ u32 pc{};
+ std::optional<u32> delayed_pc;
+
+ const std::vector<u32>& code;
+ Engines::Maxwell3D& maxwell3d;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
deleted file mode 100644
index 42031d80a..000000000
--- a/src/video_core/macro_interpreter.cpp
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/microprofile.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/macro_interpreter.h"
-
-MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192));
-
-namespace Tegra {
-namespace {
-enum class Operation : u32 {
- ALU = 0,
- AddImmediate = 1,
- ExtractInsert = 2,
- ExtractShiftLeftImmediate = 3,
- ExtractShiftLeftRegister = 4,
- Read = 5,
- Unused = 6, // This operation doesn't seem to be a valid encoding.
- Branch = 7,
-};
-} // Anonymous namespace
-
-enum class MacroInterpreter::ALUOperation : u32 {
- Add = 0,
- AddWithCarry = 1,
- Subtract = 2,
- SubtractWithBorrow = 3,
- // Operations 4-7 don't seem to be valid encodings.
- Xor = 8,
- Or = 9,
- And = 10,
- AndNot = 11,
- Nand = 12
-};
-
-enum class MacroInterpreter::ResultOperation : u32 {
- IgnoreAndFetch = 0,
- Move = 1,
- MoveAndSetMethod = 2,
- FetchAndSend = 3,
- MoveAndSend = 4,
- FetchAndSetMethod = 5,
- MoveAndSetMethodFetchAndSend = 6,
- MoveAndSetMethodSend = 7
-};
-
-enum class MacroInterpreter::BranchCondition : u32 {
- Zero = 0,
- NotZero = 1,
-};
-
-union MacroInterpreter::Opcode {
- u32 raw;
- BitField<0, 3, Operation> operation;
- BitField<4, 3, ResultOperation> result_operation;
- BitField<4, 1, BranchCondition> branch_condition;
- // If set on a branch, then the branch doesn't have a delay slot.
- BitField<5, 1, u32> branch_annul;
- BitField<7, 1, u32> is_exit;
- BitField<8, 3, u32> dst;
- BitField<11, 3, u32> src_a;
- BitField<14, 3, u32> src_b;
- // The signed immediate overlaps the second source operand and the alu operation.
- BitField<14, 18, s32> immediate;
-
- BitField<17, 5, ALUOperation> alu_operation;
-
- // Bitfield instructions data
- BitField<17, 5, u32> bf_src_bit;
- BitField<22, 5, u32> bf_size;
- BitField<27, 5, u32> bf_dst_bit;
-
- u32 GetBitfieldMask() const {
- return (1 << bf_size) - 1;
- }
-
- s32 GetBranchTarget() const {
- return static_cast<s32>(immediate * sizeof(u32));
- }
-};
-
-MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
-
-void MacroInterpreter::Execute(u32 offset, std::size_t num_parameters, const u32* parameters) {
- MICROPROFILE_SCOPE(MacroInterp);
- Reset();
-
- registers[1] = parameters[0];
-
- if (num_parameters > parameters_capacity) {
- parameters_capacity = num_parameters;
- this->parameters = std::make_unique<u32[]>(num_parameters);
- }
- std::memcpy(this->parameters.get(), parameters, num_parameters * sizeof(u32));
- this->num_parameters = num_parameters;
-
- // Execute the code until we hit an exit condition.
- bool keep_executing = true;
- while (keep_executing) {
- keep_executing = Step(offset, false);
- }
-
- // Assert the the macro used all the input parameters
- ASSERT(next_parameter_index == num_parameters);
-}
-
-void MacroInterpreter::Reset() {
- registers = {};
- pc = 0;
- delayed_pc = {};
- method_address.raw = 0;
- num_parameters = 0;
- // The next parameter index starts at 1, because $r1 already has the value of the first
- // parameter.
- next_parameter_index = 1;
- carry_flag = false;
-}
-
-bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) {
- u32 base_address = pc;
-
- Opcode opcode = GetOpcode(offset);
- pc += 4;
-
- // Update the program counter if we were delayed
- if (delayed_pc) {
- ASSERT(is_delay_slot);
- pc = *delayed_pc;
- delayed_pc = {};
- }
-
- switch (opcode.operation) {
- case Operation::ALU: {
- u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a),
- GetRegister(opcode.src_b));
- ProcessResult(opcode.result_operation, opcode.dst, result);
- break;
- }
- case Operation::AddImmediate: {
- ProcessResult(opcode.result_operation, opcode.dst,
- GetRegister(opcode.src_a) + opcode.immediate);
- break;
- }
- case Operation::ExtractInsert: {
- u32 dst = GetRegister(opcode.src_a);
- u32 src = GetRegister(opcode.src_b);
-
- src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
- dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
- dst |= src << opcode.bf_dst_bit;
- ProcessResult(opcode.result_operation, opcode.dst, dst);
- break;
- }
- case Operation::ExtractShiftLeftImmediate: {
- u32 dst = GetRegister(opcode.src_a);
- u32 src = GetRegister(opcode.src_b);
-
- u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
-
- ProcessResult(opcode.result_operation, opcode.dst, result);
- break;
- }
- case Operation::ExtractShiftLeftRegister: {
- u32 dst = GetRegister(opcode.src_a);
- u32 src = GetRegister(opcode.src_b);
-
- u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
-
- ProcessResult(opcode.result_operation, opcode.dst, result);
- break;
- }
- case Operation::Read: {
- u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate);
- ProcessResult(opcode.result_operation, opcode.dst, result);
- break;
- }
- case Operation::Branch: {
- ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
- u32 value = GetRegister(opcode.src_a);
- bool taken = EvaluateBranchCondition(opcode.branch_condition, value);
- if (taken) {
- // Ignore the delay slot if the branch has the annul bit.
- if (opcode.branch_annul) {
- pc = base_address + opcode.GetBranchTarget();
- return true;
- }
-
- delayed_pc = base_address + opcode.GetBranchTarget();
- // Execute one more instruction due to the delay slot.
- return Step(offset, true);
- }
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented macro operation {}",
- static_cast<u32>(opcode.operation.Value()));
- }
-
- // An instruction with the Exit flag will not actually
- // cause an exit if it's executed inside a delay slot.
- if (opcode.is_exit && !is_delay_slot) {
- // Exit has a delay slot, execute the next instruction
- Step(offset, true);
- return false;
- }
-
- return true;
-}
-
-MacroInterpreter::Opcode MacroInterpreter::GetOpcode(u32 offset) const {
- const auto& macro_memory{maxwell3d.GetMacroMemory()};
- ASSERT((pc % sizeof(u32)) == 0);
- ASSERT((pc + offset) < macro_memory.size() * sizeof(u32));
- return {macro_memory[offset + pc / sizeof(u32)]};
-}
-
-u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) {
- switch (operation) {
- case ALUOperation::Add: {
- const u64 result{static_cast<u64>(src_a) + src_b};
- carry_flag = result > 0xffffffff;
- return static_cast<u32>(result);
- }
- case ALUOperation::AddWithCarry: {
- const u64 result{static_cast<u64>(src_a) + src_b + (carry_flag ? 1ULL : 0ULL)};
- carry_flag = result > 0xffffffff;
- return static_cast<u32>(result);
- }
- case ALUOperation::Subtract: {
- const u64 result{static_cast<u64>(src_a) - src_b};
- carry_flag = result < 0x100000000;
- return static_cast<u32>(result);
- }
- case ALUOperation::SubtractWithBorrow: {
- const u64 result{static_cast<u64>(src_a) - src_b - (carry_flag ? 0ULL : 1ULL)};
- carry_flag = result < 0x100000000;
- return static_cast<u32>(result);
- }
- case ALUOperation::Xor:
- return src_a ^ src_b;
- case ALUOperation::Or:
- return src_a | src_b;
- case ALUOperation::And:
- return src_a & src_b;
- case ALUOperation::AndNot:
- return src_a & ~src_b;
- case ALUOperation::Nand:
- return ~(src_a & src_b);
-
- default:
- UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
- return 0;
- }
-}
-
-void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) {
- switch (operation) {
- case ResultOperation::IgnoreAndFetch:
- // Fetch parameter and ignore result.
- SetRegister(reg, FetchParameter());
- break;
- case ResultOperation::Move:
- // Move result.
- SetRegister(reg, result);
- break;
- case ResultOperation::MoveAndSetMethod:
- // Move result and use as Method Address.
- SetRegister(reg, result);
- SetMethodAddress(result);
- break;
- case ResultOperation::FetchAndSend:
- // Fetch parameter and send result.
- SetRegister(reg, FetchParameter());
- Send(result);
- break;
- case ResultOperation::MoveAndSend:
- // Move and send result.
- SetRegister(reg, result);
- Send(result);
- break;
- case ResultOperation::FetchAndSetMethod:
- // Fetch parameter and use result as Method Address.
- SetRegister(reg, FetchParameter());
- SetMethodAddress(result);
- break;
- case ResultOperation::MoveAndSetMethodFetchAndSend:
- // Move result and use as Method Address, then fetch and send parameter.
- SetRegister(reg, result);
- SetMethodAddress(result);
- Send(FetchParameter());
- break;
- case ResultOperation::MoveAndSetMethodSend:
- // Move result and use as Method Address, then send bits 12:17 of result.
- SetRegister(reg, result);
- SetMethodAddress(result);
- Send((result >> 12) & 0b111111);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation));
- }
-}
-
-u32 MacroInterpreter::FetchParameter() {
- ASSERT(next_parameter_index < num_parameters);
- return parameters[next_parameter_index++];
-}
-
-u32 MacroInterpreter::GetRegister(u32 register_id) const {
- return registers.at(register_id);
-}
-
-void MacroInterpreter::SetRegister(u32 register_id, u32 value) {
- // Register 0 is hardwired as the zero register.
- // Ensure no writes to it actually occur.
- if (register_id == 0) {
- return;
- }
-
- registers.at(register_id) = value;
-}
-
-void MacroInterpreter::SetMethodAddress(u32 address) {
- method_address.raw = address;
-}
-
-void MacroInterpreter::Send(u32 value) {
- maxwell3d.CallMethodFromMME({method_address.address, value});
- // Increment the method address by the method increment.
- method_address.address.Assign(method_address.address.Value() +
- method_address.increment.Value());
-}
-
-u32 MacroInterpreter::Read(u32 method) const {
- return maxwell3d.GetRegisterValue(method);
-}
-
-bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const {
- switch (cond) {
- case BranchCondition::Zero:
- return value == 0;
- case BranchCondition::NotZero:
- return value != 0;
- }
- UNREACHABLE();
- return true;
-}
-
-} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
deleted file mode 100644
index 631146d89..000000000
--- a/src/video_core/macro_interpreter.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <optional>
-
-#include "common/bit_field.h"
-#include "common/common_types.h"
-
-namespace Tegra {
-namespace Engines {
-class Maxwell3D;
-}
-
-class MacroInterpreter final {
-public:
- explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d);
-
- /**
- * Executes the macro code with the specified input parameters.
- * @param offset Offset to start execution at.
- * @param parameters The parameters of the macro.
- */
- void Execute(u32 offset, std::size_t num_parameters, const u32* parameters);
-
-private:
- enum class ALUOperation : u32;
- enum class BranchCondition : u32;
- enum class ResultOperation : u32;
-
- union Opcode;
-
- union MethodAddress {
- u32 raw;
- BitField<0, 12, u32> address;
- BitField<12, 6, u32> increment;
- };
-
- /// Resets the execution engine state, zeroing registers, etc.
- void Reset();
-
- /**
- * Executes a single macro instruction located at the current program counter. Returns whether
- * the interpreter should keep running.
- * @param offset Offset to start execution at.
- * @param is_delay_slot Whether the current step is being executed due to a delay slot in a
- * previous instruction.
- */
- bool Step(u32 offset, bool is_delay_slot);
-
- /// Calculates the result of an ALU operation. src_a OP src_b;
- u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b);
-
- /// Performs the result operation on the input result and stores it in the specified register
- /// (if necessary).
- void ProcessResult(ResultOperation operation, u32 reg, u32 result);
-
- /// Evaluates the branch condition and returns whether the branch should be taken or not.
- bool EvaluateBranchCondition(BranchCondition cond, u32 value) const;
-
- /// Reads an opcode at the current program counter location.
- Opcode GetOpcode(u32 offset) const;
-
- /// Returns the specified register's value. Register 0 is hardcoded to always return 0.
- u32 GetRegister(u32 register_id) const;
-
- /// Sets the register to the input value.
- void SetRegister(u32 register_id, u32 value);
-
- /// Sets the method address to use for the next Send instruction.
- void SetMethodAddress(u32 address);
-
- /// Calls a GPU Engine method with the input parameter.
- void Send(u32 value);
-
- /// Reads a GPU register located at the method address.
- u32 Read(u32 method) const;
-
- /// Returns the next parameter in the parameter queue.
- u32 FetchParameter();
-
- Engines::Maxwell3D& maxwell3d;
-
- /// Current program counter
- u32 pc;
- /// Program counter to execute at after the delay slot is executed.
- std::optional<u32> delayed_pc;
-
- static constexpr std::size_t NumMacroRegisters = 8;
-
- /// General purpose macro registers.
- std::array<u32, NumMacroRegisters> registers = {};
-
- /// Method address to use for the next Send instruction.
- MethodAddress method_address = {};
-
- /// Input parameters of the current macro.
- std::unique_ptr<u32[]> parameters;
- std::size_t num_parameters = 0;
- std::size_t parameters_capacity = 0;
- /// Index of the next parameter that will be fetched by the 'parm' instruction.
- u32 next_parameter_index = 0;
-
- bool carry_flag = false;
-};
-} // namespace Tegra
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index a3389d0d2..6e70bd362 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -4,186 +4,180 @@
#include "common/alignment.h"
#include "common/assert.h"
-#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
namespace Tegra {
-MemoryManager::MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
- : rasterizer{rasterizer}, system{system} {
- std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
- std::fill(page_table.attributes.begin(), page_table.attributes.end(),
- Common::PageType::Unmapped);
- page_table.Resize(address_space_width);
+MemoryManager::MemoryManager(Core::System& system_)
+ : system{system_}, page_table(page_table_size) {}
- // Initialize the map with a single free region covering the entire managed space.
- VirtualMemoryArea initial_vma;
- initial_vma.size = address_space_end;
- vma_map.emplace(initial_vma.base, initial_vma);
+MemoryManager::~MemoryManager() = default;
- UpdatePageTableForVMA(initial_vma);
+void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
}
-MemoryManager::~MemoryManager() = default;
+GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
+ u64 remaining_size{size};
+ for (u64 offset{}; offset < size; offset += page_size) {
+ if (remaining_size < page_size) {
+ SetPageEntry(gpu_addr + offset, page_entry + offset, remaining_size);
+ } else {
+ SetPageEntry(gpu_addr + offset, page_entry + offset);
+ }
+ remaining_size -= page_size;
+ }
+ return gpu_addr;
+}
-GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
+GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) {
+ return UpdateRange(gpu_addr, cpu_addr, size);
+}
- AllocateMemory(gpu_addr, 0, aligned_size);
+GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align) {
+ return Map(cpu_addr, *FindFreeRange(size, align), size);
+}
- return gpu_addr;
+GPUVAddr MemoryManager::MapAllocate32(VAddr cpu_addr, std::size_t size) {
+ const std::optional<GPUVAddr> gpu_addr = FindFreeRange(size, 1, true);
+ ASSERT(gpu_addr);
+ return Map(cpu_addr, *gpu_addr, size);
}
-GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
+void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
+ if (!size) {
+ return;
+ }
- AllocateMemory(gpu_addr, 0, aligned_size);
+ // Flush and invalidate through the GPU interface, to be asynchronous if possible.
+ system.GPU().FlushAndInvalidateRegion(*GpuToCpuAddress(gpu_addr), size);
- return gpu_addr;
+ UpdateRange(gpu_addr, PageEntry::State::Unmapped, size);
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
-
- MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr);
- ASSERT(system.CurrentProcess()
- ->VMManager()
- .SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped,
- Kernel::MemoryAttribute::DeviceMapped)
- .IsSuccess());
+std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) {
+ for (u64 offset{}; offset < size; offset += page_size) {
+ if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) {
+ return std::nullopt;
+ }
+ }
- return gpu_addr;
+ return UpdateRange(gpu_addr, PageEntry::State::Allocated, size);
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & page_mask) == 0);
+GPUVAddr MemoryManager::Allocate(std::size_t size, std::size_t align) {
+ return *AllocateFixed(*FindFreeRange(size, align), size);
+}
- const u64 aligned_size{Common::AlignUp(size, page_size)};
+void MemoryManager::TryLockPage(PageEntry page_entry, std::size_t size) {
+ if (!page_entry.IsValid()) {
+ return;
+ }
- MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr);
ASSERT(system.CurrentProcess()
- ->VMManager()
- .SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped,
- Kernel::MemoryAttribute::DeviceMapped)
+ ->PageTable()
+ .LockForDeviceAddressSpace(page_entry.ToAddress(), size)
.IsSuccess());
- return gpu_addr;
}
-GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & page_mask) == 0);
-
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const auto cpu_addr = GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr);
-
- // Flush and invalidate through the GPU interface, to be asynchronous if possible.
- system.GPU().FlushAndInvalidateRegion(*cpu_addr, aligned_size);
+void MemoryManager::TryUnlockPage(PageEntry page_entry, std::size_t size) {
+ if (!page_entry.IsValid()) {
+ return;
+ }
- UnmapRange(gpu_addr, aligned_size);
ASSERT(system.CurrentProcess()
- ->VMManager()
- .SetMemoryAttribute(cpu_addr.value(), size, Kernel::MemoryAttribute::DeviceMapped,
- Kernel::MemoryAttribute::None)
+ ->PageTable()
+ .UnlockForDeviceAddressSpace(page_entry.ToAddress(), size)
.IsSuccess());
+}
- return gpu_addr;
+PageEntry MemoryManager::GetPageEntry(GPUVAddr gpu_addr) const {
+ return page_table[PageEntryIndex(gpu_addr)];
}
-GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) const {
- // Find the first Free VMA.
- const VMAHandle vma_handle{
- std::find_if(vma_map.begin(), vma_map.end(), [region_start, size](const auto& vma) {
- if (vma.second.type != VirtualMemoryArea::Type::Unmapped) {
- return false;
- }
+void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
+ // TODO(bunnei): We should lock/unlock device regions. This currently causes issues due to
+ // improper tracking, but should be fixed in the future.
- const VAddr vma_end{vma.second.base + vma.second.size};
- return vma_end > region_start && vma_end >= region_start + size;
- })};
+ //// Unlock the old page
+ // TryUnlockPage(page_table[PageEntryIndex(gpu_addr)], size);
- if (vma_handle == vma_map.end()) {
- return {};
- }
+ //// Lock the new page
+ // TryLockPage(page_entry, size);
- return std::max(region_start, vma_handle->second.base);
+ page_table[PageEntryIndex(gpu_addr)] = page_entry;
}
-bool MemoryManager::IsAddressValid(GPUVAddr addr) const {
- return (addr >> page_bits) < page_table.pointers.size();
-}
+std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align,
+ bool start_32bit_address) const {
+ if (!align) {
+ align = page_size;
+ } else {
+ align = Common::AlignUp(align, page_size);
+ }
-std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
- return {};
+ u64 available_size{};
+ GPUVAddr gpu_addr{start_32bit_address ? address_space_start_low : address_space_start};
+ while (gpu_addr + available_size < address_space_size) {
+ if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) {
+ available_size += page_size;
+
+ if (available_size >= size) {
+ return gpu_addr;
+ }
+ } else {
+ gpu_addr += available_size + page_size;
+ available_size = 0;
+
+ const auto remainder{gpu_addr % align};
+ if (remainder) {
+ gpu_addr = (gpu_addr - remainder) + align;
+ }
+ }
}
- const VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]};
- if (cpu_addr) {
- return cpu_addr + (addr & page_mask);
+ return std::nullopt;
+}
+
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
+ const auto page_entry{GetPageEntry(gpu_addr)};
+ if (!page_entry.IsValid()) {
+ return std::nullopt;
}
- return {};
+ return page_entry.ToAddress() + (gpu_addr & page_mask);
}
template <typename T>
T MemoryManager::Read(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
- return {};
- }
-
- const u8* page_pointer{GetPointer(addr)};
- if (page_pointer) {
+ if (auto page_pointer{GetPointer(addr)}; page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
T value;
std::memcpy(&value, page_pointer, sizeof(T));
return value;
}
- switch (page_table.attributes[addr >> page_bits]) {
- case Common::PageType::Unmapped:
- LOG_ERROR(HW_GPU, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, addr);
- return 0;
- case Common::PageType::Memory:
- ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
- break;
- default:
- UNREACHABLE();
- }
+ UNREACHABLE();
+
return {};
}
template <typename T>
void MemoryManager::Write(GPUVAddr addr, T data) {
- if (!IsAddressValid(addr)) {
- return;
- }
-
- u8* page_pointer{GetPointer(addr)};
- if (page_pointer) {
+ if (auto page_pointer{GetPointer(addr)}; page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
std::memcpy(page_pointer, &data, sizeof(T));
return;
}
- switch (page_table.attributes[addr >> page_bits]) {
- case Common::PageType::Unmapped:
- LOG_ERROR(HW_GPU, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
- static_cast<u32>(data), addr);
- return;
- case Common::PageType::Memory:
- ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
- break;
- default:
- UNREACHABLE();
- }
+ UNREACHABLE();
}
template u8 MemoryManager::Read<u8>(GPUVAddr addr) const;
@@ -195,71 +189,48 @@ template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data);
template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data);
template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data);
-u8* MemoryManager::GetPointer(GPUVAddr addr) {
- if (!IsAddressValid(addr)) {
+u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) {
+ if (!GetPageEntry(gpu_addr).IsValid()) {
return {};
}
- auto& memory = system.Memory();
-
- const VAddr page_addr{page_table.backing_addr[addr >> page_bits]};
-
- if (page_addr != 0) {
- return memory.GetPointer(page_addr + (addr & page_mask));
+ const auto address{GpuToCpuAddress(gpu_addr)};
+ if (!address) {
+ return {};
}
- LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
- return {};
+ return system.Memory().GetPointer(*address);
}
-const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
+const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const {
+ if (!GetPageEntry(gpu_addr).IsValid()) {
return {};
}
- const auto& memory = system.Memory();
-
- const VAddr page_addr{page_table.backing_addr[addr >> page_bits]};
-
- if (page_addr != 0) {
- return memory.GetPointer(page_addr + (addr & page_mask));
+ const auto address{GpuToCpuAddress(gpu_addr)};
+ if (!address) {
+ return {};
}
- LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
- return {};
-}
-
-bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const {
- const std::size_t inner_size = size - 1;
- const GPUVAddr end = start + inner_size;
- const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
- const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
- const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
- return range == inner_size;
+ return system.Memory().GetPointer(*address);
}
-void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const {
+void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const {
std::size_t remaining_size{size};
- std::size_t page_index{src_addr >> page_bits};
- std::size_t page_offset{src_addr & page_mask};
-
- auto& memory = system.Memory();
+ std::size_t page_index{gpu_src_addr >> page_bits};
+ std::size_t page_offset{gpu_src_addr & page_mask};
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- switch (page_table.attributes[page_index]) {
- case Common::PageType::Memory: {
- const VAddr src_addr{page_table.backing_addr[page_index] + page_offset};
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto src_addr{*page_addr + page_offset};
+
// Flush must happen on the rasterizer interface, such that memory is always synchronous
// when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu.
- rasterizer.FlushRegion(src_addr, copy_amount);
- memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
- break;
- }
- default:
- UNREACHABLE();
+ rasterizer->FlushRegion(src_addr, copy_amount);
+ system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
}
page_index++;
@@ -269,24 +240,23 @@ void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::s
}
}
-void MemoryManager::ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer,
+void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer,
const std::size_t size) const {
std::size_t remaining_size{size};
- std::size_t page_index{src_addr >> page_bits};
- std::size_t page_offset{src_addr & page_mask};
-
- auto& memory = system.Memory();
+ std::size_t page_index{gpu_src_addr >> page_bits};
+ std::size_t page_offset{gpu_src_addr & page_mask};
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const u8* page_pointer = page_table.pointers[page_index];
- if (page_pointer) {
- const VAddr src_addr{page_table.backing_addr[page_index] + page_offset};
- memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
+
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto src_addr{*page_addr + page_offset};
+ system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
} else {
std::memset(dest_buffer, 0, copy_amount);
}
+
page_index++;
page_offset = 0;
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
@@ -294,28 +264,22 @@ void MemoryManager::ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer,
}
}
-void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size) {
+void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) {
std::size_t remaining_size{size};
- std::size_t page_index{dest_addr >> page_bits};
- std::size_t page_offset{dest_addr & page_mask};
-
- auto& memory = system.Memory();
+ std::size_t page_index{gpu_dest_addr >> page_bits};
+ std::size_t page_offset{gpu_dest_addr & page_mask};
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- switch (page_table.attributes[page_index]) {
- case Common::PageType::Memory: {
- const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset};
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto dest_addr{*page_addr + page_offset};
+
// Invalidate must happen on the rasterizer interface, such that memory is always
// synchronous when it is written (even when in asynchronous GPU mode).
- rasterizer.InvalidateRegion(dest_addr, copy_amount);
- memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
- break;
- }
- default:
- UNREACHABLE();
+ rasterizer->InvalidateRegion(dest_addr, copy_amount);
+ system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
}
page_index++;
@@ -325,22 +289,21 @@ void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const
}
}
-void MemoryManager::WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer,
- const std::size_t size) {
+void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer,
+ std::size_t size) {
std::size_t remaining_size{size};
- std::size_t page_index{dest_addr >> page_bits};
- std::size_t page_offset{dest_addr & page_mask};
-
- auto& memory = system.Memory();
+ std::size_t page_index{gpu_dest_addr >> page_bits};
+ std::size_t page_offset{gpu_dest_addr & page_mask};
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- u8* page_pointer = page_table.pointers[page_index];
- if (page_pointer) {
- const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset};
- memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
+
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto dest_addr{*page_addr + page_offset};
+ system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
}
+
page_index++;
page_offset = 0;
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
@@ -348,270 +311,26 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer,
}
}
-void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
+void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size) {
std::vector<u8> tmp_buffer(size);
- ReadBlock(src_addr, tmp_buffer.data(), size);
- WriteBlock(dest_addr, tmp_buffer.data(), size);
+ ReadBlock(gpu_src_addr, tmp_buffer.data(), size);
+ WriteBlock(gpu_dest_addr, tmp_buffer.data(), size);
}
-void MemoryManager::CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
+void MemoryManager::CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr,
+ std::size_t size) {
std::vector<u8> tmp_buffer(size);
- ReadBlockUnsafe(src_addr, tmp_buffer.data(), size);
- WriteBlockUnsafe(dest_addr, tmp_buffer.data(), size);
-}
-
-bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) {
- const VAddr addr = page_table.backing_addr[gpu_addr >> page_bits];
- const std::size_t page = (addr & Memory::PAGE_MASK) + size;
- return page <= Memory::PAGE_SIZE;
-}
-
-void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
- VAddr backing_addr) {
- LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size,
- (base + size) * page_size);
-
- const VAddr end{base + size};
- ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
- base + page_table.pointers.size());
-
- std::fill(page_table.attributes.begin() + base, page_table.attributes.begin() + end, type);
-
- if (memory == nullptr) {
- std::fill(page_table.pointers.begin() + base, page_table.pointers.begin() + end, memory);
- std::fill(page_table.backing_addr.begin() + base, page_table.backing_addr.begin() + end,
- backing_addr);
- } else {
- while (base != end) {
- page_table.pointers[base] = memory;
- page_table.backing_addr[base] = backing_addr;
-
- base += 1;
- memory += page_size;
- backing_addr += page_size;
- }
- }
-}
-
-void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
- MapPages(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr);
-}
-
-void MemoryManager::UnmapRegion(GPUVAddr base, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
- MapPages(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped);
-}
-
-bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
- ASSERT(base + size == next.base);
- if (type != next.type) {
- return {};
- }
- if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) {
- return {};
- }
- if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) {
- return {};
- }
- return true;
-}
-
-MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const {
- if (target >= address_space_end) {
- return vma_map.end();
- } else {
- return std::prev(vma_map.upper_bound(target));
- }
-}
-
-MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) {
- VirtualMemoryArea& vma{vma_handle->second};
-
- vma.type = VirtualMemoryArea::Type::Allocated;
- vma.backing_addr = 0;
- vma.backing_memory = {};
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(vma_handle);
-}
-
-MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset,
- u64 size) {
-
- // This is the appropriately sized VMA that will turn into our allocation.
- VMAIter vma_handle{CarveVMA(target, size)};
- VirtualMemoryArea& vma{vma_handle->second};
-
- ASSERT(vma.size == size);
-
- vma.offset = offset;
-
- return Allocate(vma_handle);
-}
-
-MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size,
- VAddr backing_addr) {
- // This is the appropriately sized VMA that will turn into our allocation.
- VMAIter vma_handle{CarveVMA(target, size)};
- VirtualMemoryArea& vma{vma_handle->second};
-
- ASSERT(vma.size == size);
-
- vma.type = VirtualMemoryArea::Type::Mapped;
- vma.backing_memory = memory;
- vma.backing_addr = backing_addr;
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(vma_handle);
-}
-
-void MemoryManager::UnmapRange(GPUVAddr target, u64 size) {
- VMAIter vma{CarveVMARange(target, size)};
- const VAddr target_end{target + size};
- const VMAIter end{vma_map.end()};
-
- // The comparison against the end of the range must be done using addresses since VMAs can be
- // merged during this process, causing invalidation of the iterators.
- while (vma != end && vma->second.base < target_end) {
- // Unmapped ranges return to allocated state and can be reused
- // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games
- vma = std::next(Allocate(vma));
- }
-
- ASSERT(FindVMA(target)->second.size >= size);
-}
-
-MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) {
- // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
- // non-const access to its container.
- return vma_map.erase(iter, iter); // Erases an empty range of elements
-}
-
-MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base);
-
- VMAIter vma_handle{StripIterConstness(FindVMA(base))};
- if (vma_handle == vma_map.end()) {
- // Target address is outside the managed range
- return {};
- }
-
- const VirtualMemoryArea& vma{vma_handle->second};
- if (vma.type == VirtualMemoryArea::Type::Mapped) {
- // Region is already allocated
- return vma_handle;
- }
-
- const VAddr start_in_vma{base - vma.base};
- const VAddr end_in_vma{start_in_vma + size};
-
- ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}",
- vma.size, end_in_vma);
-
- if (end_in_vma < vma.size) {
- // Split VMA at the end of the allocated region
- SplitVMA(vma_handle, end_in_vma);
- }
- if (start_in_vma != 0) {
- // Split VMA at the start of the allocated region
- vma_handle = SplitVMA(vma_handle, start_in_vma);
- }
-
- return vma_handle;
-}
-
-MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target);
-
- const VAddr target_end{target + size};
- ASSERT(target_end >= target);
- ASSERT(size > 0);
-
- VMAIter begin_vma{StripIterConstness(FindVMA(target))};
- const VMAIter i_end{vma_map.lower_bound(target_end)};
- if (std::any_of(begin_vma, i_end, [](const auto& entry) {
- return entry.second.type == VirtualMemoryArea::Type::Unmapped;
- })) {
- return {};
- }
-
- if (target != begin_vma->second.base) {
- begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
- }
-
- VMAIter end_vma{StripIterConstness(FindVMA(target_end))};
- if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
- end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
- }
-
- return begin_vma;
-}
-
-MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
- VirtualMemoryArea& old_vma{vma_handle->second};
- VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA
-
- // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
- // a bug. This restriction might be removed later.
- ASSERT(offset_in_vma < old_vma.size);
- ASSERT(offset_in_vma > 0);
-
- old_vma.size = offset_in_vma;
- new_vma.base += offset_in_vma;
- new_vma.size -= offset_in_vma;
-
- switch (new_vma.type) {
- case VirtualMemoryArea::Type::Unmapped:
- break;
- case VirtualMemoryArea::Type::Allocated:
- new_vma.offset += offset_in_vma;
- break;
- case VirtualMemoryArea::Type::Mapped:
- new_vma.backing_memory += offset_in_vma;
- break;
- }
-
- ASSERT(old_vma.CanBeMergedWith(new_vma));
-
- return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
-}
-
-MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) {
- const VMAIter next_vma{std::next(iter)};
- if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
- iter->second.size += next_vma->second.size;
- vma_map.erase(next_vma);
- }
-
- if (iter != vma_map.begin()) {
- VMAIter prev_vma{std::prev(iter)};
- if (prev_vma->second.CanBeMergedWith(iter->second)) {
- prev_vma->second.size += iter->second.size;
- vma_map.erase(iter);
- iter = prev_vma;
- }
- }
-
- return iter;
+ ReadBlockUnsafe(gpu_src_addr, tmp_buffer.data(), size);
+ WriteBlockUnsafe(gpu_dest_addr, tmp_buffer.data(), size);
}
-void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
- switch (vma.type) {
- case VirtualMemoryArea::Type::Unmapped:
- UnmapRegion(vma.base, vma.size);
- break;
- case VirtualMemoryArea::Type::Allocated:
- MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr);
- break;
- case VirtualMemoryArea::Type::Mapped:
- MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr);
- break;
+bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
+ const auto cpu_addr{GpuToCpuAddress(gpu_addr)};
+ if (!cpu_addr) {
+ return false;
}
+ const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size};
+ return page <= Core::Memory::PAGE_SIZE;
}
} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 0d9468535..c078193d9 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -6,9 +6,9 @@
#include <map>
#include <optional>
+#include <vector>
#include "common/common_types.h"
-#include "common/page_table.h"
namespace VideoCore {
class RasterizerInterface;
@@ -20,58 +20,70 @@ class System;
namespace Tegra {
-/**
- * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
- * with homogeneous attributes across its extents. In this particular implementation each VMA is
- * also backed by a single host memory allocation.
- */
-struct VirtualMemoryArea {
- enum class Type : u8 {
- Unmapped,
- Allocated,
- Mapped,
+class PageEntry final {
+public:
+ enum class State : u32 {
+ Unmapped = static_cast<u32>(-1),
+ Allocated = static_cast<u32>(-2),
};
- /// Virtual base address of the region.
- GPUVAddr base{};
- /// Size of the region.
- u64 size{};
- /// Memory area mapping type.
- Type type{Type::Unmapped};
- /// CPU memory mapped address corresponding to this memory area.
- VAddr backing_addr{};
- /// Offset into the backing_memory the mapping starts from.
- std::size_t offset{};
- /// Pointer backing this VMA.
- u8* backing_memory{};
-
- /// Tests if this area can be merged to the right with `next`.
- bool CanBeMergedWith(const VirtualMemoryArea& next) const;
+ constexpr PageEntry() = default;
+ constexpr PageEntry(State state) : state{state} {}
+ constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {}
+
+ [[nodiscard]] constexpr bool IsUnmapped() const {
+ return state == State::Unmapped;
+ }
+
+ [[nodiscard]] constexpr bool IsAllocated() const {
+ return state == State::Allocated;
+ }
+
+ [[nodiscard]] constexpr bool IsValid() const {
+ return !IsUnmapped() && !IsAllocated();
+ }
+
+ [[nodiscard]] constexpr VAddr ToAddress() const {
+ if (!IsValid()) {
+ return {};
+ }
+
+ return static_cast<VAddr>(state) << ShiftBits;
+ }
+
+ [[nodiscard]] constexpr PageEntry operator+(u64 offset) const {
+ // If this is a reserved value, offsets do not apply
+ if (!IsValid()) {
+ return *this;
+ }
+ return PageEntry{(static_cast<VAddr>(state) << ShiftBits) + offset};
+ }
+
+private:
+ static constexpr std::size_t ShiftBits{12};
+
+ State state{State::Unmapped};
};
+static_assert(sizeof(PageEntry) == 4, "PageEntry is too large");
class MemoryManager final {
public:
- explicit MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer);
+ explicit MemoryManager(Core::System& system);
~MemoryManager();
- GPUVAddr AllocateSpace(u64 size, u64 align);
- GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
- GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
- GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size);
- GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size);
- std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
+ /// Binds a renderer to the memory manager.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+
+ [[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
template <typename T>
- T Read(GPUVAddr addr) const;
+ [[nodiscard]] T Read(GPUVAddr addr) const;
template <typename T>
void Write(GPUVAddr addr, T data);
- u8* GetPointer(GPUVAddr addr);
- const u8* GetPointer(GPUVAddr addr) const;
-
- /// Returns true if the block is continuous in host memory, false otherwise
- bool IsBlockContinuous(GPUVAddr start, std::size_t size) const;
+ [[nodiscard]] u8* GetPointer(GPUVAddr addr);
+ [[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
/**
* ReadBlock and WriteBlock are full read and write operations over virtual
@@ -79,9 +91,9 @@ public:
* in the Host Memory counterpart. Note: This functions cause Host GPU Memory
* Flushes and Invalidations, respectively to each operation.
*/
- void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
- void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
- void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
+ void ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
+ void WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
+ void CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size);
/**
* ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and
@@ -93,97 +105,51 @@ public:
* WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture
* being flushed.
*/
- void ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
- void WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
- void CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
-
- /**
- * IsGranularRange checks if a gpu region can be simply read with a pointer
- */
- bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size);
-
-private:
- using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
- using VMAHandle = VMAMap::const_iterator;
- using VMAIter = VMAMap::iterator;
-
- bool IsAddressValid(GPUVAddr addr) const;
- void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
- VAddr backing_addr = 0);
- void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr);
- void UnmapRegion(GPUVAddr base, u64 size);
-
- /// Finds the VMA in which the given address is included in, or `vma_map.end()`.
- VMAHandle FindVMA(GPUVAddr target) const;
-
- VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size);
-
- /**
- * Maps an unmanaged host memory pointer at a given address.
- *
- * @param target The guest address to start the mapping at.
- * @param memory The memory to be mapped.
- * @param size Size of the mapping in bytes.
- * @param backing_addr The base address of the range to back this mapping.
- */
- VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr);
-
- /// Unmaps a range of addresses, splitting VMAs as necessary.
- void UnmapRange(GPUVAddr target, u64 size);
-
- /// Converts a VMAHandle to a mutable VMAIter.
- VMAIter StripIterConstness(const VMAHandle& iter);
-
- /// Marks as the specified VMA as allocated.
- VMAIter Allocate(VMAIter vma);
+ void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
+ void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
+ void CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size);
/**
- * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
- * the appropriate error checking.
+ * IsGranularRange checks if a gpu region can be simply read with a pointer.
*/
- VMAIter CarveVMA(GPUVAddr base, u64 size);
+ [[nodiscard]] bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const;
- /**
- * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
- * end of the range.
- */
- VMAIter CarveVMARange(GPUVAddr base, u64 size);
-
- /**
- * Splits a VMA in two, at the specified offset.
- * @returns the right side of the split, with the original iterator becoming the left side.
- */
- VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma);
-
- /**
- * Checks for and merges the specified VMA with adjacent ones if possible.
- * @returns the merged VMA or the original if no merging was possible.
- */
- VMAIter MergeAdjacent(VMAIter vma);
-
- /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
- void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
-
- /// Finds a free (unmapped region) of the specified size starting at the specified address.
- GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size) const;
+ [[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size);
+ [[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align);
+ [[nodiscard]] GPUVAddr MapAllocate32(VAddr cpu_addr, std::size_t size);
+ [[nodiscard]] std::optional<GPUVAddr> AllocateFixed(GPUVAddr gpu_addr, std::size_t size);
+ [[nodiscard]] GPUVAddr Allocate(std::size_t size, std::size_t align);
+ void Unmap(GPUVAddr gpu_addr, std::size_t size);
private:
+ [[nodiscard]] PageEntry GetPageEntry(GPUVAddr gpu_addr) const;
+ void SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size = page_size);
+ GPUVAddr UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size);
+ [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align,
+ bool start_32bit_address = false) const;
+
+ void TryLockPage(PageEntry page_entry, std::size_t size);
+ void TryUnlockPage(PageEntry page_entry, std::size_t size);
+
+ [[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) {
+ return (gpu_addr >> page_bits) & page_table_mask;
+ }
+
+ static constexpr u64 address_space_size = 1ULL << 40;
+ static constexpr u64 address_space_start = 1ULL << 32;
+ static constexpr u64 address_space_start_low = 1ULL << 16;
static constexpr u64 page_bits{16};
static constexpr u64 page_size{1 << page_bits};
static constexpr u64 page_mask{page_size - 1};
+ static constexpr u64 page_table_bits{24};
+ static constexpr u64 page_table_size{1 << page_table_bits};
+ static constexpr u64 page_table_mask{page_table_size - 1};
- /// Address space in bits, according to Tegra X1 TRM
- static constexpr u32 address_space_width{40};
- /// Start address for mapping, this is fairly arbitrary but must be non-zero.
- static constexpr GPUVAddr address_space_base{0x100000};
- /// End of address space, based on address space in bits.
- static constexpr GPUVAddr address_space_end{1ULL << address_space_width};
+ Core::System& system;
- Common::BackingPageTable page_table{page_bits};
- VMAMap vma_map;
- VideoCore::RasterizerInterface& rasterizer;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
- Core::System& system;
+ std::vector<PageEntry> page_table;
};
} // namespace Tegra
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index 6d522c318..9da9fb4ff 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -41,144 +41,168 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
}
static constexpr ConversionArray morton_to_linear_fns = {
- MortonCopy<true, PixelFormat::ABGR8U>,
- MortonCopy<true, PixelFormat::ABGR8S>,
- MortonCopy<true, PixelFormat::ABGR8UI>,
- MortonCopy<true, PixelFormat::B5G6R5U>,
- MortonCopy<true, PixelFormat::A2B10G10R10U>,
- MortonCopy<true, PixelFormat::A1B5G5R5U>,
- MortonCopy<true, PixelFormat::R8U>,
- MortonCopy<true, PixelFormat::R8UI>,
- MortonCopy<true, PixelFormat::RGBA16F>,
- MortonCopy<true, PixelFormat::RGBA16U>,
- MortonCopy<true, PixelFormat::RGBA16S>,
- MortonCopy<true, PixelFormat::RGBA16UI>,
- MortonCopy<true, PixelFormat::R11FG11FB10F>,
- MortonCopy<true, PixelFormat::RGBA32UI>,
- MortonCopy<true, PixelFormat::DXT1>,
- MortonCopy<true, PixelFormat::DXT23>,
- MortonCopy<true, PixelFormat::DXT45>,
- MortonCopy<true, PixelFormat::DXN1>,
- MortonCopy<true, PixelFormat::DXN2UNORM>,
- MortonCopy<true, PixelFormat::DXN2SNORM>,
- MortonCopy<true, PixelFormat::BC7U>,
- MortonCopy<true, PixelFormat::BC6H_UF16>,
- MortonCopy<true, PixelFormat::BC6H_SF16>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
- MortonCopy<true, PixelFormat::BGRA8>,
- MortonCopy<true, PixelFormat::RGBA32F>,
- MortonCopy<true, PixelFormat::RG32F>,
- MortonCopy<true, PixelFormat::R32F>,
- MortonCopy<true, PixelFormat::R16F>,
- MortonCopy<true, PixelFormat::R16U>,
- MortonCopy<true, PixelFormat::R16S>,
- MortonCopy<true, PixelFormat::R16UI>,
- MortonCopy<true, PixelFormat::R16I>,
- MortonCopy<true, PixelFormat::RG16>,
- MortonCopy<true, PixelFormat::RG16F>,
- MortonCopy<true, PixelFormat::RG16UI>,
- MortonCopy<true, PixelFormat::RG16I>,
- MortonCopy<true, PixelFormat::RG16S>,
- MortonCopy<true, PixelFormat::RGB32F>,
- MortonCopy<true, PixelFormat::RGBA8_SRGB>,
- MortonCopy<true, PixelFormat::RG8U>,
- MortonCopy<true, PixelFormat::RG8S>,
- MortonCopy<true, PixelFormat::RG32UI>,
- MortonCopy<true, PixelFormat::RGBX16F>,
- MortonCopy<true, PixelFormat::R32UI>,
- MortonCopy<true, PixelFormat::R32I>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
- MortonCopy<true, PixelFormat::BGRA8_SRGB>,
- MortonCopy<true, PixelFormat::DXT1_SRGB>,
- MortonCopy<true, PixelFormat::DXT23_SRGB>,
- MortonCopy<true, PixelFormat::DXT45_SRGB>,
- MortonCopy<true, PixelFormat::BC7U_SRGB>,
- MortonCopy<true, PixelFormat::R4G4B4A4U>,
+ MortonCopy<true, PixelFormat::A8B8G8R8_UNORM>,
+ MortonCopy<true, PixelFormat::A8B8G8R8_SNORM>,
+ MortonCopy<true, PixelFormat::A8B8G8R8_SINT>,
+ MortonCopy<true, PixelFormat::A8B8G8R8_UINT>,
+ MortonCopy<true, PixelFormat::R5G6B5_UNORM>,
+ MortonCopy<true, PixelFormat::B5G6R5_UNORM>,
+ MortonCopy<true, PixelFormat::A1R5G5B5_UNORM>,
+ MortonCopy<true, PixelFormat::A2B10G10R10_UNORM>,
+ MortonCopy<true, PixelFormat::A2B10G10R10_UINT>,
+ MortonCopy<true, PixelFormat::A1B5G5R5_UNORM>,
+ MortonCopy<true, PixelFormat::R8_UNORM>,
+ MortonCopy<true, PixelFormat::R8_SNORM>,
+ MortonCopy<true, PixelFormat::R8_SINT>,
+ MortonCopy<true, PixelFormat::R8_UINT>,
+ MortonCopy<true, PixelFormat::R16G16B16A16_FLOAT>,
+ MortonCopy<true, PixelFormat::R16G16B16A16_UNORM>,
+ MortonCopy<true, PixelFormat::R16G16B16A16_SNORM>,
+ MortonCopy<true, PixelFormat::R16G16B16A16_SINT>,
+ MortonCopy<true, PixelFormat::R16G16B16A16_UINT>,
+ MortonCopy<true, PixelFormat::B10G11R11_FLOAT>,
+ MortonCopy<true, PixelFormat::R32G32B32A32_UINT>,
+ MortonCopy<true, PixelFormat::BC1_RGBA_UNORM>,
+ MortonCopy<true, PixelFormat::BC2_UNORM>,
+ MortonCopy<true, PixelFormat::BC3_UNORM>,
+ MortonCopy<true, PixelFormat::BC4_UNORM>,
+ MortonCopy<true, PixelFormat::BC4_SNORM>,
+ MortonCopy<true, PixelFormat::BC5_UNORM>,
+ MortonCopy<true, PixelFormat::BC5_SNORM>,
+ MortonCopy<true, PixelFormat::BC7_UNORM>,
+ MortonCopy<true, PixelFormat::BC6H_UFLOAT>,
+ MortonCopy<true, PixelFormat::BC6H_SFLOAT>,
+ MortonCopy<true, PixelFormat::ASTC_2D_4X4_UNORM>,
+ MortonCopy<true, PixelFormat::B8G8R8A8_UNORM>,
+ MortonCopy<true, PixelFormat::R32G32B32A32_FLOAT>,
+ MortonCopy<true, PixelFormat::R32G32B32A32_SINT>,
+ MortonCopy<true, PixelFormat::R32G32_FLOAT>,
+ MortonCopy<true, PixelFormat::R32G32_SINT>,
+ MortonCopy<true, PixelFormat::R32_FLOAT>,
+ MortonCopy<true, PixelFormat::R16_FLOAT>,
+ MortonCopy<true, PixelFormat::R16_UNORM>,
+ MortonCopy<true, PixelFormat::R16_SNORM>,
+ MortonCopy<true, PixelFormat::R16_UINT>,
+ MortonCopy<true, PixelFormat::R16_SINT>,
+ MortonCopy<true, PixelFormat::R16G16_UNORM>,
+ MortonCopy<true, PixelFormat::R16G16_FLOAT>,
+ MortonCopy<true, PixelFormat::R16G16_UINT>,
+ MortonCopy<true, PixelFormat::R16G16_SINT>,
+ MortonCopy<true, PixelFormat::R16G16_SNORM>,
+ MortonCopy<true, PixelFormat::R32G32B32_FLOAT>,
+ MortonCopy<true, PixelFormat::A8B8G8R8_SRGB>,
+ MortonCopy<true, PixelFormat::R8G8_UNORM>,
+ MortonCopy<true, PixelFormat::R8G8_SNORM>,
+ MortonCopy<true, PixelFormat::R8G8_SINT>,
+ MortonCopy<true, PixelFormat::R8G8_UINT>,
+ MortonCopy<true, PixelFormat::R32G32_UINT>,
+ MortonCopy<true, PixelFormat::R16G16B16X16_FLOAT>,
+ MortonCopy<true, PixelFormat::R32_UINT>,
+ MortonCopy<true, PixelFormat::R32_SINT>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X8_UNORM>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5_UNORM>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4_UNORM>,
+ MortonCopy<true, PixelFormat::B8G8R8A8_SRGB>,
+ MortonCopy<true, PixelFormat::BC1_RGBA_SRGB>,
+ MortonCopy<true, PixelFormat::BC2_SRGB>,
+ MortonCopy<true, PixelFormat::BC3_SRGB>,
+ MortonCopy<true, PixelFormat::BC7_SRGB>,
+ MortonCopy<true, PixelFormat::A4B4G4R4_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X8_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X6>,
+ MortonCopy<true, PixelFormat::ASTC_2D_6X6_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_6X6_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X10>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X10_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_10X10_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_12X12>,
+ MortonCopy<true, PixelFormat::ASTC_2D_12X12_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_12X12_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X6>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X6_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_8X6_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_6X5_UNORM>,
MortonCopy<true, PixelFormat::ASTC_2D_6X5_SRGB>,
- MortonCopy<true, PixelFormat::E5B9G9R9F>,
- MortonCopy<true, PixelFormat::Z32F>,
- MortonCopy<true, PixelFormat::Z16>,
- MortonCopy<true, PixelFormat::Z24S8>,
- MortonCopy<true, PixelFormat::S8Z24>,
- MortonCopy<true, PixelFormat::Z32FS8>,
+ MortonCopy<true, PixelFormat::E5B9G9R9_FLOAT>,
+ MortonCopy<true, PixelFormat::D32_FLOAT>,
+ MortonCopy<true, PixelFormat::D16_UNORM>,
+ MortonCopy<true, PixelFormat::D24_UNORM_S8_UINT>,
+ MortonCopy<true, PixelFormat::S8_UINT_D24_UNORM>,
+ MortonCopy<true, PixelFormat::D32_FLOAT_S8_UINT>,
};
static constexpr ConversionArray linear_to_morton_fns = {
- MortonCopy<false, PixelFormat::ABGR8U>,
- MortonCopy<false, PixelFormat::ABGR8S>,
- MortonCopy<false, PixelFormat::ABGR8UI>,
- MortonCopy<false, PixelFormat::B5G6R5U>,
- MortonCopy<false, PixelFormat::A2B10G10R10U>,
- MortonCopy<false, PixelFormat::A1B5G5R5U>,
- MortonCopy<false, PixelFormat::R8U>,
- MortonCopy<false, PixelFormat::R8UI>,
- MortonCopy<false, PixelFormat::RGBA16F>,
- MortonCopy<false, PixelFormat::RGBA16S>,
- MortonCopy<false, PixelFormat::RGBA16U>,
- MortonCopy<false, PixelFormat::RGBA16UI>,
- MortonCopy<false, PixelFormat::R11FG11FB10F>,
- MortonCopy<false, PixelFormat::RGBA32UI>,
- MortonCopy<false, PixelFormat::DXT1>,
- MortonCopy<false, PixelFormat::DXT23>,
- MortonCopy<false, PixelFormat::DXT45>,
- MortonCopy<false, PixelFormat::DXN1>,
- MortonCopy<false, PixelFormat::DXN2UNORM>,
- MortonCopy<false, PixelFormat::DXN2SNORM>,
- MortonCopy<false, PixelFormat::BC7U>,
- MortonCopy<false, PixelFormat::BC6H_UF16>,
- MortonCopy<false, PixelFormat::BC6H_SF16>,
+ MortonCopy<false, PixelFormat::A8B8G8R8_UNORM>,
+ MortonCopy<false, PixelFormat::A8B8G8R8_SNORM>,
+ MortonCopy<false, PixelFormat::A8B8G8R8_SINT>,
+ MortonCopy<false, PixelFormat::A8B8G8R8_UINT>,
+ MortonCopy<false, PixelFormat::R5G6B5_UNORM>,
+ MortonCopy<false, PixelFormat::B5G6R5_UNORM>,
+ MortonCopy<false, PixelFormat::A1R5G5B5_UNORM>,
+ MortonCopy<false, PixelFormat::A2B10G10R10_UNORM>,
+ MortonCopy<false, PixelFormat::A2B10G10R10_UINT>,
+ MortonCopy<false, PixelFormat::A1B5G5R5_UNORM>,
+ MortonCopy<false, PixelFormat::R8_UNORM>,
+ MortonCopy<false, PixelFormat::R8_SNORM>,
+ MortonCopy<false, PixelFormat::R8_SINT>,
+ MortonCopy<false, PixelFormat::R8_UINT>,
+ MortonCopy<false, PixelFormat::R16G16B16A16_FLOAT>,
+ MortonCopy<false, PixelFormat::R16G16B16A16_SNORM>,
+ MortonCopy<false, PixelFormat::R16G16B16A16_SINT>,
+ MortonCopy<false, PixelFormat::R16G16B16A16_UNORM>,
+ MortonCopy<false, PixelFormat::R16G16B16A16_UINT>,
+ MortonCopy<false, PixelFormat::B10G11R11_FLOAT>,
+ MortonCopy<false, PixelFormat::R32G32B32A32_UINT>,
+ MortonCopy<false, PixelFormat::BC1_RGBA_UNORM>,
+ MortonCopy<false, PixelFormat::BC2_UNORM>,
+ MortonCopy<false, PixelFormat::BC3_UNORM>,
+ MortonCopy<false, PixelFormat::BC4_UNORM>,
+ MortonCopy<false, PixelFormat::BC4_SNORM>,
+ MortonCopy<false, PixelFormat::BC5_UNORM>,
+ MortonCopy<false, PixelFormat::BC5_SNORM>,
+ MortonCopy<false, PixelFormat::BC7_UNORM>,
+ MortonCopy<false, PixelFormat::BC6H_UFLOAT>,
+ MortonCopy<false, PixelFormat::BC6H_SFLOAT>,
// TODO(Subv): Swizzling ASTC formats are not supported
nullptr,
- MortonCopy<false, PixelFormat::BGRA8>,
- MortonCopy<false, PixelFormat::RGBA32F>,
- MortonCopy<false, PixelFormat::RG32F>,
- MortonCopy<false, PixelFormat::R32F>,
- MortonCopy<false, PixelFormat::R16F>,
- MortonCopy<false, PixelFormat::R16U>,
- MortonCopy<false, PixelFormat::R16S>,
- MortonCopy<false, PixelFormat::R16UI>,
- MortonCopy<false, PixelFormat::R16I>,
- MortonCopy<false, PixelFormat::RG16>,
- MortonCopy<false, PixelFormat::RG16F>,
- MortonCopy<false, PixelFormat::RG16UI>,
- MortonCopy<false, PixelFormat::RG16I>,
- MortonCopy<false, PixelFormat::RG16S>,
- MortonCopy<false, PixelFormat::RGB32F>,
- MortonCopy<false, PixelFormat::RGBA8_SRGB>,
- MortonCopy<false, PixelFormat::RG8U>,
- MortonCopy<false, PixelFormat::RG8S>,
- MortonCopy<false, PixelFormat::RG32UI>,
- MortonCopy<false, PixelFormat::RGBX16F>,
- MortonCopy<false, PixelFormat::R32UI>,
- MortonCopy<false, PixelFormat::R32I>,
+ MortonCopy<false, PixelFormat::B8G8R8A8_UNORM>,
+ MortonCopy<false, PixelFormat::R32G32B32A32_FLOAT>,
+ MortonCopy<false, PixelFormat::R32G32B32A32_SINT>,
+ MortonCopy<false, PixelFormat::R32G32_FLOAT>,
+ MortonCopy<false, PixelFormat::R32G32_SINT>,
+ MortonCopy<false, PixelFormat::R32_FLOAT>,
+ MortonCopy<false, PixelFormat::R16_FLOAT>,
+ MortonCopy<false, PixelFormat::R16_UNORM>,
+ MortonCopy<false, PixelFormat::R16_SNORM>,
+ MortonCopy<false, PixelFormat::R16_UINT>,
+ MortonCopy<false, PixelFormat::R16_SINT>,
+ MortonCopy<false, PixelFormat::R16G16_UNORM>,
+ MortonCopy<false, PixelFormat::R16G16_FLOAT>,
+ MortonCopy<false, PixelFormat::R16G16_UINT>,
+ MortonCopy<false, PixelFormat::R16G16_SINT>,
+ MortonCopy<false, PixelFormat::R16G16_SNORM>,
+ MortonCopy<false, PixelFormat::R32G32B32_FLOAT>,
+ MortonCopy<false, PixelFormat::A8B8G8R8_SRGB>,
+ MortonCopy<false, PixelFormat::R8G8_UNORM>,
+ MortonCopy<false, PixelFormat::R8G8_SNORM>,
+ MortonCopy<false, PixelFormat::R8G8_SINT>,
+ MortonCopy<false, PixelFormat::R8G8_UINT>,
+ MortonCopy<false, PixelFormat::R32G32_UINT>,
+ MortonCopy<false, PixelFormat::R16G16B16X16_FLOAT>,
+ MortonCopy<false, PixelFormat::R32_UINT>,
+ MortonCopy<false, PixelFormat::R32_SINT>,
nullptr,
nullptr,
nullptr,
- MortonCopy<false, PixelFormat::BGRA8_SRGB>,
- MortonCopy<false, PixelFormat::DXT1_SRGB>,
- MortonCopy<false, PixelFormat::DXT23_SRGB>,
- MortonCopy<false, PixelFormat::DXT45_SRGB>,
- MortonCopy<false, PixelFormat::BC7U_SRGB>,
- MortonCopy<false, PixelFormat::R4G4B4A4U>,
+ MortonCopy<false, PixelFormat::B8G8R8A8_SRGB>,
+ MortonCopy<false, PixelFormat::BC1_RGBA_SRGB>,
+ MortonCopy<false, PixelFormat::BC2_SRGB>,
+ MortonCopy<false, PixelFormat::BC3_SRGB>,
+ MortonCopy<false, PixelFormat::BC7_SRGB>,
+ MortonCopy<false, PixelFormat::A4B4G4R4_UNORM>,
nullptr,
nullptr,
nullptr,
@@ -197,12 +221,12 @@ static constexpr ConversionArray linear_to_morton_fns = {
nullptr,
nullptr,
nullptr,
- MortonCopy<false, PixelFormat::E5B9G9R9F>,
- MortonCopy<false, PixelFormat::Z32F>,
- MortonCopy<false, PixelFormat::Z16>,
- MortonCopy<false, PixelFormat::Z24S8>,
- MortonCopy<false, PixelFormat::S8Z24>,
- MortonCopy<false, PixelFormat::Z32FS8>,
+ MortonCopy<false, PixelFormat::E5B9G9R9_FLOAT>,
+ MortonCopy<false, PixelFormat::D32_FLOAT>,
+ MortonCopy<false, PixelFormat::D16_UNORM>,
+ MortonCopy<false, PixelFormat::D24_UNORM_S8_UINT>,
+ MortonCopy<false, PixelFormat::S8_UINT_D24_UNORM>,
+ MortonCopy<false, PixelFormat::D32_FLOAT_S8_UINT>,
};
static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) {
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 5ea2b01f2..fc54ca0ef 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -12,10 +12,12 @@
#include <mutex>
#include <optional>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include "common/assert.h"
#include "core/core.h"
+#include "core/settings.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
@@ -89,14 +91,15 @@ private:
std::shared_ptr<HostCounter> last;
};
-template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter,
- class QueryPool>
+template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
class QueryCacheBase {
public:
- explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
- : system{system}, rasterizer{rasterizer}, streams{{CounterStream{
- static_cast<QueryCache&>(*this),
- VideoCore::QueryType::SamplesPassed}}} {}
+ explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::MemoryManager& gpu_memory_)
+ : rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
+ VideoCore::QueryType::SamplesPassed}}} {}
void InvalidateRegion(VAddr addr, std::size_t size) {
std::unique_lock lock{mutex};
@@ -116,26 +119,27 @@ public:
*/
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
std::unique_lock lock{mutex};
- auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr_opt);
- VAddr cpu_addr = *cpu_addr_opt;
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ ASSERT(cpu_addr);
- CachedQuery* query = TryGet(cpu_addr);
+ CachedQuery* query = TryGet(*cpu_addr);
if (!query) {
- ASSERT_OR_EXECUTE(cpu_addr_opt, return;);
- const auto host_ptr = memory_manager.GetPointer(gpu_addr);
+ ASSERT_OR_EXECUTE(cpu_addr, return;);
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
- query = Register(type, cpu_addr, host_ptr, timestamp.has_value());
+ query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
query->BindCounter(Stream(type).Current(), timestamp);
+ if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ AsyncFlushQuery(*cpu_addr);
+ }
}
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
void UpdateCounters() {
std::unique_lock lock{mutex};
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
}
@@ -170,8 +174,36 @@ public:
return streams[static_cast<std::size_t>(type)];
}
-protected:
- std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
+ void CommitAsyncFlushes() {
+ committed_flushes.push_back(uncommitted_flushes);
+ uncommitted_flushes.reset();
+ }
+
+ bool HasUncommittedFlushes() const {
+ return uncommitted_flushes != nullptr;
+ }
+
+ bool ShouldWaitAsyncFlushes() const {
+ if (committed_flushes.empty()) {
+ return false;
+ }
+ return committed_flushes.front() != nullptr;
+ }
+
+ void PopAsyncFlushes() {
+ if (committed_flushes.empty()) {
+ return;
+ }
+ auto& flush_list = committed_flushes.front();
+ if (!flush_list) {
+ committed_flushes.pop_front();
+ return;
+ }
+ for (VAddr query_address : *flush_list) {
+ FlushAndRemoveRegion(query_address, 4);
+ }
+ committed_flushes.pop_front();
+ }
private:
/// Flushes a memory range to guest memory and removes it from the cache.
@@ -184,8 +216,8 @@ private:
return cache_begin < addr_end && addr_begin < cache_end;
};
- const u64 page_end = addr_end >> PAGE_SHIFT;
- for (u64 page = addr_begin >> PAGE_SHIFT; page <= page_end; ++page) {
+ const u64 page_end = addr_end >> PAGE_BITS;
+ for (u64 page = addr_begin >> PAGE_BITS; page <= page_end; ++page) {
const auto& it = cached_queries.find(page);
if (it == std::end(cached_queries)) {
continue;
@@ -206,14 +238,14 @@ private:
/// Registers the passed parameters as cached and returns a pointer to the stored cached query.
CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) {
rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1);
- const u64 page = static_cast<u64>(cpu_addr) >> PAGE_SHIFT;
+ const u64 page = static_cast<u64>(cpu_addr) >> PAGE_BITS;
return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr,
host_ptr);
}
/// Tries to a get a cached query. Returns nullptr on failure.
CachedQuery* TryGet(VAddr addr) {
- const u64 page = static_cast<u64>(addr) >> PAGE_SHIFT;
+ const u64 page = static_cast<u64>(addr) >> PAGE_BITS;
const auto it = cached_queries.find(page);
if (it == std::end(cached_queries)) {
return nullptr;
@@ -224,17 +256,28 @@ private:
return found != std::end(contents) ? &*found : nullptr;
}
+ void AsyncFlushQuery(VAddr addr) {
+ if (!uncommitted_flushes) {
+ uncommitted_flushes = std::make_shared<std::unordered_set<VAddr>>();
+ }
+ uncommitted_flushes->insert(addr);
+ }
+
static constexpr std::uintptr_t PAGE_SIZE = 4096;
- static constexpr unsigned PAGE_SHIFT = 12;
+ static constexpr unsigned PAGE_BITS = 12;
- Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager& gpu_memory;
std::recursive_mutex mutex;
std::unordered_map<u64, std::vector<CachedQuery>> cached_queries;
std::array<CounterStream, VideoCore::NumQueryTypes> streams;
+
+ std::shared_ptr<std::unordered_set<VAddr>> uncommitted_flushes{};
+ std::list<std::shared_ptr<std::unordered_set<VAddr>>> committed_flushes;
};
template <class QueryCache, class HostCounter>
diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp
index d01db97da..53622ca05 100644
--- a/src/video_core/rasterizer_accelerated.cpp
+++ b/src/video_core/rasterizer_accelerated.cpp
@@ -23,15 +23,15 @@ constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
} // Anonymous namespace
-RasterizerAccelerated::RasterizerAccelerated(Memory::Memory& cpu_memory_)
+RasterizerAccelerated::RasterizerAccelerated(Core::Memory::Memory& cpu_memory_)
: cpu_memory{cpu_memory_} {}
RasterizerAccelerated::~RasterizerAccelerated() = default;
void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
std::lock_guard lock{pages_mutex};
- const u64 page_start{addr >> Memory::PAGE_BITS};
- const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
+ const u64 page_start{addr >> Core::Memory::PAGE_BITS};
+ const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS};
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
// subtract after iterating
@@ -44,8 +44,8 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
const auto interval = pair.first & pages_interval;
const int count = pair.second;
- const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
- const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
+ const VAddr interval_start_addr = boost::icl::first(interval) << Core::Memory::PAGE_BITS;
+ const VAddr interval_end_addr = boost::icl::last_next(interval) << Core::Memory::PAGE_BITS;
const u64 interval_size = interval_end_addr - interval_start_addr;
if (delta > 0 && count == delta) {
diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h
index 315798e7c..91866d7dd 100644
--- a/src/video_core/rasterizer_accelerated.h
+++ b/src/video_core/rasterizer_accelerated.h
@@ -11,7 +11,7 @@
#include "common/common_types.h"
#include "video_core/rasterizer_interface.h"
-namespace Memory {
+namespace Core::Memory {
class Memory;
}
@@ -20,7 +20,7 @@ namespace VideoCore {
/// Implements the shared part in GPU accelerated rasterizers in RasterizerInterface.
class RasterizerAccelerated : public RasterizerInterface {
public:
- explicit RasterizerAccelerated(Memory::Memory& cpu_memory_);
+ explicit RasterizerAccelerated(Core::Memory::Memory& cpu_memory_);
~RasterizerAccelerated() override;
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
@@ -30,7 +30,7 @@ private:
CachedPageMap cached_pages;
std::mutex pages_mutex;
- Memory::Memory& cpu_memory;
+ Core::Memory::Memory& cpu_memory;
};
} // namespace VideoCore
diff --git a/src/video_core/rasterizer_cache.cpp b/src/video_core/rasterizer_cache.cpp
deleted file mode 100644
index 093b2cdf4..000000000
--- a/src/video_core/rasterizer_cache.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "video_core/rasterizer_cache.h"
-
-RasterizerCacheObject::~RasterizerCacheObject() = default;
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
deleted file mode 100644
index 22987751e..000000000
--- a/src/video_core/rasterizer_cache.h
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <mutex>
-#include <set>
-#include <unordered_map>
-
-#include <boost/icl/interval_map.hpp>
-#include <boost/range/iterator_range_core.hpp>
-
-#include "common/common_types.h"
-#include "core/settings.h"
-#include "video_core/gpu.h"
-#include "video_core/rasterizer_interface.h"
-
-class RasterizerCacheObject {
-public:
- explicit RasterizerCacheObject(const VAddr cpu_addr) : cpu_addr{cpu_addr} {}
-
- virtual ~RasterizerCacheObject();
-
- VAddr GetCpuAddr() const {
- return cpu_addr;
- }
-
- /// Gets the size of the shader in guest memory, required for cache management
- virtual std::size_t GetSizeInBytes() const = 0;
-
- /// Sets whether the cached object should be considered registered
- void SetIsRegistered(bool registered) {
- is_registered = registered;
- }
-
- /// Returns true if the cached object is registered
- bool IsRegistered() const {
- return is_registered;
- }
-
- /// Returns true if the cached object is dirty
- bool IsDirty() const {
- return is_dirty;
- }
-
- /// Returns ticks from when this cached object was last modified
- u64 GetLastModifiedTicks() const {
- return last_modified_ticks;
- }
-
- /// Marks an object as recently modified, used to specify whether it is clean or dirty
- template <class T>
- void MarkAsModified(bool dirty, T& cache) {
- is_dirty = dirty;
- last_modified_ticks = cache.GetModifiedTicks();
- }
-
-private:
- bool is_registered{}; ///< Whether the object is currently registered with the cache
- bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
- u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
- VAddr cpu_addr{}; ///< Cpu address memory, unique from emulated virtual address space
-};
-
-template <class T>
-class RasterizerCache : NonCopyable {
- friend class RasterizerCacheObject;
-
-public:
- explicit RasterizerCache(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
-
- /// Write any cached resources overlapping the specified region back to memory
- void FlushRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
-
- const auto& objects{GetSortedObjectsFromRegion(addr, size)};
- for (auto& object : objects) {
- FlushObject(object);
- }
- }
-
- /// Mark the specified region as being invalidated
- void InvalidateRegion(VAddr addr, u64 size) {
- std::lock_guard lock{mutex};
-
- const auto& objects{GetSortedObjectsFromRegion(addr, size)};
- for (auto& object : objects) {
- if (!object->IsRegistered()) {
- // Skip duplicates
- continue;
- }
- Unregister(object);
- }
- }
-
- /// Invalidates everything in the cache
- void InvalidateAll() {
- std::lock_guard lock{mutex};
-
- while (interval_cache.begin() != interval_cache.end()) {
- Unregister(*interval_cache.begin()->second.begin());
- }
- }
-
-protected:
- /// Tries to get an object from the cache with the specified cache address
- T TryGet(VAddr addr) const {
- const auto iter = map_cache.find(addr);
- if (iter != map_cache.end())
- return iter->second;
- return nullptr;
- }
-
- /// Register an object into the cache
- virtual void Register(const T& object) {
- std::lock_guard lock{mutex};
-
- object->SetIsRegistered(true);
- interval_cache.add({GetInterval(object), ObjectSet{object}});
- map_cache.insert({object->GetCpuAddr(), object});
- rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), 1);
- }
-
- /// Unregisters an object from the cache
- virtual void Unregister(const T& object) {
- std::lock_guard lock{mutex};
-
- object->SetIsRegistered(false);
- rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1);
- const VAddr addr = object->GetCpuAddr();
- interval_cache.subtract({GetInterval(object), ObjectSet{object}});
- map_cache.erase(addr);
- }
-
- /// Returns a ticks counter used for tracking when cached objects were last modified
- u64 GetModifiedTicks() {
- std::lock_guard lock{mutex};
-
- return ++modified_ticks;
- }
-
- virtual void FlushObjectInner(const T& object) = 0;
-
- /// Flushes the specified object, updating appropriate cache state as needed
- void FlushObject(const T& object) {
- std::lock_guard lock{mutex};
-
- if (!object->IsDirty()) {
- return;
- }
- FlushObjectInner(object);
- object->MarkAsModified(false, *this);
- }
-
- std::recursive_mutex mutex;
-
-private:
- /// Returns a list of cached objects from the specified memory region, ordered by access time
- std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
- if (size == 0) {
- return {};
- }
-
- std::vector<T> objects;
- const ObjectInterval interval{addr, addr + size};
- for (auto& pair : boost::make_iterator_range(interval_cache.equal_range(interval))) {
- for (auto& cached_object : pair.second) {
- if (!cached_object) {
- continue;
- }
- objects.push_back(cached_object);
- }
- }
-
- std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
- return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
- });
-
- return objects;
- }
-
- using ObjectSet = std::set<T>;
- using ObjectCache = std::unordered_map<VAddr, T>;
- using IntervalCache = boost::icl::interval_map<VAddr, ObjectSet>;
- using ObjectInterval = typename IntervalCache::interval_type;
-
- static auto GetInterval(const T& object) {
- return ObjectInterval::right_open(object->GetCpuAddr(),
- object->GetCpuAddr() + object->GetSizeInBytes());
- }
-
- ObjectCache map_cache;
- IntervalCache interval_cache; ///< Cache of objects
- u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
- VideoCore::RasterizerInterface& rasterizer;
-};
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 8ae5b9c4e..27ef4c69a 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -32,7 +32,7 @@ using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size
class RasterizerInterface {
public:
- virtual ~RasterizerInterface() {}
+ virtual ~RasterizerInterface() = default;
/// Dispatches a draw invocation
virtual void Draw(bool is_indexed, bool is_instanced) = 0;
@@ -49,19 +49,40 @@ public:
/// Records a GPU query and caches it
virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0;
+ /// Signal a GPU based semaphore as a fence
+ virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0;
+
+ /// Signal a GPU based syncpoint as a fence
+ virtual void SignalSyncPoint(u32 value) = 0;
+
+ /// Release all pending fences.
+ virtual void ReleaseFences() = 0;
+
/// Notify rasterizer that all caches should be flushed to Switch memory
virtual void FlushAll() = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ /// Check if the the specified memory area requires flushing to CPU Memory.
+ virtual bool MustFlushRegion(VAddr addr, u64 size) = 0;
+
/// Notify rasterizer that any caches of the specified region should be invalidated
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ /// Notify rasterizer that any caches of the specified region are desync with guest
+ virtual void OnCPUWrite(VAddr addr, u64 size) = 0;
+
+ /// Sync memory between guest and host.
+ virtual void SyncGuestHost() = 0;
+
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+ /// Notify the host renderer to wait for previous primitive and compute operations.
+ virtual void WaitForIdle() = 0;
+
/// Notify the rasterizer to send all written commands to the host GPU.
virtual void FlushCommands() = 0;
@@ -69,15 +90,16 @@ public:
virtual void TickFrame() = 0;
/// Attempt to use a faster method to perform a surface copy
- virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
- const Tegra::Engines::Fermi2D::Config& copy_config) {
+ [[nodiscard]] virtual bool AccelerateSurfaceCopy(
+ const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+ const Tegra::Engines::Fermi2D::Config& copy_config) {
return false;
}
/// Attempt to use a faster method to display the framebuffer to screen
- virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
- u32 pixel_stride) {
+ [[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config,
+ VAddr framebuffer_addr, u32 pixel_stride) {
return false;
}
@@ -85,19 +107,16 @@ public:
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
/// Initialize disk cached resources for the game being emulated
- virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
- const DiskResourceLoadCallback& callback = {}) {}
-
- /// Initializes renderer dirty flags
- virtual void SetupDirtyFlags() {}
+ virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
+ const DiskResourceLoadCallback& callback) {}
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
- GuestDriverProfile& AccessGuestDriverProfile() {
+ [[nodiscard]] GuestDriverProfile& AccessGuestDriverProfile() {
return guest_driver_profile;
}
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
- const GuestDriverProfile& AccessGuestDriverProfile() const {
+ [[nodiscard]] const GuestDriverProfile& AccessGuestDriverProfile() const {
return guest_driver_profile;
}
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 919d1f2d4..a93a1732c 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -9,7 +9,9 @@
namespace VideoCore {
-RendererBase::RendererBase(Core::Frontend::EmuWindow& window) : render_window{window} {
+RendererBase::RendererBase(Core::Frontend::EmuWindow& window_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context_)
+ : render_window{window_}, context{std::move(context_)} {
RefreshBaseSettings();
}
@@ -18,7 +20,7 @@ RendererBase::~RendererBase() = default;
void RendererBase::RefreshBaseSettings() {
UpdateCurrentFramebufferLayout();
- renderer_settings.use_framelimiter = Settings::values.use_frame_limit;
+ renderer_settings.use_framelimiter = Settings::values.use_frame_limit.GetValue();
renderer_settings.set_background_color = true;
}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 1d85219b6..51dde8eb5 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -15,7 +15,8 @@
namespace Core::Frontend {
class EmuWindow;
-}
+class GraphicsContext;
+} // namespace Core::Frontend
namespace VideoCore {
@@ -25,18 +26,19 @@ struct RendererSettings {
// Screenshot
std::atomic<bool> screenshot_requested{false};
- void* screenshot_bits;
+ void* screenshot_bits{};
std::function<void()> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout;
};
class RendererBase : NonCopyable {
public:
- explicit RendererBase(Core::Frontend::EmuWindow& window);
+ explicit RendererBase(Core::Frontend::EmuWindow& window,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
virtual ~RendererBase();
/// Initialize the renderer
- virtual bool Init() = 0;
+ [[nodiscard]] virtual bool Init() = 0;
/// Shutdown the renderer
virtual void ShutDown() = 0;
@@ -44,43 +46,46 @@ public:
/// Finalize rendering the guest frame and draw into the presentation texture
virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
- /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
- /// specific implementation)
- /// Returns true if a frame was drawn
- virtual bool TryPresent(int timeout_ms) = 0;
-
// Getter/setter functions:
// ------------------------
- f32 GetCurrentFPS() const {
+ [[nodiscard]] f32 GetCurrentFPS() const {
return m_current_fps;
}
- int GetCurrentFrame() const {
+ [[nodiscard]] int GetCurrentFrame() const {
return m_current_frame;
}
- RasterizerInterface& Rasterizer() {
+ [[nodiscard]] RasterizerInterface& Rasterizer() {
return *rasterizer;
}
- const RasterizerInterface& Rasterizer() const {
+ [[nodiscard]] const RasterizerInterface& Rasterizer() const {
return *rasterizer;
}
- Core::Frontend::EmuWindow& GetRenderWindow() {
+ [[nodiscard]] Core::Frontend::GraphicsContext& Context() {
+ return *context;
+ }
+
+ [[nodiscard]] const Core::Frontend::GraphicsContext& Context() const {
+ return *context;
+ }
+
+ [[nodiscard]] Core::Frontend::EmuWindow& GetRenderWindow() {
return render_window;
}
- const Core::Frontend::EmuWindow& GetRenderWindow() const {
+ [[nodiscard]] const Core::Frontend::EmuWindow& GetRenderWindow() const {
return render_window;
}
- RendererSettings& Settings() {
+ [[nodiscard]] RendererSettings& Settings() {
return renderer_settings;
}
- const RendererSettings& Settings() const {
+ [[nodiscard]] const RendererSettings& Settings() const {
return renderer_settings;
}
@@ -94,6 +99,7 @@ public:
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;
+ std::unique_ptr<Core::Frontend::GraphicsContext> context;
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
int m_current_frame = 0; ///< Current frame, should be set by the renderer
diff --git a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp
new file mode 100644
index 000000000..d6120c23e
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp
@@ -0,0 +1,2126 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <variant>
+
+#include <fmt/format.h>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_arb_decompiler.h"
+#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/shader/registry.h"
+#include "video_core/shader/shader_ir.h"
+
+// Predicates in the decompiled code follow the convention that -1 means true and 0 means false.
+// GLASM lacks booleans, so they have to be implemented as integers.
+// Using -1 for true is useful because both CMP.S and NOT.U can negate it, and CMP.S can be used to
+// select between two values, because -1 will be evaluated as true and 0 as false.
+
+namespace OpenGL {
+
+namespace {
+
+using Tegra::Engines::ShaderType;
+using Tegra::Shader::Attribute;
+using Tegra::Shader::PixelImap;
+using Tegra::Shader::Register;
+using namespace VideoCommon::Shader;
+using Operation = const OperationNode&;
+
+constexpr std::array INTERNAL_FLAG_NAMES = {"ZERO", "SIGN", "CARRY", "OVERFLOW"};
+
+char Swizzle(std::size_t component) {
+ static constexpr std::string_view SWIZZLE{"xyzw"};
+ return SWIZZLE.at(component);
+}
+
+constexpr bool IsGenericAttribute(Attribute::Index index) {
+ return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31;
+}
+
+u32 GetGenericAttributeIndex(Attribute::Index index) {
+ ASSERT(IsGenericAttribute(index));
+ return static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0);
+}
+
+std::string_view Modifiers(Operation operation) {
+ const auto meta = std::get_if<MetaArithmetic>(&operation.GetMeta());
+ if (meta && meta->precise) {
+ return ".PREC";
+ }
+ return "";
+}
+
+std::string_view GetInputFlags(PixelImap attribute) {
+ switch (attribute) {
+ case PixelImap::Perspective:
+ return "";
+ case PixelImap::Constant:
+ return "FLAT ";
+ case PixelImap::ScreenLinear:
+ return "NOPERSPECTIVE ";
+ case PixelImap::Unused:
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute));
+ return {};
+}
+
+std::string_view ImageType(Tegra::Shader::ImageType image_type) {
+ switch (image_type) {
+ case Tegra::Shader::ImageType::Texture1D:
+ return "1D";
+ case Tegra::Shader::ImageType::TextureBuffer:
+ return "BUFFER";
+ case Tegra::Shader::ImageType::Texture1DArray:
+ return "ARRAY1D";
+ case Tegra::Shader::ImageType::Texture2D:
+ return "2D";
+ case Tegra::Shader::ImageType::Texture2DArray:
+ return "ARRAY2D";
+ case Tegra::Shader::ImageType::Texture3D:
+ return "3D";
+ }
+ UNREACHABLE();
+ return {};
+}
+
+std::string_view StackName(MetaStackClass stack) {
+ switch (stack) {
+ case MetaStackClass::Ssy:
+ return "SSY";
+ case MetaStackClass::Pbk:
+ return "PBK";
+ }
+ UNREACHABLE();
+ return "";
+};
+
+std::string_view PrimitiveDescription(Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology topology) {
+ switch (topology) {
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::Points:
+ return "POINTS";
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::Lines:
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::LineStrip:
+ return "LINES";
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::LinesAdjacency:
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::LineStripAdjacency:
+ return "LINES_ADJACENCY";
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::Triangles:
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
+ return "TRIANGLES";
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
+ case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
+ return "TRIANGLES_ADJACENCY";
+ default:
+ UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology));
+ return "POINTS";
+ }
+}
+
+std::string_view TopologyName(Tegra::Shader::OutputTopology topology) {
+ switch (topology) {
+ case Tegra::Shader::OutputTopology::PointList:
+ return "POINTS";
+ case Tegra::Shader::OutputTopology::LineStrip:
+ return "LINE_STRIP";
+ case Tegra::Shader::OutputTopology::TriangleStrip:
+ return "TRIANGLE_STRIP";
+ default:
+ UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology));
+ return "points";
+ }
+}
+
+std::string_view StageInputName(ShaderType stage) {
+ switch (stage) {
+ case ShaderType::Vertex:
+ case ShaderType::Geometry:
+ return "vertex";
+ case ShaderType::Fragment:
+ return "fragment";
+ case ShaderType::Compute:
+ return "invocation";
+ default:
+ UNREACHABLE();
+ return "";
+ }
+}
+
+std::string TextureType(const MetaTexture& meta) {
+ if (meta.sampler.is_buffer) {
+ return "BUFFER";
+ }
+ std::string type;
+ if (meta.sampler.is_shadow) {
+ type += "SHADOW";
+ }
+ if (meta.sampler.is_array) {
+ type += "ARRAY";
+ }
+ type += [&meta] {
+ switch (meta.sampler.type) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return "1D";
+ case Tegra::Shader::TextureType::Texture2D:
+ return "2D";
+ case Tegra::Shader::TextureType::Texture3D:
+ return "3D";
+ case Tegra::Shader::TextureType::TextureCube:
+ return "CUBE";
+ }
+ UNREACHABLE();
+ return "2D";
+ }();
+ return type;
+}
+
+class ARBDecompiler final {
+public:
+ explicit ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
+ ShaderType stage, std::string_view identifier);
+
+ std::string Code() const {
+ return shader_source;
+ }
+
+private:
+ void DefineGlobalMemory();
+
+ void DeclareHeader();
+ void DeclareVertex();
+ void DeclareGeometry();
+ void DeclareFragment();
+ void DeclareCompute();
+ void DeclareInputAttributes();
+ void DeclareOutputAttributes();
+ void DeclareLocalMemory();
+ void DeclareGlobalMemory();
+ void DeclareConstantBuffers();
+ void DeclareRegisters();
+ void DeclareTemporaries();
+ void DeclarePredicates();
+ void DeclareInternalFlags();
+
+ void InitializeVariables();
+
+ void DecompileAST();
+ void DecompileBranchMode();
+
+ void VisitAST(const ASTNode& node);
+ std::string VisitExpression(const Expr& node);
+
+ void VisitBlock(const NodeBlock& bb);
+
+ std::string Visit(const Node& node);
+
+ std::tuple<std::string, std::string, std::size_t> BuildCoords(Operation);
+ std::string BuildAoffi(Operation);
+ std::string GlobalMemoryPointer(const GmemNode& gmem);
+ void Exit();
+
+ std::string Assign(Operation);
+ std::string Select(Operation);
+ std::string FClamp(Operation);
+ std::string FCastHalf0(Operation);
+ std::string FCastHalf1(Operation);
+ std::string FSqrt(Operation);
+ std::string FSwizzleAdd(Operation);
+ std::string HAdd2(Operation);
+ std::string HMul2(Operation);
+ std::string HFma2(Operation);
+ std::string HAbsolute(Operation);
+ std::string HNegate(Operation);
+ std::string HClamp(Operation);
+ std::string HCastFloat(Operation);
+ std::string HUnpack(Operation);
+ std::string HMergeF32(Operation);
+ std::string HMergeH0(Operation);
+ std::string HMergeH1(Operation);
+ std::string HPack2(Operation);
+ std::string LogicalAssign(Operation);
+ std::string LogicalPick2(Operation);
+ std::string LogicalAnd2(Operation);
+ std::string FloatOrdered(Operation);
+ std::string FloatUnordered(Operation);
+ std::string LogicalAddCarry(Operation);
+ std::string Texture(Operation);
+ std::string TextureGather(Operation);
+ std::string TextureQueryDimensions(Operation);
+ std::string TextureQueryLod(Operation);
+ std::string TexelFetch(Operation);
+ std::string TextureGradient(Operation);
+ std::string ImageLoad(Operation);
+ std::string ImageStore(Operation);
+ std::string Branch(Operation);
+ std::string BranchIndirect(Operation);
+ std::string PushFlowStack(Operation);
+ std::string PopFlowStack(Operation);
+ std::string Exit(Operation);
+ std::string Discard(Operation);
+ std::string EmitVertex(Operation);
+ std::string EndPrimitive(Operation);
+ std::string InvocationId(Operation);
+ std::string YNegate(Operation);
+ std::string ThreadId(Operation);
+ std::string ShuffleIndexed(Operation);
+ std::string Barrier(Operation);
+ std::string MemoryBarrierGroup(Operation);
+ std::string MemoryBarrierGlobal(Operation);
+
+ template <const std::string_view& op>
+ std::string Unary(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("{}{} {}, {};", op, Modifiers(operation), temporary, Visit(operation[0]));
+ return temporary;
+ }
+
+ template <const std::string_view& op>
+ std::string Binary(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("{}{} {}, {}, {};", op, Modifiers(operation), temporary, Visit(operation[0]),
+ Visit(operation[1]));
+ return temporary;
+ }
+
+ template <const std::string_view& op>
+ std::string Trinary(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("{}{} {}, {}, {}, {};", op, Modifiers(operation), temporary, Visit(operation[0]),
+ Visit(operation[1]), Visit(operation[2]));
+ return temporary;
+ }
+
+ template <const std::string_view& op, bool unordered>
+ std::string FloatComparison(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("TRUNC.U.CC RC.x, {};", Binary<op>(operation));
+ AddLine("MOV.S {}, 0;", temporary);
+ AddLine("MOV.S {} (NE.x), -1;", temporary);
+
+ const std::string op_a = Visit(operation[0]);
+ const std::string op_b = Visit(operation[1]);
+ if constexpr (unordered) {
+ AddLine("SNE.F RC.x, {}, {};", op_a, op_a);
+ AddLine("TRUNC.U.CC RC.x, RC.x;");
+ AddLine("MOV.S {} (NE.x), -1;", temporary);
+ AddLine("SNE.F RC.x, {}, {};", op_b, op_b);
+ AddLine("TRUNC.U.CC RC.x, RC.x;");
+ AddLine("MOV.S {} (NE.x), -1;", temporary);
+ } else if (op == SNE_F) {
+ AddLine("SNE.F RC.x, {}, {};", op_a, op_a);
+ AddLine("TRUNC.U.CC RC.x, RC.x;");
+ AddLine("MOV.S {} (NE.x), 0;", temporary);
+ AddLine("SNE.F RC.x, {}, {};", op_b, op_b);
+ AddLine("TRUNC.U.CC RC.x, RC.x;");
+ AddLine("MOV.S {} (NE.x), 0;", temporary);
+ }
+ return temporary;
+ }
+
+ template <const std::string_view& op, bool is_nan>
+ std::string HalfComparison(Operation operation) {
+ std::string tmp1 = AllocVectorTemporary();
+ const std::string tmp2 = AllocVectorTemporary();
+ const std::string op_a = Visit(operation[0]);
+ const std::string op_b = Visit(operation[1]);
+ AddLine("UP2H.F {}, {};", tmp1, op_a);
+ AddLine("UP2H.F {}, {};", tmp2, op_b);
+ AddLine("{} {}, {}, {};", op, tmp1, tmp1, tmp2);
+ AddLine("TRUNC.U.CC RC.xy, {};", tmp1);
+ AddLine("MOV.S {}.xy, {{0, 0, 0, 0}};", tmp1);
+ AddLine("MOV.S {}.x (NE.x), -1;", tmp1);
+ AddLine("MOV.S {}.y (NE.y), -1;", tmp1);
+ if constexpr (is_nan) {
+ AddLine("MOVC.F RC.x, {};", op_a);
+ AddLine("MOV.S {}.x (NAN.x), -1;", tmp1);
+ AddLine("MOVC.F RC.x, {};", op_b);
+ AddLine("MOV.S {}.y (NAN.x), -1;", tmp1);
+ }
+ return tmp1;
+ }
+
+ template <const std::string_view& op, const std::string_view& type>
+ std::string AtomicImage(Operation operation) {
+ const auto& meta = std::get<MetaImage>(operation.GetMeta());
+ const u32 image_id = device.GetBaseBindings(stage).image + meta.image.index;
+ const std::size_t num_coords = operation.GetOperandsCount();
+ const std::size_t num_values = meta.values.size();
+
+ const std::string coord = AllocVectorTemporary();
+ const std::string value = AllocVectorTemporary();
+ for (std::size_t i = 0; i < num_coords; ++i) {
+ AddLine("MOV.S {}.{}, {};", coord, Swizzle(i), Visit(operation[i]));
+ }
+ for (std::size_t i = 0; i < num_values; ++i) {
+ AddLine("MOV.F {}.{}, {};", value, Swizzle(i), Visit(meta.values[i]));
+ }
+
+ AddLine("ATOMIM.{}.{} {}.x, {}, {}, image[{}], {};", op, type, coord, value, coord,
+ image_id, ImageType(meta.image.type));
+ return fmt::format("{}.x", coord);
+ }
+
+ template <const std::string_view& op, const std::string_view& type>
+ std::string Atomic(Operation operation) {
+ std::string temporary = AllocTemporary();
+ std::string address;
+ std::string_view opname;
+ bool robust = false;
+ if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) {
+ address = GlobalMemoryPointer(*gmem);
+ opname = "ATOM";
+ robust = true;
+ } else if (const auto smem = std::get_if<SmemNode>(&*operation[0])) {
+ address = fmt::format("shared_mem[{}]", Visit(smem->GetAddress()));
+ opname = "ATOMS";
+ } else {
+ UNREACHABLE();
+ return "{0, 0, 0, 0}";
+ }
+ if (robust) {
+ AddLine("IF NE.x;");
+ }
+ AddLine("{}.{}.{} {}, {}, {};", opname, op, type, temporary, Visit(operation[1]), address);
+ if (robust) {
+ AddLine("ELSE;");
+ AddLine("MOV.S {}, 0;", temporary);
+ AddLine("ENDIF;");
+ }
+ return temporary;
+ }
+
+ template <char type>
+ std::string Negate(Operation operation) {
+ std::string temporary = AllocTemporary();
+ if constexpr (type == 'F') {
+ AddLine("MOV.F32 {}, -{};", temporary, Visit(operation[0]));
+ } else {
+ AddLine("MOV.{} {}, -{};", type, temporary, Visit(operation[0]));
+ }
+ return temporary;
+ }
+
+ template <char type>
+ std::string Absolute(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("MOV.{} {}, |{}|;", type, temporary, Visit(operation[0]));
+ return temporary;
+ }
+
+ template <char type>
+ std::string BitfieldInsert(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("MOV.{} {}.x, {};", type, temporary, Visit(operation[3]));
+ AddLine("MOV.{} {}.y, {};", type, temporary, Visit(operation[2]));
+ AddLine("BFI.{} {}.x, {}, {}, {};", type, temporary, temporary, Visit(operation[1]),
+ Visit(operation[0]));
+ return fmt::format("{}.x", temporary);
+ }
+
+ template <char type>
+ std::string BitfieldExtract(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("MOV.{} {}.x, {};", type, temporary, Visit(operation[2]));
+ AddLine("MOV.{} {}.y, {};", type, temporary, Visit(operation[1]));
+ AddLine("BFE.{} {}.x, {}, {};", type, temporary, temporary, Visit(operation[0]));
+ return fmt::format("{}.x", temporary);
+ }
+
+ template <char swizzle>
+ std::string LocalInvocationId(Operation) {
+ return fmt::format("invocation.localid.{}", swizzle);
+ }
+
+ template <char swizzle>
+ std::string WorkGroupId(Operation) {
+ return fmt::format("invocation.groupid.{}", swizzle);
+ }
+
+ template <char c1, char c2>
+ std::string ThreadMask(Operation) {
+ return fmt::format("{}.thread{}{}mask", StageInputName(stage), c1, c2);
+ }
+
+ template <typename... Args>
+ void AddExpression(std::string_view text, Args&&... args) {
+ shader_source += fmt::format(text, std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ void AddLine(std::string_view text, Args&&... args) {
+ AddExpression(text, std::forward<Args>(args)...);
+ shader_source += '\n';
+ }
+
+ std::string AllocLongVectorTemporary() {
+ max_long_temporaries = std::max(max_long_temporaries, num_long_temporaries + 1);
+ return fmt::format("L{}", num_long_temporaries++);
+ }
+
+ std::string AllocLongTemporary() {
+ return fmt::format("{}.x", AllocLongVectorTemporary());
+ }
+
+ std::string AllocVectorTemporary() {
+ max_temporaries = std::max(max_temporaries, num_temporaries + 1);
+ return fmt::format("T{}", num_temporaries++);
+ }
+
+ std::string AllocTemporary() {
+ return fmt::format("{}.x", AllocVectorTemporary());
+ }
+
+ void ResetTemporaries() noexcept {
+ num_temporaries = 0;
+ num_long_temporaries = 0;
+ }
+
+ const Device& device;
+ const ShaderIR& ir;
+ const Registry& registry;
+ const ShaderType stage;
+
+ std::size_t num_temporaries = 0;
+ std::size_t max_temporaries = 0;
+
+ std::size_t num_long_temporaries = 0;
+ std::size_t max_long_temporaries = 0;
+
+ std::map<GlobalMemoryBase, u32> global_memory_names;
+
+ std::string shader_source;
+
+ static constexpr std::string_view ADD_F32 = "ADD.F32";
+ static constexpr std::string_view ADD_S = "ADD.S";
+ static constexpr std::string_view ADD_U = "ADD.U";
+ static constexpr std::string_view MUL_F32 = "MUL.F32";
+ static constexpr std::string_view MUL_S = "MUL.S";
+ static constexpr std::string_view MUL_U = "MUL.U";
+ static constexpr std::string_view DIV_F32 = "DIV.F32";
+ static constexpr std::string_view DIV_S = "DIV.S";
+ static constexpr std::string_view DIV_U = "DIV.U";
+ static constexpr std::string_view MAD_F32 = "MAD.F32";
+ static constexpr std::string_view RSQ_F32 = "RSQ.F32";
+ static constexpr std::string_view COS_F32 = "COS.F32";
+ static constexpr std::string_view SIN_F32 = "SIN.F32";
+ static constexpr std::string_view EX2_F32 = "EX2.F32";
+ static constexpr std::string_view LG2_F32 = "LG2.F32";
+ static constexpr std::string_view SLT_F = "SLT.F32";
+ static constexpr std::string_view SLT_S = "SLT.S";
+ static constexpr std::string_view SLT_U = "SLT.U";
+ static constexpr std::string_view SEQ_F = "SEQ.F32";
+ static constexpr std::string_view SEQ_S = "SEQ.S";
+ static constexpr std::string_view SEQ_U = "SEQ.U";
+ static constexpr std::string_view SLE_F = "SLE.F32";
+ static constexpr std::string_view SLE_S = "SLE.S";
+ static constexpr std::string_view SLE_U = "SLE.U";
+ static constexpr std::string_view SGT_F = "SGT.F32";
+ static constexpr std::string_view SGT_S = "SGT.S";
+ static constexpr std::string_view SGT_U = "SGT.U";
+ static constexpr std::string_view SNE_F = "SNE.F32";
+ static constexpr std::string_view SNE_S = "SNE.S";
+ static constexpr std::string_view SNE_U = "SNE.U";
+ static constexpr std::string_view SGE_F = "SGE.F32";
+ static constexpr std::string_view SGE_S = "SGE.S";
+ static constexpr std::string_view SGE_U = "SGE.U";
+ static constexpr std::string_view AND_S = "AND.S";
+ static constexpr std::string_view AND_U = "AND.U";
+ static constexpr std::string_view TRUNC_F = "TRUNC.F";
+ static constexpr std::string_view TRUNC_S = "TRUNC.S";
+ static constexpr std::string_view TRUNC_U = "TRUNC.U";
+ static constexpr std::string_view SHL_S = "SHL.S";
+ static constexpr std::string_view SHL_U = "SHL.U";
+ static constexpr std::string_view SHR_S = "SHR.S";
+ static constexpr std::string_view SHR_U = "SHR.U";
+ static constexpr std::string_view OR_S = "OR.S";
+ static constexpr std::string_view OR_U = "OR.U";
+ static constexpr std::string_view XOR_S = "XOR.S";
+ static constexpr std::string_view XOR_U = "XOR.U";
+ static constexpr std::string_view NOT_S = "NOT.S";
+ static constexpr std::string_view NOT_U = "NOT.U";
+ static constexpr std::string_view BTC_S = "BTC.S";
+ static constexpr std::string_view BTC_U = "BTC.U";
+ static constexpr std::string_view BTFM_S = "BTFM.S";
+ static constexpr std::string_view BTFM_U = "BTFM.U";
+ static constexpr std::string_view ROUND_F = "ROUND.F";
+ static constexpr std::string_view CEIL_F = "CEIL.F";
+ static constexpr std::string_view FLR_F = "FLR.F";
+ static constexpr std::string_view I2F_S = "I2F.S";
+ static constexpr std::string_view I2F_U = "I2F.U";
+ static constexpr std::string_view MIN_F = "MIN.F";
+ static constexpr std::string_view MIN_S = "MIN.S";
+ static constexpr std::string_view MIN_U = "MIN.U";
+ static constexpr std::string_view MAX_F = "MAX.F";
+ static constexpr std::string_view MAX_S = "MAX.S";
+ static constexpr std::string_view MAX_U = "MAX.U";
+ static constexpr std::string_view MOV_U = "MOV.U";
+ static constexpr std::string_view TGBALLOT_U = "TGBALLOT.U";
+ static constexpr std::string_view TGALL_U = "TGALL.U";
+ static constexpr std::string_view TGANY_U = "TGANY.U";
+ static constexpr std::string_view TGEQ_U = "TGEQ.U";
+ static constexpr std::string_view EXCH = "EXCH";
+ static constexpr std::string_view ADD = "ADD";
+ static constexpr std::string_view MIN = "MIN";
+ static constexpr std::string_view MAX = "MAX";
+ static constexpr std::string_view AND = "AND";
+ static constexpr std::string_view OR = "OR";
+ static constexpr std::string_view XOR = "XOR";
+ static constexpr std::string_view U32 = "U32";
+ static constexpr std::string_view S32 = "S32";
+
+ static constexpr std::size_t NUM_ENTRIES = static_cast<std::size_t>(OperationCode::Amount);
+ using DecompilerType = std::string (ARBDecompiler::*)(Operation);
+ static constexpr std::array<DecompilerType, NUM_ENTRIES> OPERATION_DECOMPILERS = {
+ &ARBDecompiler::Assign,
+
+ &ARBDecompiler::Select,
+
+ &ARBDecompiler::Binary<ADD_F32>,
+ &ARBDecompiler::Binary<MUL_F32>,
+ &ARBDecompiler::Binary<DIV_F32>,
+ &ARBDecompiler::Trinary<MAD_F32>,
+ &ARBDecompiler::Negate<'F'>,
+ &ARBDecompiler::Absolute<'F'>,
+ &ARBDecompiler::FClamp,
+ &ARBDecompiler::FCastHalf0,
+ &ARBDecompiler::FCastHalf1,
+ &ARBDecompiler::Binary<MIN_F>,
+ &ARBDecompiler::Binary<MAX_F>,
+ &ARBDecompiler::Unary<COS_F32>,
+ &ARBDecompiler::Unary<SIN_F32>,
+ &ARBDecompiler::Unary<EX2_F32>,
+ &ARBDecompiler::Unary<LG2_F32>,
+ &ARBDecompiler::Unary<RSQ_F32>,
+ &ARBDecompiler::FSqrt,
+ &ARBDecompiler::Unary<ROUND_F>,
+ &ARBDecompiler::Unary<FLR_F>,
+ &ARBDecompiler::Unary<CEIL_F>,
+ &ARBDecompiler::Unary<TRUNC_F>,
+ &ARBDecompiler::Unary<I2F_S>,
+ &ARBDecompiler::Unary<I2F_U>,
+ &ARBDecompiler::FSwizzleAdd,
+
+ &ARBDecompiler::Binary<ADD_S>,
+ &ARBDecompiler::Binary<MUL_S>,
+ &ARBDecompiler::Binary<DIV_S>,
+ &ARBDecompiler::Negate<'S'>,
+ &ARBDecompiler::Absolute<'S'>,
+ &ARBDecompiler::Binary<MIN_S>,
+ &ARBDecompiler::Binary<MAX_S>,
+
+ &ARBDecompiler::Unary<TRUNC_S>,
+ &ARBDecompiler::Unary<MOV_U>,
+ &ARBDecompiler::Binary<SHL_S>,
+ &ARBDecompiler::Binary<SHR_U>,
+ &ARBDecompiler::Binary<SHR_S>,
+ &ARBDecompiler::Binary<AND_S>,
+ &ARBDecompiler::Binary<OR_S>,
+ &ARBDecompiler::Binary<XOR_S>,
+ &ARBDecompiler::Unary<NOT_S>,
+ &ARBDecompiler::BitfieldInsert<'S'>,
+ &ARBDecompiler::BitfieldExtract<'S'>,
+ &ARBDecompiler::Unary<BTC_S>,
+ &ARBDecompiler::Unary<BTFM_S>,
+
+ &ARBDecompiler::Binary<ADD_U>,
+ &ARBDecompiler::Binary<MUL_U>,
+ &ARBDecompiler::Binary<DIV_U>,
+ &ARBDecompiler::Binary<MIN_U>,
+ &ARBDecompiler::Binary<MAX_U>,
+ &ARBDecompiler::Unary<TRUNC_U>,
+ &ARBDecompiler::Unary<MOV_U>,
+ &ARBDecompiler::Binary<SHL_U>,
+ &ARBDecompiler::Binary<SHR_U>,
+ &ARBDecompiler::Binary<SHR_U>,
+ &ARBDecompiler::Binary<AND_U>,
+ &ARBDecompiler::Binary<OR_U>,
+ &ARBDecompiler::Binary<XOR_U>,
+ &ARBDecompiler::Unary<NOT_U>,
+ &ARBDecompiler::BitfieldInsert<'U'>,
+ &ARBDecompiler::BitfieldExtract<'U'>,
+ &ARBDecompiler::Unary<BTC_U>,
+ &ARBDecompiler::Unary<BTFM_U>,
+
+ &ARBDecompiler::HAdd2,
+ &ARBDecompiler::HMul2,
+ &ARBDecompiler::HFma2,
+ &ARBDecompiler::HAbsolute,
+ &ARBDecompiler::HNegate,
+ &ARBDecompiler::HClamp,
+ &ARBDecompiler::HCastFloat,
+ &ARBDecompiler::HUnpack,
+ &ARBDecompiler::HMergeF32,
+ &ARBDecompiler::HMergeH0,
+ &ARBDecompiler::HMergeH1,
+ &ARBDecompiler::HPack2,
+
+ &ARBDecompiler::LogicalAssign,
+ &ARBDecompiler::Binary<AND_U>,
+ &ARBDecompiler::Binary<OR_U>,
+ &ARBDecompiler::Binary<XOR_U>,
+ &ARBDecompiler::Unary<NOT_U>,
+ &ARBDecompiler::LogicalPick2,
+ &ARBDecompiler::LogicalAnd2,
+
+ &ARBDecompiler::FloatComparison<SLT_F, false>,
+ &ARBDecompiler::FloatComparison<SEQ_F, false>,
+ &ARBDecompiler::FloatComparison<SLE_F, false>,
+ &ARBDecompiler::FloatComparison<SGT_F, false>,
+ &ARBDecompiler::FloatComparison<SNE_F, false>,
+ &ARBDecompiler::FloatComparison<SGE_F, false>,
+ &ARBDecompiler::FloatOrdered,
+ &ARBDecompiler::FloatUnordered,
+ &ARBDecompiler::FloatComparison<SLT_F, true>,
+ &ARBDecompiler::FloatComparison<SEQ_F, true>,
+ &ARBDecompiler::FloatComparison<SLE_F, true>,
+ &ARBDecompiler::FloatComparison<SGT_F, true>,
+ &ARBDecompiler::FloatComparison<SNE_F, true>,
+ &ARBDecompiler::FloatComparison<SGE_F, true>,
+
+ &ARBDecompiler::Binary<SLT_S>,
+ &ARBDecompiler::Binary<SEQ_S>,
+ &ARBDecompiler::Binary<SLE_S>,
+ &ARBDecompiler::Binary<SGT_S>,
+ &ARBDecompiler::Binary<SNE_S>,
+ &ARBDecompiler::Binary<SGE_S>,
+
+ &ARBDecompiler::Binary<SLT_U>,
+ &ARBDecompiler::Binary<SEQ_U>,
+ &ARBDecompiler::Binary<SLE_U>,
+ &ARBDecompiler::Binary<SGT_U>,
+ &ARBDecompiler::Binary<SNE_U>,
+ &ARBDecompiler::Binary<SGE_U>,
+
+ &ARBDecompiler::LogicalAddCarry,
+
+ &ARBDecompiler::HalfComparison<SLT_F, false>,
+ &ARBDecompiler::HalfComparison<SEQ_F, false>,
+ &ARBDecompiler::HalfComparison<SLE_F, false>,
+ &ARBDecompiler::HalfComparison<SGT_F, false>,
+ &ARBDecompiler::HalfComparison<SNE_F, false>,
+ &ARBDecompiler::HalfComparison<SGE_F, false>,
+ &ARBDecompiler::HalfComparison<SLT_F, true>,
+ &ARBDecompiler::HalfComparison<SEQ_F, true>,
+ &ARBDecompiler::HalfComparison<SLE_F, true>,
+ &ARBDecompiler::HalfComparison<SGT_F, true>,
+ &ARBDecompiler::HalfComparison<SNE_F, true>,
+ &ARBDecompiler::HalfComparison<SGE_F, true>,
+
+ &ARBDecompiler::Texture,
+ &ARBDecompiler::Texture,
+ &ARBDecompiler::TextureGather,
+ &ARBDecompiler::TextureQueryDimensions,
+ &ARBDecompiler::TextureQueryLod,
+ &ARBDecompiler::TexelFetch,
+ &ARBDecompiler::TextureGradient,
+
+ &ARBDecompiler::ImageLoad,
+ &ARBDecompiler::ImageStore,
+
+ &ARBDecompiler::AtomicImage<ADD, U32>,
+ &ARBDecompiler::AtomicImage<AND, U32>,
+ &ARBDecompiler::AtomicImage<OR, U32>,
+ &ARBDecompiler::AtomicImage<XOR, U32>,
+ &ARBDecompiler::AtomicImage<EXCH, U32>,
+
+ &ARBDecompiler::Atomic<EXCH, U32>,
+ &ARBDecompiler::Atomic<ADD, U32>,
+ &ARBDecompiler::Atomic<MIN, U32>,
+ &ARBDecompiler::Atomic<MAX, U32>,
+ &ARBDecompiler::Atomic<AND, U32>,
+ &ARBDecompiler::Atomic<OR, U32>,
+ &ARBDecompiler::Atomic<XOR, U32>,
+
+ &ARBDecompiler::Atomic<EXCH, S32>,
+ &ARBDecompiler::Atomic<ADD, S32>,
+ &ARBDecompiler::Atomic<MIN, S32>,
+ &ARBDecompiler::Atomic<MAX, S32>,
+ &ARBDecompiler::Atomic<AND, S32>,
+ &ARBDecompiler::Atomic<OR, S32>,
+ &ARBDecompiler::Atomic<XOR, S32>,
+
+ &ARBDecompiler::Atomic<ADD, U32>,
+ &ARBDecompiler::Atomic<MIN, U32>,
+ &ARBDecompiler::Atomic<MAX, U32>,
+ &ARBDecompiler::Atomic<AND, U32>,
+ &ARBDecompiler::Atomic<OR, U32>,
+ &ARBDecompiler::Atomic<XOR, U32>,
+
+ &ARBDecompiler::Atomic<ADD, S32>,
+ &ARBDecompiler::Atomic<MIN, S32>,
+ &ARBDecompiler::Atomic<MAX, S32>,
+ &ARBDecompiler::Atomic<AND, S32>,
+ &ARBDecompiler::Atomic<OR, S32>,
+ &ARBDecompiler::Atomic<XOR, S32>,
+
+ &ARBDecompiler::Branch,
+ &ARBDecompiler::BranchIndirect,
+ &ARBDecompiler::PushFlowStack,
+ &ARBDecompiler::PopFlowStack,
+ &ARBDecompiler::Exit,
+ &ARBDecompiler::Discard,
+
+ &ARBDecompiler::EmitVertex,
+ &ARBDecompiler::EndPrimitive,
+
+ &ARBDecompiler::InvocationId,
+ &ARBDecompiler::YNegate,
+ &ARBDecompiler::LocalInvocationId<'x'>,
+ &ARBDecompiler::LocalInvocationId<'y'>,
+ &ARBDecompiler::LocalInvocationId<'z'>,
+ &ARBDecompiler::WorkGroupId<'x'>,
+ &ARBDecompiler::WorkGroupId<'y'>,
+ &ARBDecompiler::WorkGroupId<'z'>,
+
+ &ARBDecompiler::Unary<TGBALLOT_U>,
+ &ARBDecompiler::Unary<TGALL_U>,
+ &ARBDecompiler::Unary<TGANY_U>,
+ &ARBDecompiler::Unary<TGEQ_U>,
+
+ &ARBDecompiler::ThreadId,
+ &ARBDecompiler::ThreadMask<'e', 'q'>,
+ &ARBDecompiler::ThreadMask<'g', 'e'>,
+ &ARBDecompiler::ThreadMask<'g', 't'>,
+ &ARBDecompiler::ThreadMask<'l', 'e'>,
+ &ARBDecompiler::ThreadMask<'l', 't'>,
+ &ARBDecompiler::ShuffleIndexed,
+
+ &ARBDecompiler::Barrier,
+ &ARBDecompiler::MemoryBarrierGroup,
+ &ARBDecompiler::MemoryBarrierGlobal,
+ };
+};
+
+ARBDecompiler::ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
+ ShaderType stage, std::string_view identifier)
+ : device{device}, ir{ir}, registry{registry}, stage{stage} {
+ DefineGlobalMemory();
+
+ AddLine("TEMP RC;");
+ AddLine("TEMP FSWZA[4];");
+ AddLine("TEMP FSWZB[4];");
+ if (ir.IsDecompiled()) {
+ DecompileAST();
+ } else {
+ DecompileBranchMode();
+ }
+ AddLine("END");
+
+ const std::string code = std::move(shader_source);
+ DeclareHeader();
+ DeclareVertex();
+ DeclareGeometry();
+ DeclareFragment();
+ DeclareCompute();
+ DeclareInputAttributes();
+ DeclareOutputAttributes();
+ DeclareLocalMemory();
+ DeclareGlobalMemory();
+ DeclareConstantBuffers();
+ DeclareRegisters();
+ DeclareTemporaries();
+ DeclarePredicates();
+ DeclareInternalFlags();
+
+ shader_source += code;
+}
+
+std::string_view HeaderStageName(ShaderType stage) {
+ switch (stage) {
+ case ShaderType::Vertex:
+ return "vp";
+ case ShaderType::Geometry:
+ return "gp";
+ case ShaderType::Fragment:
+ return "fp";
+ case ShaderType::Compute:
+ return "cp";
+ default:
+ UNREACHABLE();
+ return "";
+ }
+}
+
+void ARBDecompiler::DefineGlobalMemory() {
+ u32 binding = 0;
+ for (const auto& pair : ir.GetGlobalMemory()) {
+ const GlobalMemoryBase base = pair.first;
+ global_memory_names.emplace(base, binding);
+ ++binding;
+ }
+}
+
+void ARBDecompiler::DeclareHeader() {
+ AddLine("!!NV{}5.0", HeaderStageName(stage));
+ // Enabling this allows us to cheat on some instructions like TXL with SHADOWARRAY2D
+ AddLine("OPTION NV_internal;");
+ AddLine("OPTION NV_gpu_program_fp64;");
+ AddLine("OPTION NV_shader_thread_group;");
+ if (ir.UsesWarps() && device.HasWarpIntrinsics()) {
+ AddLine("OPTION NV_shader_thread_shuffle;");
+ }
+ if (stage == ShaderType::Vertex) {
+ if (device.HasNvViewportArray2()) {
+ AddLine("OPTION NV_viewport_array2;");
+ }
+ }
+ if (stage == ShaderType::Fragment) {
+ AddLine("OPTION ARB_draw_buffers;");
+ }
+ if (device.HasImageLoadFormatted()) {
+ AddLine("OPTION EXT_shader_image_load_formatted;");
+ }
+}
+
+void ARBDecompiler::DeclareVertex() {
+ if (stage != ShaderType::Vertex) {
+ return;
+ }
+ AddLine("OUTPUT result_clip[] = {{ result.clip[0..7] }};");
+}
+
+void ARBDecompiler::DeclareGeometry() {
+ if (stage != ShaderType::Geometry) {
+ return;
+ }
+ const auto& info = registry.GetGraphicsInfo();
+ const auto& header = ir.GetHeader();
+ AddLine("PRIMITIVE_IN {};", PrimitiveDescription(info.primitive_topology));
+ AddLine("PRIMITIVE_OUT {};", TopologyName(header.common3.output_topology));
+ AddLine("VERTICES_OUT {};", header.common4.max_output_vertices.Value());
+ AddLine("ATTRIB vertex_position = vertex.position;");
+}
+
+void ARBDecompiler::DeclareFragment() {
+ if (stage != ShaderType::Fragment) {
+ return;
+ }
+ AddLine("OUTPUT result_color7 = result.color[7];");
+ AddLine("OUTPUT result_color6 = result.color[6];");
+ AddLine("OUTPUT result_color5 = result.color[5];");
+ AddLine("OUTPUT result_color4 = result.color[4];");
+ AddLine("OUTPUT result_color3 = result.color[3];");
+ AddLine("OUTPUT result_color2 = result.color[2];");
+ AddLine("OUTPUT result_color1 = result.color[1];");
+ AddLine("OUTPUT result_color0 = result.color;");
+}
+
+void ARBDecompiler::DeclareCompute() {
+ if (stage != ShaderType::Compute) {
+ return;
+ }
+ const ComputeInfo& info = registry.GetComputeInfo();
+ AddLine("GROUP_SIZE {} {} {};", info.workgroup_size[0], info.workgroup_size[1],
+ info.workgroup_size[2]);
+ if (info.shared_memory_size_in_words == 0) {
+ return;
+ }
+ const u32 limit = device.GetMaxComputeSharedMemorySize();
+ u32 size_in_bytes = info.shared_memory_size_in_words * 4;
+ if (size_in_bytes > limit) {
+ LOG_ERROR(Render_OpenGL, "Shared memory size {} is clamped to host's limit {}",
+ size_in_bytes, limit);
+ size_in_bytes = limit;
+ }
+
+ AddLine("SHARED_MEMORY {};", size_in_bytes);
+ AddLine("SHARED shared_mem[] = {{program.sharedmem}};");
+}
+
+void ARBDecompiler::DeclareInputAttributes() {
+ if (stage == ShaderType::Compute) {
+ return;
+ }
+ const std::string_view stage_name = StageInputName(stage);
+ for (const auto attribute : ir.GetInputAttributes()) {
+ if (!IsGenericAttribute(attribute)) {
+ continue;
+ }
+ const u32 index = GetGenericAttributeIndex(attribute);
+
+ std::string_view suffix;
+ if (stage == ShaderType::Fragment) {
+ const auto input_mode{ir.GetHeader().ps.GetPixelImap(index)};
+ if (input_mode == PixelImap::Unused) {
+ return;
+ }
+ suffix = GetInputFlags(input_mode);
+ }
+ AddLine("{}ATTRIB in_attr{}[] = {{ {}.attrib[{}..{}] }};", suffix, index, stage_name, index,
+ index);
+ }
+}
+
+void ARBDecompiler::DeclareOutputAttributes() {
+ if (stage == ShaderType::Compute) {
+ return;
+ }
+ for (const auto attribute : ir.GetOutputAttributes()) {
+ if (!IsGenericAttribute(attribute)) {
+ continue;
+ }
+ const u32 index = GetGenericAttributeIndex(attribute);
+ AddLine("OUTPUT out_attr{}[] = {{ result.attrib[{}..{}] }};", index, index, index);
+ }
+}
+
+void ARBDecompiler::DeclareLocalMemory() {
+ u64 size = 0;
+ if (stage == ShaderType::Compute) {
+ size = registry.GetComputeInfo().local_memory_size_in_words * 4ULL;
+ } else {
+ size = ir.GetHeader().GetLocalMemorySize();
+ }
+ if (size == 0) {
+ return;
+ }
+ const u64 element_count = Common::AlignUp(size, 4) / 4;
+ AddLine("TEMP lmem[{}];", element_count);
+}
+
+void ARBDecompiler::DeclareGlobalMemory() {
+ const size_t num_entries = ir.GetGlobalMemory().size();
+ if (num_entries > 0) {
+ AddLine("PARAM c[{}] = {{ program.local[0..{}] }};", num_entries, num_entries - 1);
+ }
+}
+
+void ARBDecompiler::DeclareConstantBuffers() {
+ u32 binding = 0;
+ for (const auto& cbuf : ir.GetConstantBuffers()) {
+ AddLine("CBUFFER cbuf{}[] = {{ program.buffer[{}] }};", cbuf.first, binding);
+ ++binding;
+ }
+}
+
+void ARBDecompiler::DeclareRegisters() {
+ for (const u32 gpr : ir.GetRegisters()) {
+ AddLine("TEMP R{};", gpr);
+ }
+}
+
+void ARBDecompiler::DeclareTemporaries() {
+ for (std::size_t i = 0; i < max_temporaries; ++i) {
+ AddLine("TEMP T{};", i);
+ }
+ for (std::size_t i = 0; i < max_long_temporaries; ++i) {
+ AddLine("LONG TEMP L{};", i);
+ }
+}
+
+void ARBDecompiler::DeclarePredicates() {
+ for (const Tegra::Shader::Pred pred : ir.GetPredicates()) {
+ AddLine("TEMP P{};", static_cast<u64>(pred));
+ }
+}
+
+void ARBDecompiler::DeclareInternalFlags() {
+ for (const char* name : INTERNAL_FLAG_NAMES) {
+ AddLine("TEMP {};", name);
+ }
+}
+
+void ARBDecompiler::InitializeVariables() {
+ AddLine("MOV.F32 FSWZA[0], -1;");
+ AddLine("MOV.F32 FSWZA[1], 1;");
+ AddLine("MOV.F32 FSWZA[2], -1;");
+ AddLine("MOV.F32 FSWZA[3], 0;");
+ AddLine("MOV.F32 FSWZB[0], -1;");
+ AddLine("MOV.F32 FSWZB[1], -1;");
+ AddLine("MOV.F32 FSWZB[2], 1;");
+ AddLine("MOV.F32 FSWZB[3], -1;");
+
+ if (stage == ShaderType::Vertex || stage == ShaderType::Geometry) {
+ AddLine("MOV.F result.position, {{0, 0, 0, 1}};");
+ }
+ for (const auto attribute : ir.GetOutputAttributes()) {
+ if (!IsGenericAttribute(attribute)) {
+ continue;
+ }
+ const u32 index = GetGenericAttributeIndex(attribute);
+ AddLine("MOV.F result.attrib[{}], {{0, 0, 0, 1}};", index);
+ }
+ for (const u32 gpr : ir.GetRegisters()) {
+ AddLine("MOV.F R{}, {{0, 0, 0, 0}};", gpr);
+ }
+ for (const Tegra::Shader::Pred pred : ir.GetPredicates()) {
+ AddLine("MOV.U P{}, {{0, 0, 0, 0}};", static_cast<u64>(pred));
+ }
+}
+
+void ARBDecompiler::DecompileAST() {
+ const u32 num_flow_variables = ir.GetASTNumVariables();
+ for (u32 i = 0; i < num_flow_variables; ++i) {
+ AddLine("TEMP F{};", i);
+ }
+ for (u32 i = 0; i < num_flow_variables; ++i) {
+ AddLine("MOV.U F{}, {{0, 0, 0, 0}};", i);
+ }
+
+ InitializeVariables();
+
+ VisitAST(ir.GetASTProgram());
+}
+
+void ARBDecompiler::DecompileBranchMode() {
+ static constexpr u32 FLOW_STACK_SIZE = 20;
+ if (!ir.IsFlowStackDisabled()) {
+ AddLine("TEMP SSY[{}];", FLOW_STACK_SIZE);
+ AddLine("TEMP PBK[{}];", FLOW_STACK_SIZE);
+ AddLine("TEMP SSY_TOP;");
+ AddLine("TEMP PBK_TOP;");
+ }
+
+ AddLine("TEMP PC;");
+
+ if (!ir.IsFlowStackDisabled()) {
+ AddLine("MOV.U SSY_TOP.x, 0;");
+ AddLine("MOV.U PBK_TOP.x, 0;");
+ }
+
+ InitializeVariables();
+
+ const auto basic_block_end = ir.GetBasicBlocks().end();
+ auto basic_block_it = ir.GetBasicBlocks().begin();
+ const u32 first_address = basic_block_it->first;
+ AddLine("MOV.U PC.x, {};", first_address);
+
+ AddLine("REP;");
+
+ std::size_t num_blocks = 0;
+ while (basic_block_it != basic_block_end) {
+ const auto& [address, bb] = *basic_block_it;
+ ++num_blocks;
+
+ AddLine("SEQ.S.CC RC.x, PC.x, {};", address);
+ AddLine("IF NE.x;");
+
+ VisitBlock(bb);
+
+ ++basic_block_it;
+
+ if (basic_block_it != basic_block_end) {
+ const auto op = std::get_if<OperationNode>(&*bb[bb.size() - 1]);
+ if (!op || op->GetCode() != OperationCode::Branch) {
+ const u32 next_address = basic_block_it->first;
+ AddLine("MOV.U PC.x, {};", next_address);
+ AddLine("CONT;");
+ }
+ }
+
+ AddLine("ELSE;");
+ }
+ AddLine("RET;");
+ while (num_blocks--) {
+ AddLine("ENDIF;");
+ }
+
+ AddLine("ENDREP;");
+}
+
+void ARBDecompiler::VisitAST(const ASTNode& node) {
+ if (const auto ast = std::get_if<ASTProgram>(&*node->GetInnerData())) {
+ for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ VisitAST(current);
+ }
+ } else if (const auto ast = std::get_if<ASTIfThen>(&*node->GetInnerData())) {
+ const std::string condition = VisitExpression(ast->condition);
+ ResetTemporaries();
+
+ AddLine("MOVC.U RC.x, {};", condition);
+ AddLine("IF NE.x;");
+ for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ VisitAST(current);
+ }
+ AddLine("ENDIF;");
+ } else if (const auto ast = std::get_if<ASTIfElse>(&*node->GetInnerData())) {
+ AddLine("ELSE;");
+ for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ VisitAST(current);
+ }
+ } else if (const auto ast = std::get_if<ASTBlockDecoded>(&*node->GetInnerData())) {
+ VisitBlock(ast->nodes);
+ } else if (const auto ast = std::get_if<ASTVarSet>(&*node->GetInnerData())) {
+ AddLine("MOV.U F{}, {};", ast->index, VisitExpression(ast->condition));
+ ResetTemporaries();
+ } else if (const auto ast = std::get_if<ASTDoWhile>(&*node->GetInnerData())) {
+ const std::string condition = VisitExpression(ast->condition);
+ ResetTemporaries();
+ AddLine("REP;");
+ for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ VisitAST(current);
+ }
+ AddLine("MOVC.U RC.x, {};", condition);
+ AddLine("BRK (NE.x);");
+ AddLine("ENDREP;");
+ } else if (const auto ast = std::get_if<ASTReturn>(&*node->GetInnerData())) {
+ const bool is_true = ExprIsTrue(ast->condition);
+ if (!is_true) {
+ AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition));
+ AddLine("IF NE.x;");
+ ResetTemporaries();
+ }
+ if (ast->kills) {
+ AddLine("KIL TR;");
+ } else {
+ Exit();
+ }
+ if (!is_true) {
+ AddLine("ENDIF;");
+ }
+ } else if (const auto ast = std::get_if<ASTBreak>(&*node->GetInnerData())) {
+ if (ExprIsTrue(ast->condition)) {
+ AddLine("BRK;");
+ } else {
+ AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition));
+ AddLine("BRK (NE.x);");
+ ResetTemporaries();
+ }
+ } else if (std::holds_alternative<ASTLabel>(*node->GetInnerData())) {
+ // Nothing to do
+ } else {
+ UNREACHABLE();
+ }
+}
+
+std::string ARBDecompiler::VisitExpression(const Expr& node) {
+ if (const auto expr = std::get_if<ExprAnd>(&*node)) {
+ std::string result = AllocTemporary();
+ AddLine("AND.U {}, {}, {};", result, VisitExpression(expr->operand1),
+ VisitExpression(expr->operand2));
+ return result;
+ }
+ if (const auto expr = std::get_if<ExprOr>(&*node)) {
+ std::string result = AllocTemporary();
+ AddLine("OR.U {}, {}, {};", result, VisitExpression(expr->operand1),
+ VisitExpression(expr->operand2));
+ return result;
+ }
+ if (const auto expr = std::get_if<ExprNot>(&*node)) {
+ std::string result = AllocTemporary();
+ AddLine("CMP.S {}, {}, 0, -1;", result, VisitExpression(expr->operand1));
+ return result;
+ }
+ if (const auto expr = std::get_if<ExprPredicate>(&*node)) {
+ return fmt::format("P{}.x", static_cast<u64>(expr->predicate));
+ }
+ if (const auto expr = std::get_if<ExprCondCode>(&*node)) {
+ return Visit(ir.GetConditionCode(expr->cc));
+ }
+ if (const auto expr = std::get_if<ExprVar>(&*node)) {
+ return fmt::format("F{}.x", expr->var_index);
+ }
+ if (const auto expr = std::get_if<ExprBoolean>(&*node)) {
+ return expr->value ? "0xffffffff" : "0";
+ }
+ if (const auto expr = std::get_if<ExprGprEqual>(&*node)) {
+ std::string result = AllocTemporary();
+ AddLine("SEQ.U {}, R{}.x, {};", result, expr->gpr, expr->value);
+ return result;
+ }
+ UNREACHABLE();
+ return "0";
+}
+
+void ARBDecompiler::VisitBlock(const NodeBlock& bb) {
+ for (const auto& node : bb) {
+ Visit(node);
+ }
+}
+
+std::string ARBDecompiler::Visit(const Node& node) {
+ if (const auto operation = std::get_if<OperationNode>(&*node)) {
+ if (const auto amend_index = operation->GetAmendIndex()) {
+ Visit(ir.GetAmendNode(*amend_index));
+ }
+ const std::size_t index = static_cast<std::size_t>(operation->GetCode());
+ if (index >= OPERATION_DECOMPILERS.size()) {
+ UNREACHABLE_MSG("Out of bounds operation: {}", index);
+ return {};
+ }
+ const auto decompiler = OPERATION_DECOMPILERS[index];
+ if (decompiler == nullptr) {
+ UNREACHABLE_MSG("Undefined operation: {}", index);
+ return {};
+ }
+ return (this->*decompiler)(*operation);
+ }
+
+ if (const auto gpr = std::get_if<GprNode>(&*node)) {
+ const u32 index = gpr->GetIndex();
+ if (index == Register::ZeroIndex) {
+ return "{0, 0, 0, 0}.x";
+ }
+ return fmt::format("R{}.x", index);
+ }
+
+ if (const auto cv = std::get_if<CustomVarNode>(&*node)) {
+ return fmt::format("CV{}.x", cv->GetIndex());
+ }
+
+ if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
+ std::string temporary = AllocTemporary();
+ AddLine("MOV.U {}, {};", temporary, immediate->GetValue());
+ return temporary;
+ }
+
+ if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
+ std::string temporary = AllocTemporary();
+ switch (const auto index = predicate->GetIndex(); index) {
+ case Tegra::Shader::Pred::UnusedIndex:
+ AddLine("MOV.S {}, -1;", temporary);
+ break;
+ case Tegra::Shader::Pred::NeverExecute:
+ AddLine("MOV.S {}, 0;", temporary);
+ break;
+ default:
+ AddLine("MOV.S {}, P{}.x;", temporary, static_cast<u64>(index));
+ break;
+ }
+ if (predicate->IsNegated()) {
+ AddLine("CMP.S {}, {}, 0, -1;", temporary, temporary);
+ }
+ return temporary;
+ }
+
+ if (const auto abuf = std::get_if<AbufNode>(&*node)) {
+ if (abuf->IsPhysicalBuffer()) {
+ UNIMPLEMENTED_MSG("Physical buffers are not implemented");
+ return "{0, 0, 0, 0}.x";
+ }
+
+ const Attribute::Index index = abuf->GetIndex();
+ const u32 element = abuf->GetElement();
+ const char swizzle = Swizzle(element);
+ switch (index) {
+ case Attribute::Index::Position: {
+ if (stage == ShaderType::Geometry) {
+ return fmt::format("{}_position[{}].{}", StageInputName(stage),
+ Visit(abuf->GetBuffer()), swizzle);
+ } else {
+ return fmt::format("{}.position.{}", StageInputName(stage), swizzle);
+ }
+ }
+ case Attribute::Index::TessCoordInstanceIDVertexID:
+ ASSERT(stage == ShaderType::Vertex);
+ switch (element) {
+ case 2:
+ return "vertex.instance";
+ case 3:
+ return "vertex.id";
+ }
+ UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
+ break;
+ case Attribute::Index::PointCoord:
+ switch (element) {
+ case 0:
+ return "fragment.pointcoord.x";
+ case 1:
+ return "fragment.pointcoord.y";
+ }
+ UNIMPLEMENTED();
+ break;
+ case Attribute::Index::FrontFacing: {
+ ASSERT(stage == ShaderType::Fragment);
+ ASSERT(element == 3);
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("SGT.S RC.x, fragment.facing, {{0, 0, 0, 0}};");
+ AddLine("MOV.U.CC RC.x, -RC;");
+ AddLine("MOV.S {}.x, 0;", temporary);
+ AddLine("MOV.S {}.x (NE.x), -1;", temporary);
+ return fmt::format("{}.x", temporary);
+ }
+ default:
+ if (IsGenericAttribute(index)) {
+ if (stage == ShaderType::Geometry) {
+ return fmt::format("in_attr{}[{}][0].{}", GetGenericAttributeIndex(index),
+ Visit(abuf->GetBuffer()), swizzle);
+ } else {
+ return fmt::format("{}.attrib[{}].{}", StageInputName(stage),
+ GetGenericAttributeIndex(index), swizzle);
+ }
+ }
+ UNIMPLEMENTED_MSG("Unimplemented input attribute={}", static_cast<int>(index));
+ break;
+ }
+ return "{0, 0, 0, 0}.x";
+ }
+
+ if (const auto cbuf = std::get_if<CbufNode>(&*node)) {
+ std::string offset_string;
+ const auto& offset = cbuf->GetOffset();
+ if (const auto imm = std::get_if<ImmediateNode>(&*offset)) {
+ offset_string = std::to_string(imm->GetValue());
+ } else {
+ offset_string = Visit(offset);
+ }
+ std::string temporary = AllocTemporary();
+ AddLine("LDC.F32 {}, cbuf{}[{}];", temporary, cbuf->GetIndex(), offset_string);
+ return temporary;
+ }
+
+ if (const auto gmem = std::get_if<GmemNode>(&*node)) {
+ std::string temporary = AllocTemporary();
+ AddLine("MOV {}, 0;", temporary);
+ AddLine("LOAD.U32 {} (NE.x), {};", temporary, GlobalMemoryPointer(*gmem));
+ return temporary;
+ }
+
+ if (const auto lmem = std::get_if<LmemNode>(&*node)) {
+ std::string temporary = Visit(lmem->GetAddress());
+ AddLine("SHR.U {}, {}, 2;", temporary, temporary);
+ AddLine("MOV.U {}, lmem[{}].x;", temporary, temporary);
+ return temporary;
+ }
+
+ if (const auto smem = std::get_if<SmemNode>(&*node)) {
+ std::string temporary = Visit(smem->GetAddress());
+ AddLine("LDS.U32 {}, shared_mem[{}];", temporary, temporary);
+ return temporary;
+ }
+
+ if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
+ const std::size_t index = static_cast<std::size_t>(internal_flag->GetFlag());
+ return fmt::format("{}.x", INTERNAL_FLAG_NAMES[index]);
+ }
+
+ if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
+ if (const auto amend_index = conditional->GetAmendIndex()) {
+ Visit(ir.GetAmendNode(*amend_index));
+ }
+ AddLine("MOVC.U RC.x, {};", Visit(conditional->GetCondition()));
+ AddLine("IF NE.x;");
+ VisitBlock(conditional->GetCode());
+ AddLine("ENDIF;");
+ return {};
+ }
+
+ if ([[maybe_unused]] const auto cmt = std::get_if<CommentNode>(&*node)) {
+ // Uncommenting this will generate invalid code. GLASM lacks comments.
+ // AddLine("// {}", cmt->GetText());
+ return {};
+ }
+
+ UNIMPLEMENTED();
+ return {};
+}
+
+std::tuple<std::string, std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ UNIMPLEMENTED_IF(meta.sampler.is_indexed);
+
+ const bool is_extended = meta.sampler.is_shadow && meta.sampler.is_array &&
+ meta.sampler.type == Tegra::Shader::TextureType::TextureCube;
+ const std::size_t count = operation.GetOperandsCount();
+ std::string temporary = AllocVectorTemporary();
+ std::size_t i = 0;
+ for (; i < count; ++i) {
+ AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), Visit(operation[i]));
+ }
+ if (meta.sampler.is_array) {
+ AddLine("I2F.S {}.{}, {};", temporary, Swizzle(i), Visit(meta.array));
+ ++i;
+ }
+ if (meta.sampler.is_shadow) {
+ std::string compare = Visit(meta.depth_compare);
+ if (is_extended) {
+ ASSERT(i == 4);
+ std::string extra_coord = AllocVectorTemporary();
+ AddLine("MOV.F {}.x, {};", extra_coord, compare);
+ return {fmt::format("{}, {}", temporary, extra_coord), extra_coord, 0};
+ }
+ AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), compare);
+ ++i;
+ }
+ return {temporary, temporary, i};
+}
+
+std::string ARBDecompiler::BuildAoffi(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ if (meta.aoffi.empty()) {
+ return {};
+ }
+ const std::string temporary = AllocVectorTemporary();
+ std::size_t i = 0;
+ for (auto& node : meta.aoffi) {
+ AddLine("MOV.S {}.{}, {};", temporary, Swizzle(i++), Visit(node));
+ }
+ return fmt::format(", offset({})", temporary);
+}
+
+std::string ARBDecompiler::GlobalMemoryPointer(const GmemNode& gmem) {
+ // Read a bindless SSBO, return its address and set CC accordingly
+ // address = c[binding].xy
+ // length = c[binding].z
+ const u32 binding = global_memory_names.at(gmem.GetDescriptor());
+
+ const std::string pointer = AllocLongVectorTemporary();
+ std::string temporary = AllocTemporary();
+
+ AddLine("PK64.U {}, c[{}];", pointer, binding);
+ AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem.GetRealAddress()),
+ Visit(gmem.GetBaseAddress()));
+ AddLine("CVT.U64.U32 {}.z, {};", pointer, temporary);
+ AddLine("ADD.U64 {}.x, {}.x, {}.z;", pointer, pointer, pointer);
+ // Compare offset to length and set CC
+ AddLine("SLT.U.CC RC.x, {}, c[{}].z;", temporary, binding);
+ return fmt::format("{}.x", pointer);
+}
+
+void ARBDecompiler::Exit() {
+ if (stage != ShaderType::Fragment) {
+ AddLine("RET;");
+ return;
+ }
+
+ const auto safe_get_register = [this](u32 reg) -> std::string {
+ // TODO(Rodrigo): Replace with contains once C++20 releases
+ const auto& used_registers = ir.GetRegisters();
+ if (used_registers.find(reg) != used_registers.end()) {
+ return fmt::format("R{}.x", reg);
+ }
+ return "{0, 0, 0, 0}.x";
+ };
+
+ const auto& header = ir.GetHeader();
+ u32 current_reg = 0;
+ for (u32 rt = 0; rt < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; ++rt) {
+ for (u32 component = 0; component < 4; ++component) {
+ if (!header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ continue;
+ }
+ AddLine("MOV.F result_color{}.{}, {};", rt, Swizzle(component),
+ safe_get_register(current_reg));
+ ++current_reg;
+ }
+ }
+ if (header.ps.omap.depth) {
+ AddLine("MOV.F result.depth.z, {};", safe_get_register(current_reg + 1));
+ }
+
+ AddLine("RET;");
+}
+
+std::string ARBDecompiler::Assign(Operation operation) {
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
+
+ std::string dest_name;
+ if (const auto gpr = std::get_if<GprNode>(&*dest)) {
+ if (gpr->GetIndex() == Register::ZeroIndex) {
+ // Writing to Register::ZeroIndex is a no op
+ return {};
+ }
+ dest_name = fmt::format("R{}.x", gpr->GetIndex());
+ } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
+ const u32 element = abuf->GetElement();
+ const char swizzle = Swizzle(element);
+ switch (const Attribute::Index index = abuf->GetIndex()) {
+ case Attribute::Index::Position:
+ dest_name = fmt::format("result.position.{}", swizzle);
+ break;
+ case Attribute::Index::LayerViewportPointSize:
+ switch (element) {
+ case 0:
+ UNIMPLEMENTED();
+ return {};
+ case 1:
+ case 2:
+ if (!device.HasNvViewportArray2()) {
+ LOG_ERROR(
+ Render_OpenGL,
+ "NV_viewport_array2 is missing. Maxwell gen 2 or better is required.");
+ return {};
+ }
+ dest_name = element == 1 ? "result.layer.x" : "result.viewport.x";
+ break;
+ case 3:
+ dest_name = "result.pointsize.x";
+ break;
+ }
+ break;
+ case Attribute::Index::ClipDistances0123:
+ dest_name = fmt::format("result.clip[{}].x", element);
+ break;
+ case Attribute::Index::ClipDistances4567:
+ dest_name = fmt::format("result.clip[{}].x", element + 4);
+ break;
+ default:
+ if (!IsGenericAttribute(index)) {
+ UNREACHABLE();
+ return {};
+ }
+ dest_name =
+ fmt::format("result.attrib[{}].{}", GetGenericAttributeIndex(index), swizzle);
+ break;
+ }
+ } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
+ const std::string address = Visit(lmem->GetAddress());
+ AddLine("SHR.U {}, {}, 2;", address, address);
+ dest_name = fmt::format("lmem[{}].x", address);
+ } else if (const auto smem = std::get_if<SmemNode>(&*dest)) {
+ AddLine("STS.U32 {}, shared_mem[{}];", Visit(src), Visit(smem->GetAddress()));
+ ResetTemporaries();
+ return {};
+ } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
+ AddLine("IF NE.x;");
+ AddLine("STORE.U32 {}, {};", Visit(src), GlobalMemoryPointer(*gmem));
+ AddLine("ENDIF;");
+ ResetTemporaries();
+ return {};
+ } else {
+ UNREACHABLE();
+ ResetTemporaries();
+ return {};
+ }
+
+ AddLine("MOV.U {}, {};", dest_name, Visit(src));
+ ResetTemporaries();
+ return {};
+}
+
+std::string ARBDecompiler::Select(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("CMP.S {}, {}, {}, {};", temporary, Visit(operation[0]), Visit(operation[1]),
+ Visit(operation[2]));
+ return temporary;
+}
+
+std::string ARBDecompiler::FClamp(Operation operation) {
+ // 1.0f in hex, replace with std::bit_cast on C++20
+ static constexpr u32 POSITIVE_ONE = 0x3f800000;
+
+ std::string temporary = AllocTemporary();
+ const Node& value = operation[0];
+ const Node& low = operation[1];
+ const Node& high = operation[2];
+ const auto* const imm_low = std::get_if<ImmediateNode>(&*low);
+ const auto* const imm_high = std::get_if<ImmediateNode>(&*high);
+ if (imm_low && imm_high && imm_low->GetValue() == 0 && imm_high->GetValue() == POSITIVE_ONE) {
+ AddLine("MOV.F32.SAT {}, {};", temporary, Visit(value));
+ } else {
+ AddLine("MIN.F {}, {}, {};", temporary, Visit(value), Visit(high));
+ AddLine("MAX.F {}, {}, {};", temporary, temporary, Visit(low));
+ }
+ return temporary;
+}
+
+std::string ARBDecompiler::FCastHalf0(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.x, {};", temporary, Visit(operation[0]));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::FCastHalf1(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.y, {};", temporary, Visit(operation[0]));
+ AddLine("MOV {}.x, {}.y;", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::FSqrt(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("RSQ.F32 {}, {};", temporary, Visit(operation[0]));
+ AddLine("RCP.F32 {}, {};", temporary, temporary);
+ return temporary;
+}
+
+std::string ARBDecompiler::FSwizzleAdd(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ if (!device.HasWarpIntrinsics()) {
+ LOG_ERROR(Render_OpenGL,
+ "NV_shader_thread_shuffle is missing. Kepler or better is required.");
+ AddLine("ADD.F {}.x, {}, {};", temporary, Visit(operation[0]), Visit(operation[1]));
+ return fmt::format("{}.x", temporary);
+ }
+
+ AddLine("AND.U {}.z, {}.threadid, 3;", temporary, StageInputName(stage));
+ AddLine("SHL.U {}.z, {}.z, 1;", temporary, temporary);
+ AddLine("SHR.U {}.z, {}, {}.z;", temporary, Visit(operation[2]), temporary);
+ AddLine("AND.U {}.z, {}.z, 3;", temporary, temporary);
+ AddLine("MUL.F32 {}.x, {}, FSWZA[{}.z];", temporary, Visit(operation[0]), temporary);
+ AddLine("MUL.F32 {}.y, {}, FSWZB[{}.z];", temporary, Visit(operation[1]), temporary);
+ AddLine("ADD.F32 {}.x, {}.x, {}.y;", temporary, temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HAdd2(Operation operation) {
+ const std::string tmp1 = AllocVectorTemporary();
+ const std::string tmp2 = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", tmp1, Visit(operation[0]));
+ AddLine("UP2H.F {}.xy, {};", tmp2, Visit(operation[1]));
+ AddLine("ADD.F16 {}, {}, {};", tmp1, tmp1, tmp2);
+ AddLine("PK2H.F {}.x, {};", tmp1, tmp1);
+ return fmt::format("{}.x", tmp1);
+}
+
+std::string ARBDecompiler::HMul2(Operation operation) {
+ const std::string tmp1 = AllocVectorTemporary();
+ const std::string tmp2 = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", tmp1, Visit(operation[0]));
+ AddLine("UP2H.F {}.xy, {};", tmp2, Visit(operation[1]));
+ AddLine("MUL.F16 {}, {}, {};", tmp1, tmp1, tmp2);
+ AddLine("PK2H.F {}.x, {};", tmp1, tmp1);
+ return fmt::format("{}.x", tmp1);
+}
+
+std::string ARBDecompiler::HFma2(Operation operation) {
+ const std::string tmp1 = AllocVectorTemporary();
+ const std::string tmp2 = AllocVectorTemporary();
+ const std::string tmp3 = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", tmp1, Visit(operation[0]));
+ AddLine("UP2H.F {}.xy, {};", tmp2, Visit(operation[1]));
+ AddLine("UP2H.F {}.xy, {};", tmp3, Visit(operation[2]));
+ AddLine("MAD.F16 {}, {}, {}, {};", tmp1, tmp1, tmp2, tmp3);
+ AddLine("PK2H.F {}.x, {};", tmp1, tmp1);
+ return fmt::format("{}.x", tmp1);
+}
+
+std::string ARBDecompiler::HAbsolute(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, Visit(operation[0]));
+ AddLine("PK2H.F {}.x, |{}|;", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HNegate(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, Visit(operation[0]));
+ AddLine("MOVC.S RC.x, {};", Visit(operation[1]));
+ AddLine("MOV.F {}.x (NE.x), -{}.x;", temporary, temporary);
+ AddLine("MOVC.S RC.x, {};", Visit(operation[2]));
+ AddLine("MOV.F {}.y (NE.x), -{}.y;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HClamp(Operation operation) {
+ const std::string tmp1 = AllocVectorTemporary();
+ const std::string tmp2 = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", tmp1, Visit(operation[0]));
+ AddLine("MOV.U {}.x, {};", tmp2, Visit(operation[1]));
+ AddLine("MOV.U {}.y, {}.x;", tmp2, tmp2);
+ AddLine("MAX.F {}, {}, {};", tmp1, tmp1, tmp2);
+ AddLine("MOV.U {}.x, {};", tmp2, Visit(operation[2]));
+ AddLine("MOV.U {}.y, {}.x;", tmp2, tmp2);
+ AddLine("MIN.F {}, {}, {};", tmp1, tmp1, tmp2);
+ AddLine("PK2H.F {}.x, {};", tmp1, tmp1);
+ return fmt::format("{}.x", tmp1);
+}
+
+std::string ARBDecompiler::HCastFloat(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("MOV.F {}.y, {{0, 0, 0, 0}};", temporary);
+ AddLine("MOV.F {}.x, {};", temporary, Visit(operation[0]));
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HUnpack(Operation operation) {
+ std::string operand = Visit(operation[0]);
+ switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
+ case Tegra::Shader::HalfType::H0_H1:
+ return operand;
+ case Tegra::Shader::HalfType::F32: {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("MOV.U {}.x, {};", temporary, operand);
+ AddLine("MOV.U {}.y, {}.x;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+ }
+ case Tegra::Shader::HalfType::H0_H0: {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, operand);
+ AddLine("MOV.U {}.y, {}.x;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+ }
+ case Tegra::Shader::HalfType::H1_H1: {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, operand);
+ AddLine("MOV.U {}.x, {}.y;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+ }
+ }
+ UNREACHABLE();
+ return "{0, 0, 0, 0}.x";
+}
+
+std::string ARBDecompiler::HMergeF32(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, Visit(operation[0]));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HMergeH0(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, Visit(operation[0]));
+ AddLine("UP2H.F {}.zw, {};", temporary, Visit(operation[1]));
+ AddLine("MOV.U {}.x, {}.z;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HMergeH1(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("UP2H.F {}.xy, {};", temporary, Visit(operation[0]));
+ AddLine("UP2H.F {}.zw, {};", temporary, Visit(operation[1]));
+ AddLine("MOV.U {}.y, {}.w;", temporary, temporary);
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::HPack2(Operation operation) {
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("MOV.U {}.x, {};", temporary, Visit(operation[0]));
+ AddLine("MOV.U {}.y, {};", temporary, Visit(operation[1]));
+ AddLine("PK2H.F {}.x, {};", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::LogicalAssign(Operation operation) {
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
+
+ std::string target;
+
+ if (const auto pred = std::get_if<PredicateNode>(&*dest)) {
+ ASSERT_MSG(!pred->IsNegated(), "Negating logical assignment");
+
+ const Tegra::Shader::Pred index = pred->GetIndex();
+ switch (index) {
+ case Tegra::Shader::Pred::NeverExecute:
+ case Tegra::Shader::Pred::UnusedIndex:
+ // Writing to these predicates is a no-op
+ return {};
+ }
+ target = fmt::format("P{}.x", static_cast<u64>(index));
+ } else if (const auto internal_flag = std::get_if<InternalFlagNode>(&*dest)) {
+ const std::size_t index = static_cast<std::size_t>(internal_flag->GetFlag());
+ target = fmt::format("{}.x", INTERNAL_FLAG_NAMES[index]);
+ } else {
+ UNREACHABLE();
+ ResetTemporaries();
+ return {};
+ }
+
+ AddLine("MOV.U {}, {};", target, Visit(src));
+ ResetTemporaries();
+ return {};
+}
+
+std::string ARBDecompiler::LogicalPick2(Operation operation) {
+ std::string temporary = AllocTemporary();
+ const u32 index = std::get<ImmediateNode>(*operation[1]).GetValue();
+ AddLine("MOV.U {}, {}.{};", temporary, Visit(operation[0]), Swizzle(index));
+ return temporary;
+}
+
+std::string ARBDecompiler::LogicalAnd2(Operation operation) {
+ std::string temporary = AllocTemporary();
+ const std::string op = Visit(operation[0]);
+ AddLine("AND.U {}, {}.x, {}.y;", temporary, op, op);
+ return temporary;
+}
+
+std::string ARBDecompiler::FloatOrdered(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("MOVC.F32 RC.x, {};", Visit(operation[0]));
+ AddLine("MOVC.F32 RC.y, {};", Visit(operation[1]));
+ AddLine("MOV.S {}, -1;", temporary);
+ AddLine("MOV.S {} (NAN.x), 0;", temporary);
+ AddLine("MOV.S {} (NAN.y), 0;", temporary);
+ return temporary;
+}
+
+std::string ARBDecompiler::FloatUnordered(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("MOVC.F32 RC.x, {};", Visit(operation[0]));
+ AddLine("MOVC.F32 RC.y, {};", Visit(operation[1]));
+ AddLine("MOV.S {}, 0;", temporary);
+ AddLine("MOV.S {} (NAN.x), -1;", temporary);
+ AddLine("MOV.S {} (NAN.y), -1;", temporary);
+ return temporary;
+}
+
+std::string ARBDecompiler::LogicalAddCarry(Operation operation) {
+ std::string temporary = AllocTemporary();
+ AddLine("ADDC.U RC, {}, {};", Visit(operation[0]), Visit(operation[1]));
+ AddLine("MOV.S {}, 0;", temporary);
+ AddLine("IF CF.x;");
+ AddLine("MOV.S {}, -1;", temporary);
+ AddLine("ENDIF;");
+ return temporary;
+}
+
+std::string ARBDecompiler::Texture(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
+
+ std::string_view opcode = "TEX";
+ std::string extra;
+ if (meta.bias) {
+ ASSERT(!meta.lod);
+ opcode = "TXB";
+
+ if (swizzle < 4) {
+ AddLine("MOV.F {}.w, {};", temporary, Visit(meta.bias));
+ } else {
+ const std::string bias = AllocTemporary();
+ AddLine("MOV.F {}, {};", bias, Visit(meta.bias));
+ extra = fmt::format(" {},", bias);
+ }
+ }
+ if (meta.lod) {
+ ASSERT(!meta.bias);
+ opcode = "TXL";
+
+ if (swizzle < 4) {
+ AddLine("MOV.F {}.w, {};", temporary, Visit(meta.lod));
+ } else {
+ const std::string lod = AllocTemporary();
+ AddLine("MOV.F {}, {};", lod, Visit(meta.lod));
+ extra = fmt::format(" {},", lod);
+ }
+ }
+
+ AddLine("{}.F {}, {},{} texture[{}], {}{};", opcode, temporary, coords, extra, sampler_id,
+ TextureType(meta), BuildAoffi(operation));
+ AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::TextureGather(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
+
+ std::string comp;
+ if (!meta.sampler.is_shadow) {
+ const auto& immediate = std::get<ImmediateNode>(*meta.component);
+ comp = fmt::format(".{}", Swizzle(immediate.GetValue()));
+ }
+
+ AddLine("TXG.F {}, {}, texture[{}]{}, {}{};", temporary, temporary, sampler_id, comp,
+ TextureType(meta), BuildAoffi(operation));
+ AddLine("MOV.U {}.x, {}.{};", temporary, coords, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::TextureQueryDimensions(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const std::string temporary = AllocVectorTemporary();
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+
+ ASSERT(!meta.sampler.is_array);
+
+ const std::string lod = operation.GetOperandsCount() > 0 ? Visit(operation[0]) : "0";
+ AddLine("TXQ {}, {}, texture[{}], {};", temporary, lod, sampler_id, TextureType(meta));
+ AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::TextureQueryLod(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const std::string temporary = AllocVectorTemporary();
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+
+ ASSERT(!meta.sampler.is_array);
+
+ const std::size_t count = operation.GetOperandsCount();
+ for (std::size_t i = 0; i < count; ++i) {
+ AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), Visit(operation[i]));
+ }
+ AddLine("LOD.F {}, {}, texture[{}], {};", temporary, temporary, sampler_id, TextureType(meta));
+ AddLine("MUL.F32 {}, {}, {{256, 256, 0, 0}};", temporary, temporary);
+ AddLine("TRUNC.S {}, {};", temporary, temporary);
+ AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::TexelFetch(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
+
+ if (!meta.sampler.is_buffer) {
+ ASSERT(swizzle < 4);
+ AddLine("MOV.F {}.w, {};", temporary, Visit(meta.lod));
+ }
+ AddLine("TXF.F {}, {}, texture[{}], {}{};", temporary, coords, sampler_id, TextureType(meta),
+ BuildAoffi(operation));
+ AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::TextureGradient(Operation operation) {
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
+ const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
+ const std::string ddx = AllocVectorTemporary();
+ const std::string ddy = AllocVectorTemporary();
+ const std::string coord = std::get<1>(BuildCoords(operation));
+
+ const std::size_t num_components = meta.derivates.size() / 2;
+ for (std::size_t index = 0; index < num_components; ++index) {
+ const char swizzle = Swizzle(index);
+ AddLine("MOV.F {}.{}, {};", ddx, swizzle, Visit(meta.derivates[index * 2]));
+ AddLine("MOV.F {}.{}, {};", ddy, swizzle, Visit(meta.derivates[index * 2 + 1]));
+ }
+
+ const std::string_view result = coord;
+ AddLine("TXD.F {}, {}, {}, {}, texture[{}], {}{};", result, coord, ddx, ddy, sampler_id,
+ TextureType(meta), BuildAoffi(operation));
+ AddLine("MOV.F {}.x, {}.{};", result, result, Swizzle(meta.element));
+ return fmt::format("{}.x", result);
+}
+
+std::string ARBDecompiler::ImageLoad(Operation operation) {
+ const auto& meta = std::get<MetaImage>(operation.GetMeta());
+ const u32 image_id = device.GetBaseBindings(stage).image + meta.image.index;
+ const std::size_t count = operation.GetOperandsCount();
+ const std::string_view type = ImageType(meta.image.type);
+
+ const std::string temporary = AllocVectorTemporary();
+ for (std::size_t i = 0; i < count; ++i) {
+ AddLine("MOV.S {}.{}, {};", temporary, Swizzle(i), Visit(operation[i]));
+ }
+ AddLine("LOADIM.F {}, {}, image[{}], {};", temporary, temporary, image_id, type);
+ AddLine("MOV.F {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::ImageStore(Operation operation) {
+ const auto& meta = std::get<MetaImage>(operation.GetMeta());
+ const u32 image_id = device.GetBaseBindings(stage).image + meta.image.index;
+ const std::size_t num_coords = operation.GetOperandsCount();
+ const std::size_t num_values = meta.values.size();
+ const std::string_view type = ImageType(meta.image.type);
+
+ const std::string coord = AllocVectorTemporary();
+ const std::string value = AllocVectorTemporary();
+ for (std::size_t i = 0; i < num_coords; ++i) {
+ AddLine("MOV.S {}.{}, {};", coord, Swizzle(i), Visit(operation[i]));
+ }
+ for (std::size_t i = 0; i < num_values; ++i) {
+ AddLine("MOV.F {}.{}, {};", value, Swizzle(i), Visit(meta.values[i]));
+ }
+ AddLine("STOREIM.F image[{}], {}, {}, {};", image_id, value, coord, type);
+ return {};
+}
+
+std::string ARBDecompiler::Branch(Operation operation) {
+ const auto target = std::get<ImmediateNode>(*operation[0]);
+ AddLine("MOV.U PC.x, {};", target.GetValue());
+ AddLine("CONT;");
+ return {};
+}
+
+std::string ARBDecompiler::BranchIndirect(Operation operation) {
+ AddLine("MOV.U PC.x, {};", Visit(operation[0]));
+ AddLine("CONT;");
+ return {};
+}
+
+std::string ARBDecompiler::PushFlowStack(Operation operation) {
+ const auto stack = std::get<MetaStackClass>(operation.GetMeta());
+ const u32 target = std::get<ImmediateNode>(*operation[0]).GetValue();
+ const std::string_view stack_name = StackName(stack);
+ AddLine("MOV.U {}[{}_TOP.x].x, {};", stack_name, stack_name, target);
+ AddLine("ADD.S {}_TOP.x, {}_TOP.x, 1;", stack_name, stack_name);
+ return {};
+}
+
+std::string ARBDecompiler::PopFlowStack(Operation operation) {
+ const auto stack = std::get<MetaStackClass>(operation.GetMeta());
+ const std::string_view stack_name = StackName(stack);
+ AddLine("SUB.S {}_TOP.x, {}_TOP.x, 1;", stack_name, stack_name);
+ AddLine("MOV.U PC.x, {}[{}_TOP.x].x;", stack_name, stack_name);
+ AddLine("CONT;");
+ return {};
+}
+
+std::string ARBDecompiler::Exit(Operation) {
+ Exit();
+ return {};
+}
+
+std::string ARBDecompiler::Discard(Operation) {
+ AddLine("KIL TR;");
+ return {};
+}
+
+std::string ARBDecompiler::EmitVertex(Operation) {
+ AddLine("EMIT;");
+ return {};
+}
+
+std::string ARBDecompiler::EndPrimitive(Operation) {
+ AddLine("ENDPRIM;");
+ return {};
+}
+
+std::string ARBDecompiler::InvocationId(Operation) {
+ return "primitive.invocation";
+}
+
+std::string ARBDecompiler::YNegate(Operation) {
+ LOG_WARNING(Render_OpenGL, "(STUBBED)");
+ std::string temporary = AllocTemporary();
+ AddLine("MOV.F {}, 1;", temporary);
+ return temporary;
+}
+
+std::string ARBDecompiler::ThreadId(Operation) {
+ return fmt::format("{}.threadid", StageInputName(stage));
+}
+
+std::string ARBDecompiler::ShuffleIndexed(Operation operation) {
+ if (!device.HasWarpIntrinsics()) {
+ LOG_ERROR(Render_OpenGL,
+ "NV_shader_thread_shuffle is missing. Kepler or better is required.");
+ return Visit(operation[0]);
+ }
+ const std::string temporary = AllocVectorTemporary();
+ AddLine("SHFIDX.U {}, {}, {}, {{31, 0, 0, 0}};", temporary, Visit(operation[0]),
+ Visit(operation[1]));
+ AddLine("MOV.U {}.x, {}.y;", temporary, temporary);
+ return fmt::format("{}.x", temporary);
+}
+
+std::string ARBDecompiler::Barrier(Operation) {
+ AddLine("BAR;");
+ return {};
+}
+
+std::string ARBDecompiler::MemoryBarrierGroup(Operation) {
+ AddLine("MEMBAR.CTA;");
+ return {};
+}
+
+std::string ARBDecompiler::MemoryBarrierGlobal(Operation) {
+ AddLine("MEMBAR;");
+ return {};
+}
+
+} // Anonymous namespace
+
+std::string DecompileAssemblyShader(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
+ const VideoCommon::Shader::Registry& registry,
+ Tegra::Engines::ShaderType stage, std::string_view identifier) {
+ return ARBDecompiler(device, ir, registry, stage, identifier).Code();
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_arb_decompiler.h b/src/video_core/renderer_opengl/gl_arb_decompiler.h
new file mode 100644
index 000000000..6afc87220
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_arb_decompiler.h
@@ -0,0 +1,29 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include "common/common_types.h"
+
+namespace Tegra::Engines {
+enum class ShaderType : u32;
+}
+
+namespace VideoCommon::Shader {
+class ShaderIR;
+class Registry;
+} // namespace VideoCommon::Shader
+
+namespace OpenGL {
+
+class Device;
+
+std::string DecompileAssemblyShader(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
+ const VideoCommon::Shader::Registry& registry,
+ Tegra::Engines::ShaderType stage, std::string_view identifier);
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 4eb37a96c..b1c4cd62f 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -8,6 +8,7 @@
#include "common/assert.h"
#include "common/microprofile.h"
+#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
@@ -21,22 +22,54 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
MICROPROFILE_DEFINE(OpenGL_Buffer_Download, "OpenGL", "Buffer Download", MP_RGB(192, 192, 128));
-CachedBufferBlock::CachedBufferBlock(VAddr cpu_addr, const std::size_t size)
+Buffer::Buffer(const Device& device, VAddr cpu_addr, std::size_t size)
: VideoCommon::BufferBlock{cpu_addr, size} {
gl_buffer.Create();
glNamedBufferData(gl_buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW);
+ if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) {
+ glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_WRITE);
+ glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address);
+ }
}
-CachedBufferBlock::~CachedBufferBlock() = default;
+Buffer::~Buffer() = default;
+
+void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
+ glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size),
+ data);
+}
-OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
- const Device& device, std::size_t stream_size)
- : GenericBufferCache{rasterizer, system, std::make_unique<OGLStreamBuffer>(stream_size, true)} {
+void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
+ MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
+ const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
+ const GLintptr gl_offset = static_cast<GLintptr>(offset);
+ if (read_buffer.handle == 0) {
+ read_buffer.Create();
+ glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr,
+ GL_STREAM_READ);
+ }
+ glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
+ glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size);
+ glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data);
+}
+
+void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
+ std::size_t size) {
+ glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset),
+ static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
+}
+
+OGLBufferCache::OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
+ const Device& device_, std::size_t stream_size)
+ : GenericBufferCache{rasterizer, gpu_memory, cpu_memory,
+ std::make_unique<OGLStreamBuffer>(device_, stream_size, true)},
+ device{device_} {
if (!device.HasFastBufferSubData()) {
return;
}
- static constexpr auto size = static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize);
+ static constexpr GLsizeiptr size = static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize);
glCreateBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs));
for (const GLuint cbuf : cbufs) {
glNamedBufferData(cbuf, size, nullptr, GL_STREAM_DRAW);
@@ -47,49 +80,21 @@ OGLBufferCache::~OGLBufferCache() {
glDeleteBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs));
}
-Buffer OGLBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
- return std::make_shared<CachedBufferBlock>(cpu_addr, size);
-}
-
-void OGLBufferCache::WriteBarrier() {
- glMemoryBarrier(GL_ALL_BARRIER_BITS);
+std::shared_ptr<Buffer> OGLBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
+ return std::make_shared<Buffer>(device, cpu_addr, size);
}
-const GLuint* OGLBufferCache::ToHandle(const Buffer& buffer) {
- return buffer->GetHandle();
-}
-
-const GLuint* OGLBufferCache::GetEmptyBuffer(std::size_t) {
- static const GLuint null_buffer = 0;
- return &null_buffer;
-}
-
-void OGLBufferCache::UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- const u8* data) {
- glNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset),
- static_cast<GLsizeiptr>(size), data);
-}
-
-void OGLBufferCache::DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- u8* data) {
- MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
- glGetNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset),
- static_cast<GLsizeiptr>(size), data);
-}
-
-void OGLBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
- std::size_t dst_offset, std::size_t size) {
- glCopyNamedBufferSubData(*src->GetHandle(), *dst->GetHandle(),
- static_cast<GLintptr>(src_offset), static_cast<GLintptr>(dst_offset),
- static_cast<GLsizeiptr>(size));
+OGLBufferCache::BufferInfo OGLBufferCache::GetEmptyBuffer(std::size_t) {
+ return {0, 0, 0};
}
OGLBufferCache::BufferInfo OGLBufferCache::ConstBufferUpload(const void* raw_pointer,
std::size_t size) {
DEBUG_ASSERT(cbuf_cursor < std::size(cbufs));
- const GLuint& cbuf = cbufs[cbuf_cursor++];
+ const GLuint cbuf = cbufs[cbuf_cursor++];
+
glNamedBufferSubData(cbuf, 0, static_cast<GLsizeiptr>(size), raw_pointer);
- return {&cbuf, 0};
+ return {cbuf, 0, 0};
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index d94a11252..f75b32e31 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -10,7 +10,6 @@
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
@@ -24,59 +23,59 @@ class Device;
class OGLStreamBuffer;
class RasterizerOpenGL;
-class CachedBufferBlock;
+class Buffer : public VideoCommon::BufferBlock {
+public:
+ explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size);
+ ~Buffer();
-using Buffer = std::shared_ptr<CachedBufferBlock>;
-using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>;
+ void Upload(std::size_t offset, std::size_t size, const u8* data);
-class CachedBufferBlock : public VideoCommon::BufferBlock {
-public:
- explicit CachedBufferBlock(VAddr cpu_addr, const std::size_t size);
- ~CachedBufferBlock();
+ void Download(std::size_t offset, std::size_t size, u8* data);
+
+ void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
+ std::size_t size);
- const GLuint* GetHandle() const {
- return &gl_buffer.handle;
+ GLuint Handle() const noexcept {
+ return gl_buffer.handle;
+ }
+
+ u64 Address() const noexcept {
+ return gpu_address;
}
private:
- OGLBuffer gl_buffer{};
+ OGLBuffer gl_buffer;
+ OGLBuffer read_buffer;
+ u64 gpu_address = 0;
};
+using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>;
class OGLBufferCache final : public GenericBufferCache {
public:
- explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
+ explicit OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
const Device& device, std::size_t stream_size);
~OGLBufferCache();
- const GLuint* GetEmptyBuffer(std::size_t) override;
+ BufferInfo GetEmptyBuffer(std::size_t) override;
void Acquire() noexcept {
cbuf_cursor = 0;
}
protected:
- Buffer CreateBlock(VAddr cpu_addr, std::size_t size) override;
-
- void WriteBarrier() override;
-
- const GLuint* ToHandle(const Buffer& buffer) override;
-
- void UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- const u8* data) override;
-
- void DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- u8* data) override;
-
- void CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
- std::size_t dst_offset, std::size_t size) override;
+ std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override;
BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) override;
private:
+ static constexpr std::size_t NUM_CBUFS = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers *
+ Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram;
+
+ const Device& device;
+
std::size_t cbuf_cursor = 0;
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers *
- Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram>
- cbufs;
+ std::array<GLuint, NUM_CBUFS> cbufs{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index c286502ba..a94e4f72e 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -6,6 +6,7 @@
#include <array>
#include <cstddef>
#include <cstring>
+#include <limits>
#include <optional>
#include <vector>
@@ -13,6 +14,7 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
+#include "core/settings.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -25,24 +27,27 @@ constexpr u32 ReservedUniformBlocks = 1;
constexpr u32 NumStages = 5;
-constexpr std::array LimitUBOs = {GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,
- GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS,
- GL_MAX_GEOMETRY_UNIFORM_BLOCKS, GL_MAX_FRAGMENT_UNIFORM_BLOCKS};
+constexpr std::array LimitUBOs = {
+ GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,
+ GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_GEOMETRY_UNIFORM_BLOCKS,
+ GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMPUTE_UNIFORM_BLOCKS};
constexpr std::array LimitSSBOs = {
- GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,
+ GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS,
- GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS};
+ GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS};
-constexpr std::array LimitSamplers = {
- GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,
- GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,
- GL_MAX_TEXTURE_IMAGE_UNITS};
+constexpr std::array LimitSamplers = {GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,
+ GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TEXTURE_IMAGE_UNITS,
+ GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS};
-constexpr std::array LimitImages = {GL_MAX_VERTEX_IMAGE_UNIFORMS,
- GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS,
- GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS,
- GL_MAX_GEOMETRY_IMAGE_UNIFORMS, GL_MAX_FRAGMENT_IMAGE_UNIFORMS};
+constexpr std::array LimitImages = {
+ GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS,
+ GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, GL_MAX_GEOMETRY_IMAGE_UNIFORMS,
+ GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMPUTE_IMAGE_UNIFORMS};
template <typename T>
T GetInteger(GLenum pname) {
@@ -84,10 +89,17 @@ u32 Extract(u32& base, u32& num, u32 amount, std::optional<GLenum> limit = {}) {
return std::exchange(base, base + amount);
}
+std::array<u32, Tegra::Engines::MaxShaderTypes> BuildMaxUniformBuffers() noexcept {
+ std::array<u32, Tegra::Engines::MaxShaderTypes> max;
+ std::transform(LimitUBOs.begin(), LimitUBOs.end(), max.begin(),
+ [](GLenum pname) { return GetInteger<u32>(pname); });
+ return max;
+}
+
std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindings() noexcept {
std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> bindings;
- static std::array<std::size_t, 5> stage_swizzle = {0, 1, 2, 3, 4};
+ static constexpr std::array<std::size_t, 5> stage_swizzle{0, 1, 2, 3, 4};
const u32 total_ubos = GetInteger<u32>(GL_MAX_UNIFORM_BUFFER_BINDINGS);
const u32 total_ssbos = GetInteger<u32>(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
const u32 total_samplers = GetInteger<u32>(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
@@ -111,16 +123,24 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin
u32 num_images = GetInteger<u32>(GL_MAX_IMAGE_UNITS);
u32 base_images = 0;
- // Reserve more image bindings on fragment and vertex stages.
+ // GL_MAX_IMAGE_UNITS is guaranteed by the spec to have a minimum value of 8.
+ // Due to the limitation of GL_MAX_IMAGE_UNITS, reserve at least 4 image bindings on the
+ // fragment stage, and at least 1 for the rest of the stages.
+ // So far games are observed to use 1 image binding on vertex and 4 on fragment stages.
+
+ // Reserve at least 4 image bindings on the fragment stage.
bindings[4].image =
- Extract(base_images, num_images, num_images / NumStages + 2, LimitImages[4]);
- bindings[0].image =
- Extract(base_images, num_images, num_images / NumStages + 1, LimitImages[0]);
+ Extract(base_images, num_images, std::max(4U, num_images / NumStages), LimitImages[4]);
+
+ // This is guaranteed to be at least 1.
+ const u32 total_extracted_images = num_images / (NumStages - 1);
// Reserve the other image bindings.
- const u32 total_extracted_images = num_images / (NumStages - 2);
- for (std::size_t i = 2; i < NumStages; ++i) {
+ for (std::size_t i = 0; i < NumStages; ++i) {
const std::size_t stage = stage_swizzle[i];
+ if (stage == 4) {
+ continue;
+ }
bindings[stage].image =
Extract(base_images, num_images, total_extracted_images, LimitImages[stage]);
}
@@ -132,6 +152,7 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin
}
bool IsASTCSupported() {
+ static constexpr std::array targets = {GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY};
static constexpr std::array formats = {
GL_COMPRESSED_RGBA_ASTC_4x4_KHR, GL_COMPRESSED_RGBA_ASTC_5x4_KHR,
GL_COMPRESSED_RGBA_ASTC_5x5_KHR, GL_COMPRESSED_RGBA_ASTC_6x5_KHR,
@@ -148,59 +169,94 @@ bool IsASTCSupported() {
GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR,
GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR,
};
- return std::find_if_not(formats.begin(), formats.end(), [](GLenum format) {
- GLint supported;
- glGetInternalformativ(GL_TEXTURE_2D, format, GL_INTERNALFORMAT_SUPPORTED, 1,
- &supported);
- return supported == GL_TRUE;
- }) == formats.end();
+ static constexpr std::array required_support = {
+ GL_VERTEX_TEXTURE, GL_TESS_CONTROL_TEXTURE, GL_TESS_EVALUATION_TEXTURE,
+ GL_GEOMETRY_TEXTURE, GL_FRAGMENT_TEXTURE, GL_COMPUTE_TEXTURE,
+ };
+
+ for (const GLenum target : targets) {
+ for (const GLenum format : formats) {
+ for (const GLenum support : required_support) {
+ GLint value;
+ glGetInternalformativ(target, format, support, 1, &value);
+ if (value != GL_FULL_SUPPORT) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
}
} // Anonymous namespace
-Device::Device() : base_bindings{BuildBaseBindings()} {
+Device::Device()
+ : max_uniform_buffers{BuildMaxUniformBuffers()}, base_bindings{BuildBaseBindings()} {
const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
- const auto renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
+ const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
const std::vector extensions = GetExtensions();
const bool is_nvidia = vendor == "NVIDIA Corporation";
const bool is_amd = vendor == "ATI Technologies Inc.";
- const bool is_intel = vendor == "Intel";
- const bool is_intel_proprietary = is_intel && std::strstr(renderer, "Mesa") == nullptr;
+
+ bool disable_fast_buffer_sub_data = false;
+ if (is_nvidia && version == "4.6.0 NVIDIA 443.24") {
+ LOG_WARNING(
+ Render_OpenGL,
+ "Beta driver 443.24 is known to have issues. There might be performance issues.");
+ disable_fast_buffer_sub_data = true;
+ }
uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
+ max_compute_shared_memory_size = GetInteger<u32>(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE);
has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
GLAD_GL_NV_shader_thread_shuffle;
has_shader_ballot = GLAD_GL_ARB_shader_ballot;
has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
+ has_texture_shadow_lod = HasExtension(extensions, "GL_EXT_texture_shadow_lod");
has_astc = IsASTCSupported();
has_variable_aoffi = TestVariableAoffi();
has_component_indexing_bug = is_amd;
has_precise_bug = TestPreciseBug();
- has_broken_compute = is_intel_proprietary;
- has_fast_buffer_sub_data = is_nvidia;
+ has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2;
+ has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory;
+
+ // At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive
+ // uniform buffers as "push constants"
+ has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data;
+
+ use_assembly_shaders = Settings::values.use_assembly_shaders.GetValue() &&
+ GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 &&
+ GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2;
+
+ use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
+
+ if (Settings::values.use_assembly_shaders.GetValue() && !use_assembly_shaders) {
+ LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported");
+ }
}
Device::Device(std::nullptr_t) {
- uniform_buffer_alignment = 0;
+ max_uniform_buffers.fill(std::numeric_limits<u32>::max());
+ uniform_buffer_alignment = 4;
+ shader_storage_alignment = 4;
max_vertex_attributes = 16;
max_varyings = 15;
+ max_compute_shared_memory_size = 0x10000;
has_warp_intrinsics = true;
has_shader_ballot = true;
has_vertex_viewport_layer = true;
has_image_load_formatted = true;
+ has_texture_shadow_lod = true;
has_variable_aoffi = true;
- has_component_indexing_bug = false;
- has_broken_compute = false;
- has_precise_bug = false;
}
bool Device::TestVariableAoffi() {
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index a55050cb5..8a4b6b9fc 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -24,6 +24,10 @@ public:
explicit Device();
explicit Device(std::nullptr_t);
+ u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept {
+ return max_uniform_buffers[static_cast<std::size_t>(shader_type)];
+ }
+
const BaseBindings& GetBaseBindings(std::size_t stage_index) const noexcept {
return base_bindings[stage_index];
}
@@ -48,6 +52,10 @@ public:
return max_varyings;
}
+ u32 GetMaxComputeSharedMemorySize() const {
+ return max_compute_shared_memory_size;
+ }
+
bool HasWarpIntrinsics() const {
return has_warp_intrinsics;
}
@@ -64,6 +72,14 @@ public:
return has_image_load_formatted;
}
+ bool HasTextureShadowLod() const {
+ return has_texture_shadow_lod;
+ }
+
+ bool HasVertexBufferUnifiedMemory() const {
+ return has_vertex_buffer_unified_memory;
+ }
+
bool HasASTC() const {
return has_astc;
}
@@ -80,33 +96,47 @@ public:
return has_precise_bug;
}
- bool HasBrokenCompute() const {
- return has_broken_compute;
- }
-
bool HasFastBufferSubData() const {
return has_fast_buffer_sub_data;
}
+ bool HasNvViewportArray2() const {
+ return has_nv_viewport_array2;
+ }
+
+ bool UseAssemblyShaders() const {
+ return use_assembly_shaders;
+ }
+
+ bool UseAsynchronousShaders() const {
+ return use_asynchronous_shaders;
+ }
+
private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
- std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings;
+ std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};
+ std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};
std::size_t uniform_buffer_alignment{};
std::size_t shader_storage_alignment{};
u32 max_vertex_attributes{};
u32 max_varyings{};
+ u32 max_compute_shared_memory_size{};
bool has_warp_intrinsics{};
bool has_shader_ballot{};
bool has_vertex_viewport_layer{};
bool has_image_load_formatted{};
+ bool has_texture_shadow_lod{};
+ bool has_vertex_buffer_unified_memory{};
bool has_astc{};
bool has_variable_aoffi{};
bool has_component_indexing_bug{};
bool has_precise_bug{};
- bool has_broken_compute{};
bool has_fast_buffer_sub_data{};
+ bool has_nv_viewport_array2{};
+ bool use_assembly_shaders{};
+ bool use_asynchronous_shaders{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp
new file mode 100644
index 000000000..b532fdcc2
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp
@@ -0,0 +1,73 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+
+#include <glad/glad.h>
+
+#include "video_core/renderer_opengl/gl_buffer_cache.h"
+#include "video_core/renderer_opengl/gl_fence_manager.h"
+
+namespace OpenGL {
+
+GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {}
+
+GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed)
+ : FenceBase(address, payload, is_stubbed) {}
+
+GLInnerFence::~GLInnerFence() = default;
+
+void GLInnerFence::Queue() {
+ if (is_stubbed) {
+ return;
+ }
+ ASSERT(sync_object.handle == 0);
+ sync_object.Create();
+}
+
+bool GLInnerFence::IsSignaled() const {
+ if (is_stubbed) {
+ return true;
+ }
+ ASSERT(sync_object.handle != 0);
+ GLsizei length;
+ GLint sync_status;
+ glGetSynciv(sync_object.handle, GL_SYNC_STATUS, sizeof(GLint), &length, &sync_status);
+ return sync_status == GL_SIGNALED;
+}
+
+void GLInnerFence::Wait() {
+ if (is_stubbed) {
+ return;
+ }
+ ASSERT(sync_object.handle != 0);
+ glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED);
+}
+
+FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCacheOpenGL& texture_cache,
+ OGLBufferCache& buffer_cache, QueryCache& query_cache)
+ : GenericFenceManager{rasterizer, gpu, texture_cache, buffer_cache, query_cache} {}
+
+Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) {
+ return std::make_shared<GLInnerFence>(value, is_stubbed);
+}
+
+Fence FenceManagerOpenGL::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) {
+ return std::make_shared<GLInnerFence>(addr, value, is_stubbed);
+}
+
+void FenceManagerOpenGL::QueueFence(Fence& fence) {
+ fence->Queue();
+}
+
+bool FenceManagerOpenGL::IsFenceSignaled(Fence& fence) const {
+ return fence->IsSignaled();
+}
+
+void FenceManagerOpenGL::WaitFence(Fence& fence) {
+ fence->Wait();
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
new file mode 100644
index 000000000..da1dcdace
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -0,0 +1,52 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+#include "video_core/fence_manager.h"
+#include "video_core/renderer_opengl/gl_buffer_cache.h"
+#include "video_core/renderer_opengl/gl_query_cache.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_texture_cache.h"
+
+namespace OpenGL {
+
+class GLInnerFence : public VideoCommon::FenceBase {
+public:
+ GLInnerFence(u32 payload, bool is_stubbed);
+ GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed);
+ ~GLInnerFence();
+
+ void Queue();
+
+ bool IsSignaled() const;
+
+ void Wait();
+
+private:
+ OGLSync sync_object;
+};
+
+using Fence = std::shared_ptr<GLInnerFence>;
+using GenericFenceManager =
+ VideoCommon::FenceManager<Fence, TextureCacheOpenGL, OGLBufferCache, QueryCache>;
+
+class FenceManagerOpenGL final : public GenericFenceManager {
+public:
+ explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
+ QueryCache& query_cache);
+
+protected:
+ Fence CreateFence(u32 value, bool is_stubbed) override;
+ Fence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) override;
+ void QueueFence(Fence& fence) override;
+ bool IsFenceSignaled(Fence& fence) const override;
+ void WaitFence(Fence& fence) override;
+};
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index f12e9f55f..1a3d9720e 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -30,12 +30,11 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
-QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer)
- : VideoCommon::QueryCacheBase<
- QueryCache, CachedQuery, CounterStream, HostCounter,
- std::vector<OGLQuery>>{system,
- static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)},
- gl_rasterizer{gl_rasterizer} {}
+QueryCache::QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory)
+ : VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter>(
+ rasterizer, maxwell3d, gpu_memory),
+ gl_rasterizer{rasterizer} {}
QueryCache::~QueryCache() = default;
@@ -90,13 +89,15 @@ u64 HostCounter::BlockingQuery() const {
CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr)
: VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {}
+CachedQuery::~CachedQuery() = default;
+
CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept
: VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}
CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
- VideoCommon::CachedQueryBase<HostCounter>::operator=(std::move(rhs));
cache = rhs.cache;
type = rhs.type;
+ CachedQueryBase<HostCounter>::operator=(std::move(rhs));
return *this;
}
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index d8e7052a1..82cac51ee 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,10 +26,11 @@ class RasterizerOpenGL;
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
-class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
- HostCounter, std::vector<OGLQuery>> {
+class QueryCache final
+ : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer);
+ explicit QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
@@ -40,6 +41,7 @@ public:
private:
RasterizerOpenGL& gl_rasterizer;
+ std::array<std::vector<OGLQuery>, VideoCore::NumQueryTypes> query_pools;
};
class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
@@ -62,10 +64,12 @@ class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> {
public:
explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr,
u8* host_ptr);
- CachedQuery(CachedQuery&& rhs) noexcept;
- CachedQuery(const CachedQuery&) = delete;
+ ~CachedQuery() override;
+ CachedQuery(CachedQuery&& rhs) noexcept;
CachedQuery& operator=(CachedQuery&& rhs) noexcept;
+
+ CachedQuery(const CachedQuery&) = delete;
CachedQuery& operator=(const CachedQuery&) = delete;
void Flush() override;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index f4598fbf7..cfddbde5d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -30,6 +30,7 @@
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+#include "video_core/shader_cache.h"
namespace OpenGL {
@@ -54,19 +55,36 @@ MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255
namespace {
-constexpr std::size_t NumSupportedVertexAttributes = 16;
+constexpr std::size_t NUM_CONST_BUFFERS_PER_STAGE = 18;
+constexpr std::size_t NUM_CONST_BUFFERS_BYTES_PER_STAGE =
+ NUM_CONST_BUFFERS_PER_STAGE * Maxwell::MaxConstBufferSize;
+constexpr std::size_t TOTAL_CONST_BUFFER_BYTES =
+ NUM_CONST_BUFFERS_BYTES_PER_STAGE * Maxwell::MaxShaderStage;
+
+constexpr std::size_t NUM_SUPPORTED_VERTEX_ATTRIBUTES = 16;
+constexpr std::size_t NUM_SUPPORTED_VERTEX_BINDINGS = 16;
template <typename Engine, typename Entry>
Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
ShaderType shader_type, std::size_t index = 0) {
- if (entry.IsBindless()) {
- const Tegra::Texture::TextureHandle tex_handle =
- engine.AccessConstBuffer32(shader_type, entry.GetBuffer(), entry.GetOffset());
- return engine.GetTextureInfo(tex_handle);
+ if constexpr (std::is_same_v<Entry, SamplerEntry>) {
+ if (entry.is_separated) {
+ const u32 buffer_1 = entry.buffer;
+ const u32 buffer_2 = entry.secondary_buffer;
+ const u32 offset_1 = entry.offset;
+ const u32 offset_2 = entry.secondary_offset;
+ const u32 handle_1 = engine.AccessConstBuffer32(shader_type, buffer_1, offset_1);
+ const u32 handle_2 = engine.AccessConstBuffer32(shader_type, buffer_2, offset_2);
+ return engine.GetTextureInfo(handle_1 | handle_2);
+ }
+ }
+ if (entry.is_bindless) {
+ const u32 handle = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset);
+ return engine.GetTextureInfo(handle);
}
+
const auto& gpu_profile = engine.AccessGuestDriverProfile();
- const u32 offset =
- entry.GetOffset() + static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
+ const u32 offset = entry.offset + static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
return engine.GetStageTexture(shader_type, offset);
} else {
@@ -89,23 +107,84 @@ std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer,
return buffer.size;
}
+/// Translates hardware transform feedback indices
+/// @param location Hardware location
+/// @return Pair of ARB_transform_feedback3 token stream first and third arguments
+/// @note Read https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_transform_feedback3.txt
+std::pair<GLint, GLint> TransformFeedbackEnum(u8 location) {
+ const u8 index = location / 4;
+ if (index >= 8 && index <= 39) {
+ return {GL_GENERIC_ATTRIB_NV, index - 8};
+ }
+ if (index >= 48 && index <= 55) {
+ return {GL_TEXTURE_COORD_NV, index - 48};
+ }
+ switch (index) {
+ case 7:
+ return {GL_POSITION, 0};
+ case 40:
+ return {GL_PRIMARY_COLOR_NV, 0};
+ case 41:
+ return {GL_SECONDARY_COLOR_NV, 0};
+ case 42:
+ return {GL_BACK_PRIMARY_COLOR_NV, 0};
+ case 43:
+ return {GL_BACK_SECONDARY_COLOR_NV, 0};
+ }
+ UNIMPLEMENTED_MSG("index={}", static_cast<int>(index));
+ return {GL_POSITION, 0};
+}
+
void oglEnable(GLenum cap, bool state) {
(state ? glEnable : glDisable)(cap);
}
+void UpdateBindlessSSBOs(GLenum target, const BindlessSSBO* ssbos, size_t num_ssbos) {
+ if (num_ssbos == 0) {
+ return;
+ }
+ glProgramLocalParametersI4uivNV(target, 0, static_cast<GLsizei>(num_ssbos),
+ reinterpret_cast<const GLuint*>(ssbos));
+}
+
} // Anonymous namespace
-RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- ScreenInfo& info, GLShader::ProgramManager& program_manager,
- StateTracker& state_tracker)
- : RasterizerAccelerated{system.Memory()}, texture_cache{system, *this, device, state_tracker},
- shader_cache{*this, system, emu_window, device}, query_cache{system, *this}, system{system},
- screen_info{info}, program_manager{program_manager}, state_tracker{state_tracker},
- buffer_cache{*this, system, device, STREAM_BUFFER_SIZE} {
+RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
+ Core::Memory::Memory& cpu_memory, const Device& device_,
+ ScreenInfo& screen_info_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_)
+ : RasterizerAccelerated{cpu_memory}, gpu(gpu_), maxwell3d(gpu.Maxwell3D()),
+ kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_),
+ screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_),
+ texture_cache(*this, maxwell3d, gpu_memory, device, state_tracker),
+ shader_cache(*this, emu_window, gpu, maxwell3d, kepler_compute, gpu_memory, device),
+ query_cache(*this, maxwell3d, gpu_memory),
+ buffer_cache(*this, gpu_memory, cpu_memory, device, STREAM_BUFFER_SIZE),
+ fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
+ async_shaders(emu_window) {
CheckExtensions();
+
+ unified_uniform_buffer.Create();
+ glNamedBufferStorage(unified_uniform_buffer.handle, TOTAL_CONST_BUFFER_BYTES, nullptr, 0);
+
+ if (device.UseAssemblyShaders()) {
+ glCreateBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data());
+ for (const GLuint cbuf : staging_cbufs) {
+ glNamedBufferStorage(cbuf, static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize),
+ nullptr, 0);
+ }
+ }
+
+ if (device.UseAsynchronousShaders()) {
+ async_shaders.AllocateWorkers();
+ }
}
-RasterizerOpenGL::~RasterizerOpenGL() {}
+RasterizerOpenGL::~RasterizerOpenGL() {
+ if (device.UseAssemblyShaders()) {
+ glDeleteBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data());
+ }
+}
void RasterizerOpenGL::CheckExtensions() {
if (!GLAD_GL_ARB_texture_filter_anisotropic && !GLAD_GL_EXT_texture_filter_anisotropic) {
@@ -116,8 +195,7 @@ void RasterizerOpenGL::CheckExtensions() {
}
void RasterizerOpenGL::SetupVertexFormat() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexFormats]) {
return;
}
@@ -131,13 +209,13 @@ void RasterizerOpenGL::SetupVertexFormat() {
// avoid OpenGL errors.
// TODO(Subv): Analyze the shader to identify which attributes are actually used and don't
// assume every shader uses them all.
- for (std::size_t index = 0; index < NumSupportedVertexAttributes; ++index) {
+ for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
if (!flags[Dirty::VertexFormat0 + index]) {
continue;
}
flags[Dirty::VertexFormat0 + index] = false;
- const auto attrib = gpu.regs.vertex_attrib_format[index];
+ const auto attrib = maxwell3d.regs.vertex_attrib_format[index];
const auto gl_index = static_cast<GLuint>(index);
// Disable constant attributes.
@@ -150,9 +228,10 @@ void RasterizerOpenGL::SetupVertexFormat() {
if (attrib.type == Maxwell::VertexAttribute::Type::SignedInt ||
attrib.type == Maxwell::VertexAttribute::Type::UnsignedInt) {
glVertexAttribIFormat(gl_index, attrib.ComponentCount(),
- MaxwellToGL::VertexType(attrib), attrib.offset);
+ MaxwellToGL::VertexFormat(attrib), attrib.offset);
} else {
- glVertexAttribFormat(gl_index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
+ glVertexAttribFormat(gl_index, attrib.ComponentCount(),
+ MaxwellToGL::VertexFormat(attrib),
attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset);
}
glVertexAttribBinding(gl_index, attrib.buffer);
@@ -160,8 +239,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
}
void RasterizerOpenGL::SetupVertexBuffer() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexBuffers]) {
return;
}
@@ -169,9 +247,11 @@ void RasterizerOpenGL::SetupVertexBuffer() {
MICROPROFILE_SCOPE(OpenGL_VB);
+ const bool use_unified_memory = device.HasVertexBufferUnifiedMemory();
+
// Upload all guest vertex arrays sequentially to our buffer
- const auto& regs = gpu.regs;
- for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& regs = maxwell3d.regs;
+ for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) {
if (!flags[Dirty::VertexBuffer0 + index]) {
continue;
}
@@ -184,27 +264,37 @@ void RasterizerOpenGL::SetupVertexBuffer() {
const GPUVAddr start = vertex_array.StartAddress();
const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
-
- ASSERT(end > start);
- const u64 size = end - start + 1;
- const auto [vertex_buffer, vertex_buffer_offset] = buffer_cache.UploadMemory(start, size);
-
- // Bind the vertex array to the buffer at the current offset.
- vertex_array_pushbuffer.SetVertexBuffer(static_cast<GLuint>(index), vertex_buffer,
- vertex_buffer_offset, vertex_array.stride);
+ ASSERT(end >= start);
+
+ const GLuint gl_index = static_cast<GLuint>(index);
+ const u64 size = end - start;
+ if (size == 0) {
+ glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride);
+ if (use_unified_memory) {
+ glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index, 0, 0);
+ }
+ continue;
+ }
+ const auto info = buffer_cache.UploadMemory(start, size);
+ if (use_unified_memory) {
+ glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride);
+ glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index,
+ info.address + info.offset, size);
+ } else {
+ glBindVertexBuffer(gl_index, info.handle, info.offset, vertex_array.stride);
+ }
}
}
void RasterizerOpenGL::SetupVertexInstances() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexInstances]) {
return;
}
flags[Dirty::VertexInstances] = false;
- const auto& regs = gpu.regs;
- for (std::size_t index = 0; index < NumSupportedVertexAttributes; ++index) {
+ const auto& regs = maxwell3d.regs;
+ for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
if (!flags[Dirty::VertexInstance0 + index]) {
continue;
}
@@ -219,24 +309,23 @@ void RasterizerOpenGL::SetupVertexInstances() {
GLintptr RasterizerOpenGL::SetupIndexBuffer() {
MICROPROFILE_SCOPE(OpenGL_Index);
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
const std::size_t size = CalculateIndexBufferSize();
- const auto [buffer, offset] = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
- vertex_array_pushbuffer.SetIndexBuffer(buffer);
- return offset;
+ const auto info = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.handle);
+ return info.offset;
}
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = system.GPU().Maxwell3D();
u32 clip_distances = 0;
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
- const auto& shader_config = gpu.regs.shader_config[index];
+ const auto& shader_config = maxwell3d.regs.shader_config[index];
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
switch (program) {
case Maxwell::ShaderProgram::Geometry:
program_manager.UseGeometryShader(0);
@@ -251,23 +340,15 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
// Currently this stages are not supported in the OpenGL backend.
- // Todo(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL
- if (program == Maxwell::ShaderProgram::TesselationControl) {
- continue;
- } else if (program == Maxwell::ShaderProgram::TesselationEval) {
+ // TODO(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL
+ if (program == Maxwell::ShaderProgram::TesselationControl ||
+ program == Maxwell::ShaderProgram::TesselationEval) {
continue;
}
- Shader shader{shader_cache.GetStageProgram(program)};
+ Shader* const shader = shader_cache.GetStageProgram(program, async_shaders);
- // Stage indices are 0 - 5
- const std::size_t stage = index == 0 ? 0 : index - 1;
- SetupDrawConstBuffers(stage, shader);
- SetupDrawGlobalMemory(stage, shader);
- SetupDrawTextures(stage, shader);
- SetupDrawImages(stage, shader);
-
- const GLuint program_handle = shader->GetHandle();
+ const GLuint program_handle = shader->IsBuilt() ? shader->GetHandle() : 0;
switch (program) {
case Maxwell::ShaderProgram::VertexA:
case Maxwell::ShaderProgram::VertexB:
@@ -284,6 +365,13 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
shader_config.enable.Value(), shader_config.offset);
}
+ // Stage indices are 0 - 5
+ const std::size_t stage = index == 0 ? 0 : index - 1;
+ SetupDrawConstBuffers(stage, shader);
+ SetupDrawGlobalMemory(stage, shader);
+ SetupDrawTextures(stage, shader);
+ SetupDrawImages(stage, shader);
+
// Workaround for Intel drivers.
// When a clip distance is enabled but not set in the shader it crops parts of the screen
// (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the
@@ -298,11 +386,11 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
SyncClipEnabled(clip_distances);
- gpu.dirty.flags[Dirty::Shaders] = false;
+ maxwell3d.dirty.flags[Dirty::Shaders] = false;
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
std::size_t size = 0;
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
@@ -312,49 +400,42 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
const GPUVAddr start = regs.vertex_array[index].StartAddress();
const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
- ASSERT(end > start);
- size += end - start + 1;
+ size += end - start;
+ ASSERT(end >= start);
}
return size;
}
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
+ return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
+ static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
}
-void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading,
+void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
- shader_cache.LoadDiskCache(stop_loading, callback);
-}
-
-void RasterizerOpenGL::SetupDirtyFlags() {
- state_tracker.Initialize();
+ shader_cache.LoadDiskCache(title_id, stop_loading, callback);
}
void RasterizerOpenGL::ConfigureFramebuffers() {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
- auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
+ if (!maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
return;
}
- gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
+ maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
texture_cache.GuardRenderTargets(true);
- View depth_surface = texture_cache.GetDepthBufferSurface();
+ View depth_surface = texture_cache.GetDepthBufferSurface(true);
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
// Bind the framebuffer surfaces
FramebufferCacheKey key;
const auto colors_count = static_cast<std::size_t>(regs.rt_control.count);
for (std::size_t index = 0; index < colors_count; ++index) {
- View color_surface{texture_cache.GetColorBufferSurface(index)};
+ View color_surface{texture_cache.GetColorBufferSurface(index, true)};
if (!color_surface) {
continue;
}
@@ -378,40 +459,62 @@ void RasterizerOpenGL::ConfigureFramebuffers() {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key));
}
-void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color_fb, bool using_depth_fb,
- bool using_stencil_fb) {
- auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
+void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) {
+ const auto& regs = maxwell3d.regs;
texture_cache.GuardRenderTargets(true);
View color_surface;
- if (using_color_fb) {
+
+ if (using_color) {
+ // Determine if we have to preserve the contents.
+ // First we have to make sure all clear masks are enabled.
+ bool preserve_contents = !regs.clear_buffers.R || !regs.clear_buffers.G ||
+ !regs.clear_buffers.B || !regs.clear_buffers.A;
const std::size_t index = regs.clear_buffers.RT;
- color_surface = texture_cache.GetColorBufferSurface(index);
+ if (regs.clear_flags.scissor) {
+ // Then we have to confirm scissor testing clears the whole image.
+ const auto& scissor = regs.scissor_test[0];
+ preserve_contents |= scissor.min_x > 0;
+ preserve_contents |= scissor.min_y > 0;
+ preserve_contents |= scissor.max_x < regs.rt[index].width;
+ preserve_contents |= scissor.max_y < regs.rt[index].height;
+ }
+
+ color_surface = texture_cache.GetColorBufferSurface(index, preserve_contents);
texture_cache.MarkColorBufferInUse(index);
}
+
View depth_surface;
- if (using_depth_fb || using_stencil_fb) {
- depth_surface = texture_cache.GetDepthBufferSurface();
+ if (using_depth_stencil) {
+ bool preserve_contents = false;
+ if (regs.clear_flags.scissor) {
+ // For depth stencil clears we only have to confirm scissor test covers the whole image.
+ const auto& scissor = regs.scissor_test[0];
+ preserve_contents |= scissor.min_x > 0;
+ preserve_contents |= scissor.min_y > 0;
+ preserve_contents |= scissor.max_x < regs.zeta_width;
+ preserve_contents |= scissor.max_y < regs.zeta_height;
+ }
+
+ depth_surface = texture_cache.GetDepthBufferSurface(preserve_contents);
texture_cache.MarkDepthBufferInUse();
}
texture_cache.GuardRenderTargets(false);
FramebufferCacheKey key;
- key.colors[0] = color_surface;
- key.zeta = depth_surface;
+ key.colors[0] = std::move(color_surface);
+ key.zeta = std::move(depth_surface);
state_tracker.NotifyFramebuffer();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key));
}
void RasterizerOpenGL::Clear() {
- const auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.ShouldExecute()) {
+ if (!maxwell3d.ShouldExecute()) {
return;
}
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -419,8 +522,7 @@ void RasterizerOpenGL::Clear() {
if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A) {
use_color = true;
- }
- if (use_color) {
+
state_tracker.NotifyColorMask0();
glColorMaski(0, regs.clear_buffers.R != 0, regs.clear_buffers.G != 0,
regs.clear_buffers.B != 0, regs.clear_buffers.A != 0);
@@ -458,7 +560,7 @@ void RasterizerOpenGL::Clear() {
UNIMPLEMENTED_IF(regs.clear_flags.viewport);
- ConfigureClearFramebuffer(use_color, use_depth, use_stencil);
+ ConfigureClearFramebuffer(use_color, use_depth || use_stencil);
if (use_color) {
glClearBufferfv(GL_COLOR, 0, regs.clear_color);
@@ -477,7 +579,6 @@ void RasterizerOpenGL::Clear() {
void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
- auto& gpu = system.GPU().Maxwell3D();
query_cache.UpdateCounters();
@@ -502,6 +603,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
SyncFramebufferSRGB();
buffer_cache.Acquire();
+ current_cbuf = 0;
std::size_t buffer_size = CalculateVertexArraysSize();
@@ -511,20 +613,28 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
}
// Uniform space for the 5 shader stages
- buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
- (sizeof(GLShader::MaxwellUniformData) + device.GetUniformBufferAlignment()) *
- Maxwell::MaxShaderStage;
+ buffer_size =
+ Common::AlignUp<std::size_t>(buffer_size, 4) +
+ (sizeof(MaxwellUniformData) + device.GetUniformBufferAlignment()) * Maxwell::MaxShaderStage;
// Add space for at least 18 constant buffers
buffer_size += Maxwell::MaxConstBuffers *
(Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
// Prepare the vertex array.
- buffer_cache.Map(buffer_size);
+ const bool invalidated = buffer_cache.Map(buffer_size);
+
+ if (invalidated) {
+ // When the stream buffer has been invalidated, we have to consider vertex buffers as dirty
+ auto& dirty = maxwell3d.dirty.flags;
+ dirty[Dirty::VertexBuffers] = true;
+ for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) {
+ dirty[index] = true;
+ }
+ }
// Prepare vertex array format.
SetupVertexFormat();
- vertex_array_pushbuffer.Setup();
// Upload vertex and index data.
SetupVertexBuffer();
@@ -534,21 +644,19 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
index_buffer_offset = SetupIndexBuffer();
}
- // Prepare packed bindings.
- bind_ubo_pushbuffer.Setup();
- bind_ssbo_pushbuffer.Setup();
-
// Setup emulation uniform buffer.
- GLShader::MaxwellUniformData ubo;
- ubo.SetFromRegs(gpu);
- const auto [buffer, offset] =
- buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
- bind_ubo_pushbuffer.Push(EmulationUniformBlockBinding, buffer, offset,
- static_cast<GLsizeiptr>(sizeof(ubo)));
+ if (!device.UseAssemblyShaders()) {
+ MaxwellUniformData ubo;
+ ubo.SetFromRegs(maxwell3d);
+ const auto info =
+ buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
+ glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset,
+ static_cast<GLsizeiptr>(sizeof(ubo)));
+ }
// Setup shaders and their used resources.
texture_cache.GuardSamplers(true);
- const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
+ const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology);
SetupShaders(primitive_mode);
texture_cache.GuardSamplers(false);
@@ -557,11 +665,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
// Signal the buffer cache that we are not going to upload more things.
buffer_cache.Unmap();
- // Now that we are no longer uploading data, we can safely bind the buffers to OpenGL.
- vertex_array_pushbuffer.Bind();
- bind_ubo_pushbuffer.Bind();
- bind_ssbo_pushbuffer.Bind();
-
program_manager.BindGraphicsPipeline();
if (texture_cache.TextureBarrier()) {
@@ -570,14 +673,14 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
BeginTransformFeedback(primitive_mode);
- const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
+ const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance);
const GLsizei num_instances =
- static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1);
+ static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1);
if (is_indexed) {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count);
const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset);
- const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format);
+ const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format);
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
glDrawElements(primitive_mode, num_vertices, format, offset);
} else if (num_instances == 1 && base_instance == 0) {
@@ -596,8 +699,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
base_instance);
}
} else {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count);
if (num_instances == 1 && base_instance == 0) {
glDrawArrays(primitive_mode, base_vertex, num_vertices);
} else if (base_instance == 0) {
@@ -611,37 +714,32 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
EndTransformFeedback();
++num_queued_commands;
+
+ gpu.TickWork();
}
void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
- if (device.HasBrokenCompute()) {
- return;
- }
-
buffer_cache.Acquire();
+ current_cbuf = 0;
auto kernel = shader_cache.GetComputeKernel(code_addr);
+ program_manager.BindCompute(kernel->GetHandle());
+
SetupComputeTextures(kernel);
SetupComputeImages(kernel);
- program_manager.BindComputeShader(kernel->GetHandle());
const std::size_t buffer_size =
Tegra::Engines::KeplerCompute::NumConstBuffers *
(Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
buffer_cache.Map(buffer_size);
- bind_ubo_pushbuffer.Setup();
- bind_ssbo_pushbuffer.Setup();
-
SetupComputeConstBuffers(kernel);
SetupComputeGlobalMemory(kernel);
buffer_cache.Unmap();
- bind_ubo_pushbuffer.Bind();
- bind_ssbo_pushbuffer.Bind();
-
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
+ program_manager.BindCompute(kernel->GetHandle());
glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z);
++num_queued_commands;
}
@@ -667,6 +765,13 @@ void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
query_cache.FlushRegion(addr, size);
}
+bool RasterizerOpenGL::MustFlushRegion(VAddr addr, u64 size) {
+ if (!Settings::IsGPULevelHigh()) {
+ return buffer_cache.MustFlushRegion(addr, size);
+ }
+ return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size);
+}
+
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
if (addr == 0 || size == 0) {
@@ -678,13 +783,64 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
query_cache.InvalidateRegion(addr, size);
}
+void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ if (addr == 0 || size == 0) {
+ return;
+ }
+ texture_cache.OnCPUWrite(addr, size);
+ shader_cache.OnCPUWrite(addr, size);
+ buffer_cache.OnCPUWrite(addr, size);
+}
+
+void RasterizerOpenGL::SyncGuestHost() {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ texture_cache.SyncGuestHost();
+ buffer_cache.SyncGuestHost();
+ shader_cache.SyncGuestHost();
+}
+
+void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
+ if (!gpu.IsAsync()) {
+ gpu_memory.Write<u32>(addr, value);
+ return;
+ }
+ fence_manager.SignalSemaphore(addr, value);
+}
+
+void RasterizerOpenGL::SignalSyncPoint(u32 value) {
+ if (!gpu.IsAsync()) {
+ gpu.IncrementSyncPoint(value);
+ return;
+ }
+ fence_manager.SignalSyncPoint(value);
+}
+
+void RasterizerOpenGL::ReleaseFences() {
+ if (!gpu.IsAsync()) {
+ return;
+ }
+ fence_manager.WaitPendingFences();
+}
+
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
- if (Settings::values.use_accurate_gpu_emulation) {
+ if (Settings::IsGPULevelExtreme()) {
FlushRegion(addr, size);
}
InvalidateRegion(addr, size);
}
+void RasterizerOpenGL::WaitForIdle() {
+ // Place a barrier on everything that is not framebuffer related.
+ // This is related to another flag that is not currently implemented.
+ glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT |
+ GL_UNIFORM_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT |
+ GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT |
+ GL_PIXEL_BUFFER_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT |
+ GL_BUFFER_UPDATE_BARRIER_BIT | GL_TRANSFORM_FEEDBACK_BARRIER_BIT |
+ GL_SHADER_STORAGE_BARRIER_BIT | GL_QUERY_BUFFER_BARRIER_BIT);
+}
+
void RasterizerOpenGL::FlushCommands() {
// Only flush when we have commands queued to OpenGL.
if (num_queued_commands == 0) {
@@ -739,40 +895,72 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
return true;
}
-void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, const Shader& shader) {
+void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* shader) {
+ static constexpr std::array PARAMETER_LUT{
+ GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
+ GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV,
+ GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV,
+ };
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& stages = system.GPU().Maxwell3D().state.shader_stages;
+ const auto& stages = maxwell3d.state.shader_stages;
const auto& shader_stage = stages[stage_index];
-
- u32 binding = device.GetBaseBindings(stage_index).uniform_buffer;
- for (const auto& entry : shader->GetEntries().const_buffers) {
- const auto& buffer = shader_stage.const_buffers[entry.GetIndex()];
- SetupConstBuffer(binding++, buffer, entry);
+ const auto& entries = shader->GetEntries();
+ const bool use_unified = entries.use_unified_uniforms;
+ const std::size_t base_unified_offset = stage_index * NUM_CONST_BUFFERS_BYTES_PER_STAGE;
+
+ const auto base_bindings = device.GetBaseBindings(stage_index);
+ u32 binding = device.UseAssemblyShaders() ? 0 : base_bindings.uniform_buffer;
+ for (const auto& entry : entries.const_buffers) {
+ const u32 index = entry.GetIndex();
+ const auto& buffer = shader_stage.const_buffers[index];
+ SetupConstBuffer(PARAMETER_LUT[stage_index], binding, buffer, entry, use_unified,
+ base_unified_offset + index * Maxwell::MaxConstBufferSize);
+ ++binding;
+ }
+ if (use_unified) {
+ const u32 index = static_cast<u32>(base_bindings.shader_storage_buffer +
+ entries.global_memory_entries.size());
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle,
+ base_unified_offset, NUM_CONST_BUFFERS_BYTES_PER_STAGE);
}
}
-void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) {
+void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) {
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
+ const auto& entries = kernel->GetEntries();
+ const bool use_unified = entries.use_unified_uniforms;
u32 binding = 0;
- for (const auto& entry : kernel->GetEntries().const_buffers) {
+ for (const auto& entry : entries.const_buffers) {
const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
Tegra::Engines::ConstBufferInfo buffer;
buffer.address = config.Address();
buffer.size = config.size;
buffer.enabled = mask[entry.GetIndex()];
- SetupConstBuffer(binding++, buffer, entry);
+ SetupConstBuffer(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding, buffer, entry,
+ use_unified, entry.GetIndex() * Maxwell::MaxConstBufferSize);
+ ++binding;
+ }
+ if (use_unified) {
+ const GLuint index = static_cast<GLuint>(entries.global_memory_entries.size());
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle, 0,
+ NUM_CONST_BUFFERS_BYTES_PER_STAGE);
}
}
-void RasterizerOpenGL::SetupConstBuffer(u32 binding, const Tegra::Engines::ConstBufferInfo& buffer,
- const ConstBufferEntry& entry) {
+void RasterizerOpenGL::SetupConstBuffer(GLenum stage, u32 binding,
+ const Tegra::Engines::ConstBufferInfo& buffer,
+ const ConstBufferEntry& entry, bool use_unified,
+ std::size_t unified_offset) {
if (!buffer.enabled) {
// Set values to zero to unbind buffers
- bind_ubo_pushbuffer.Push(binding, buffer_cache.GetEmptyBuffer(sizeof(float)), 0,
- sizeof(float));
+ if (device.UseAssemblyShaders()) {
+ glBindBufferRangeNV(stage, entry.GetIndex(), 0, 0, 0);
+ } else {
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding, 0, 0, sizeof(float));
+ }
return;
}
@@ -780,68 +968,112 @@ void RasterizerOpenGL::SetupConstBuffer(u32 binding, const Tegra::Engines::Const
// UBO alignment requirements.
const std::size_t size = Common::AlignUp(GetConstBufferSize(buffer, entry), sizeof(GLvec4));
- const auto alignment = device.GetUniformBufferAlignment();
- const auto [cbuf, offset] = buffer_cache.UploadMemory(buffer.address, size, alignment, false,
- device.HasFastBufferSubData());
- bind_ubo_pushbuffer.Push(binding, cbuf, offset, size);
+ const bool fast_upload = !use_unified && device.HasFastBufferSubData();
+
+ const std::size_t alignment = use_unified ? 4 : device.GetUniformBufferAlignment();
+ const GPUVAddr gpu_addr = buffer.address;
+ auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, false, fast_upload);
+
+ if (device.UseAssemblyShaders()) {
+ UNIMPLEMENTED_IF(use_unified);
+ if (info.offset != 0) {
+ const GLuint staging_cbuf = staging_cbufs[current_cbuf++];
+ glCopyNamedBufferSubData(info.handle, staging_cbuf, info.offset, 0, size);
+ info.handle = staging_cbuf;
+ info.offset = 0;
+ }
+ glBindBufferRangeNV(stage, binding, info.handle, info.offset, size);
+ return;
+ }
+
+ if (use_unified) {
+ glCopyNamedBufferSubData(info.handle, unified_uniform_buffer.handle, info.offset,
+ unified_offset, size);
+ } else {
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding, info.handle, info.offset, size);
+ }
}
-void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, const Shader& shader) {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]};
+void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader) {
+ static constexpr std::array TARGET_LUT = {
+ GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV,
+ GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
+ };
+
+ const auto& cbufs{maxwell3d.state.shader_stages[stage_index]};
+ const auto& entries{shader->GetEntries().global_memory_entries};
- u32 binding = device.GetBaseBindings(stage_index).shader_storage_buffer;
- for (const auto& entry : shader->GetEntries().global_memory_entries) {
- const auto addr{cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset()};
- const auto gpu_addr{memory_manager.Read<u64>(addr)};
- const auto size{memory_manager.Read<u32>(addr + 8)};
- SetupGlobalMemory(binding++, entry, gpu_addr, size);
+ std::array<BindlessSSBO, 32> ssbos;
+ ASSERT(entries.size() < ssbos.size());
+
+ const bool assembly_shaders = device.UseAssemblyShaders();
+ u32 binding = assembly_shaders ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer;
+ for (const auto& entry : entries) {
+ const GPUVAddr addr{cbufs.const_buffers[entry.cbuf_index].address + entry.cbuf_offset};
+ const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
+ const u32 size{gpu_memory.Read<u32>(addr + 8)};
+ SetupGlobalMemory(binding, entry, gpu_addr, size, &ssbos[binding]);
+ ++binding;
+ }
+ if (assembly_shaders) {
+ UpdateBindlessSSBOs(TARGET_LUT[stage_index], ssbos.data(), entries.size());
}
}
-void RasterizerOpenGL::SetupComputeGlobalMemory(const Shader& kernel) {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto cbufs{gpu.KeplerCompute().launch_description.const_buffer_config};
+void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
+ const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
+ const auto& entries{kernel->GetEntries().global_memory_entries};
+
+ std::array<BindlessSSBO, 32> ssbos;
+ ASSERT(entries.size() < ssbos.size());
u32 binding = 0;
- for (const auto& entry : kernel->GetEntries().global_memory_entries) {
- const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
- const auto gpu_addr{memory_manager.Read<u64>(addr)};
- const auto size{memory_manager.Read<u32>(addr + 8)};
- SetupGlobalMemory(binding++, entry, gpu_addr, size);
+ for (const auto& entry : entries) {
+ const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset};
+ const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
+ const u32 size{gpu_memory.Read<u32>(addr + 8)};
+ SetupGlobalMemory(binding, entry, gpu_addr, size, &ssbos[binding]);
+ ++binding;
+ }
+ if (device.UseAssemblyShaders()) {
+ UpdateBindlessSSBOs(GL_COMPUTE_PROGRAM_NV, ssbos.data(), ssbos.size());
}
}
void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry,
- GPUVAddr gpu_addr, std::size_t size) {
- const auto alignment{device.GetShaderStorageBufferAlignment()};
- const auto [ssbo, buffer_offset] =
- buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.IsWritten());
- bind_ssbo_pushbuffer.Push(binding, ssbo, buffer_offset, static_cast<GLsizeiptr>(size));
+ GPUVAddr gpu_addr, size_t size, BindlessSSBO* ssbo) {
+ const size_t alignment{device.GetShaderStorageBufferAlignment()};
+ const auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.is_written);
+ if (device.UseAssemblyShaders()) {
+ *ssbo = BindlessSSBO{
+ .address = static_cast<GLuint64EXT>(info.address + info.offset),
+ .length = static_cast<GLsizei>(size),
+ .padding = 0,
+ };
+ } else {
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, info.handle, info.offset,
+ static_cast<GLsizeiptr>(size));
+ }
}
-void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, const Shader& shader) {
+void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& maxwell3d = system.GPU().Maxwell3D();
u32 binding = device.GetBaseBindings(stage_index).sampler;
for (const auto& entry : shader->GetEntries().samplers) {
const auto shader_type = static_cast<ShaderType>(stage_index);
- for (std::size_t i = 0; i < entry.Size(); ++i) {
+ for (std::size_t i = 0; i < entry.size; ++i) {
const auto texture = GetTextureInfo(maxwell3d, entry, shader_type, i);
SetupTexture(binding++, texture, entry);
}
}
}
-void RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) {
+void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& compute = system.GPU().KeplerCompute();
u32 binding = 0;
for (const auto& entry : kernel->GetEntries().samplers) {
- for (std::size_t i = 0; i < entry.Size(); ++i) {
- const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i);
+ for (std::size_t i = 0; i < entry.size; ++i) {
+ const auto texture = GetTextureInfo(kepler_compute, entry, ShaderType::Compute, i);
SetupTexture(binding++, texture, entry);
}
}
@@ -856,33 +1088,27 @@ void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextu
glBindTextureUnit(binding, 0);
return;
}
- glBindTextureUnit(binding, view->GetTexture());
-
- if (view->GetSurfaceParams().IsBuffer()) {
- return;
+ const GLuint handle = view->GetTexture(texture.tic.x_source, texture.tic.y_source,
+ texture.tic.z_source, texture.tic.w_source);
+ glBindTextureUnit(binding, handle);
+ if (!view->GetSurfaceParams().IsBuffer()) {
+ glBindSampler(binding, sampler_cache.GetSampler(texture.tsc));
}
- // Apply swizzle to textures that are not buffers.
- view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
- texture.tic.w_source);
-
- glBindSampler(binding, sampler_cache.GetSampler(texture.tsc));
}
-void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, const Shader& shader) {
- const auto& maxwell3d = system.GPU().Maxwell3D();
+void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) {
u32 binding = device.GetBaseBindings(stage_index).image;
for (const auto& entry : shader->GetEntries().images) {
- const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index);
+ const auto shader_type = static_cast<ShaderType>(stage_index);
const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic;
SetupImage(binding++, tic, entry);
}
}
-void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
- const auto& compute = system.GPU().KeplerCompute();
+void RasterizerOpenGL::SetupComputeImages(Shader* shader) {
u32 binding = 0;
for (const auto& entry : shader->GetEntries().images) {
- const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic;
+ const auto tic = GetTextureInfo(kepler_compute, entry, ShaderType::Compute).tic;
SetupImage(binding++, tic, entry);
}
}
@@ -894,27 +1120,43 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t
glBindImageTexture(binding, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
return;
}
- if (!tic.IsBuffer()) {
- view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
- }
- if (entry.IsWritten()) {
+ if (entry.is_written) {
view->MarkAsModified(texture_cache.Tick());
}
- glBindImageTexture(binding, view->GetTexture(), 0, GL_TRUE, 0, GL_READ_WRITE,
- view->GetFormat());
+ const GLuint handle = view->GetTexture(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
+ glBindImageTexture(binding, handle, 0, GL_TRUE, 0, GL_READ_WRITE, view->GetFormat());
}
void RasterizerOpenGL::SyncViewport() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
const bool dirty_viewport = flags[Dirty::Viewports];
+ const bool dirty_clip_control = flags[Dirty::ClipControl];
+
+ if (dirty_clip_control || flags[Dirty::FrontFace]) {
+ flags[Dirty::FrontFace] = false;
+
+ GLenum mode = MaxwellToGL::FrontFace(regs.front_face);
+ if (regs.screen_y_control.triangle_rast_flip != 0 &&
+ regs.viewport_transform[0].scale_y < 0.0f) {
+ switch (mode) {
+ case GL_CW:
+ mode = GL_CCW;
+ break;
+ case GL_CCW:
+ mode = GL_CW;
+ break;
+ }
+ }
+ glFrontFace(mode);
+ }
+
if (dirty_viewport || flags[Dirty::ClipControl]) {
flags[Dirty::ClipControl] = false;
bool flip_y = false;
- if (regs.viewport_transform[0].scale_y < 0.0) {
+ if (regs.viewport_transform[0].scale_y < 0.0f) {
flip_y = !flip_y;
}
if (regs.screen_y_control.y_negate != 0) {
@@ -946,34 +1188,36 @@ void RasterizerOpenGL::SyncViewport() {
const GLdouble near_depth = src.translate_z - src.scale_z * reduce_z;
const GLdouble far_depth = src.translate_z + src.scale_z;
glDepthRangeIndexed(static_cast<GLuint>(i), near_depth, far_depth);
+
+ if (!GLAD_GL_NV_viewport_swizzle) {
+ continue;
+ }
+ glViewportSwizzleNV(static_cast<GLuint>(i), MaxwellToGL::ViewportSwizzle(src.swizzle.x),
+ MaxwellToGL::ViewportSwizzle(src.swizzle.y),
+ MaxwellToGL::ViewportSwizzle(src.swizzle.z),
+ MaxwellToGL::ViewportSwizzle(src.swizzle.w));
}
}
}
void RasterizerOpenGL::SyncDepthClamp() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::DepthClampEnabled]) {
return;
}
flags[Dirty::DepthClampEnabled] = false;
- const auto& state = gpu.regs.view_volume_clip_control;
- UNIMPLEMENTED_IF_MSG(state.depth_clamp_far != state.depth_clamp_near,
- "Unimplemented depth clamp separation!");
-
- oglEnable(GL_DEPTH_CLAMP, state.depth_clamp_far || state.depth_clamp_near);
+ oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0);
}
void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ClipDistances] && !flags[Dirty::Shaders]) {
return;
}
flags[Dirty::ClipDistances] = false;
- clip_mask &= gpu.regs.clip_distance_enabled;
+ clip_mask &= maxwell3d.regs.clip_distance_enabled;
if (clip_mask == last_clip_distance_mask) {
return;
}
@@ -989,9 +1233,8 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::CullTest]) {
flags[Dirty::CullTest] = false;
@@ -1003,34 +1246,27 @@ void RasterizerOpenGL::SyncCullMode() {
glDisable(GL_CULL_FACE);
}
}
-
- if (flags[Dirty::FrontFace]) {
- flags[Dirty::FrontFace] = false;
- glFrontFace(MaxwellToGL::FrontFace(regs.front_face));
- }
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PrimitiveRestart]) {
return;
}
flags[Dirty::PrimitiveRestart] = false;
- if (gpu.regs.primitive_restart.enabled) {
+ if (maxwell3d.regs.primitive_restart.enabled) {
glEnable(GL_PRIMITIVE_RESTART);
- glPrimitiveRestartIndex(gpu.regs.primitive_restart.index);
+ glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index);
} else {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void RasterizerOpenGL::SyncDepthTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
- const auto& regs = gpu.regs;
if (flags[Dirty::DepthMask]) {
flags[Dirty::DepthMask] = false;
glDepthMask(regs.depth_write_enabled ? GL_TRUE : GL_FALSE);
@@ -1048,14 +1284,13 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::StencilTest]) {
return;
}
flags[Dirty::StencilTest] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_STENCIL_TEST, regs.stencil_enable);
glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func),
@@ -1080,25 +1315,24 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncRasterizeEnable() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::RasterizeEnable]) {
return;
}
flags[Dirty::RasterizeEnable] = false;
- oglEnable(GL_RASTERIZER_DISCARD, gpu.regs.rasterize_enable == 0);
+ oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0);
}
void RasterizerOpenGL::SyncPolygonModes() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonModes]) {
return;
}
flags[Dirty::PolygonModes] = false;
- if (gpu.regs.fill_rectangle) {
+ const auto& regs = maxwell3d.regs;
+ if (regs.fill_rectangle) {
if (!GLAD_GL_NV_fill_rectangle) {
LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported");
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
@@ -1111,27 +1345,26 @@ void RasterizerOpenGL::SyncPolygonModes() {
return;
}
- if (gpu.regs.polygon_mode_front == gpu.regs.polygon_mode_back) {
+ if (regs.polygon_mode_front == regs.polygon_mode_back) {
flags[Dirty::PolygonModeFront] = false;
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
return;
}
if (flags[Dirty::PolygonModeFront]) {
flags[Dirty::PolygonModeFront] = false;
- glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
}
if (flags[Dirty::PolygonModeBack]) {
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_back));
+ glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_back));
}
}
void RasterizerOpenGL::SyncColorMask() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ColorMasks]) {
return;
}
@@ -1140,7 +1373,7 @@ void RasterizerOpenGL::SyncColorMask() {
const bool force = flags[Dirty::ColorMaskCommon];
flags[Dirty::ColorMaskCommon] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.color_mask_common) {
if (!force && !flags[Dirty::ColorMask0]) {
return;
@@ -1165,33 +1398,30 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::MultisampleControl]) {
return;
}
flags[Dirty::MultisampleControl] = false;
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage);
oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one);
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FragmentClampColor]) {
return;
}
flags[Dirty::FragmentClampColor] = false;
- glClampColor(GL_CLAMP_FRAGMENT_COLOR, gpu.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
}
void RasterizerOpenGL::SyncBlendState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::BlendColor]) {
flags[Dirty::BlendColor] = false;
@@ -1248,14 +1478,13 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LogicOp]) {
return;
}
flags[Dirty::LogicOp] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.logic_op.enable) {
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation));
@@ -1265,14 +1494,13 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::Scissors]) {
return;
}
flags[Dirty::Scissors] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < Maxwell::NumViewports; ++index) {
if (!flags[Dirty::Scissor0 + index]) {
continue;
@@ -1291,16 +1519,15 @@ void RasterizerOpenGL::SyncScissorTest() {
}
void RasterizerOpenGL::SyncPointState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PointSize]) {
return;
}
flags[Dirty::PointSize] = false;
- oglEnable(GL_POINT_SPRITE, gpu.regs.point_sprite_enable);
+ oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable);
- if (gpu.regs.vp_point_size.enable) {
+ if (maxwell3d.regs.vp_point_size.enable) {
// By definition of GL_POINT_SIZE, it only matters if GL_PROGRAM_POINT_SIZE is disabled.
glEnable(GL_PROGRAM_POINT_SIZE);
return;
@@ -1308,32 +1535,30 @@ void RasterizerOpenGL::SyncPointState() {
// Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
// in OpenGL).
- glPointSize(std::max(1.0f, gpu.regs.point_size));
+ glPointSize(std::max(1.0f, maxwell3d.regs.point_size));
glDisable(GL_PROGRAM_POINT_SIZE);
}
void RasterizerOpenGL::SyncLineState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LineWidth]) {
return;
}
flags[Dirty::LineWidth] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable);
glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased);
}
void RasterizerOpenGL::SyncPolygonOffset() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonOffset]) {
return;
}
flags[Dirty::PolygonOffset] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable);
oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable);
oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable);
@@ -1347,18 +1572,13 @@ void RasterizerOpenGL::SyncPolygonOffset() {
}
void RasterizerOpenGL::SyncAlphaTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::AlphaTest]) {
return;
}
flags[Dirty::AlphaTest] = false;
- const auto& regs = gpu.regs;
- if (regs.alpha_test_enabled && regs.rt_control.count > 1) {
- LOG_WARNING(Render_OpenGL, "Alpha testing with more than one render target is not tested");
- }
-
+ const auto& regs = maxwell3d.regs;
if (regs.alpha_test_enabled) {
glEnable(GL_ALPHA_TEST);
glAlphaFunc(MaxwellToGL::ComparisonOp(regs.alpha_test_func), regs.alpha_test_ref);
@@ -1368,22 +1588,79 @@ void RasterizerOpenGL::SyncAlphaTest() {
}
void RasterizerOpenGL::SyncFramebufferSRGB() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FramebufferSRGB]) {
return;
}
flags[Dirty::FramebufferSRGB] = false;
- oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb);
+ oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb);
+}
+
+void RasterizerOpenGL::SyncTransformFeedback() {
+ // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
+ // when this is required.
+ const auto& regs = maxwell3d.regs;
+
+ static constexpr std::size_t STRIDE = 3;
+ std::array<GLint, 128 * STRIDE * Maxwell::NumTransformFeedbackBuffers> attribs;
+ std::array<GLint, Maxwell::NumTransformFeedbackBuffers> streams;
+
+ GLint* cursor = attribs.data();
+ GLint* current_stream = streams.data();
+
+ for (std::size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
+ const auto& layout = regs.tfb_layouts[feedback];
+ UNIMPLEMENTED_IF_MSG(layout.stride != layout.varying_count * 4, "Stride padding");
+ if (layout.varying_count == 0) {
+ continue;
+ }
+
+ *current_stream = static_cast<GLint>(feedback);
+ if (current_stream != streams.data()) {
+ // When stepping one stream, push the expected token
+ cursor[0] = GL_NEXT_BUFFER_NV;
+ cursor[1] = 0;
+ cursor[2] = 0;
+ cursor += STRIDE;
+ }
+ ++current_stream;
+
+ const auto& locations = regs.tfb_varying_locs[feedback];
+ std::optional<u8> current_index;
+ for (u32 offset = 0; offset < layout.varying_count; ++offset) {
+ const u8 location = locations[offset];
+ const u8 index = location / 4;
+
+ if (current_index == index) {
+ // Increase number of components of the previous attachment
+ ++cursor[-2];
+ continue;
+ }
+ current_index = index;
+
+ std::tie(cursor[0], cursor[2]) = TransformFeedbackEnum(location);
+ cursor[1] = 1;
+ cursor += STRIDE;
+ }
+ }
+
+ const GLsizei num_attribs = static_cast<GLsizei>((cursor - attribs.data()) / STRIDE);
+ const GLsizei num_strides = static_cast<GLsizei>(current_stream - streams.data());
+ glTransformFeedbackStreamAttribsNV(num_attribs, attribs.data(), num_strides, streams.data(),
+ GL_INTERLEAVED_ATTRIBS);
}
void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
+ if (device.UseAssemblyShaders()) {
+ SyncTransformFeedback();
+ }
+
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry));
@@ -1410,11 +1687,15 @@ void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
static_cast<GLsizeiptr>(size));
}
+ // We may have to call BeginTransformFeedbackNV here since they seem to call different
+ // implementations on Nvidia's driver (the pointer is different) but we are using
+ // ARB_transform_feedback3 features with NV_transform_feedback interactions and the ARB
+ // extension doesn't define BeginTransformFeedback (without NV) interactions. It just works.
glBeginTransformFeedback(GL_POINTS);
}
void RasterizerOpenGL::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -1431,8 +1712,9 @@ void RasterizerOpenGL::EndTransformFeedback() {
const GLuint handle = transform_feedback_buffers[index].handle;
const GPUVAddr gpu_addr = binding.Address();
const std::size_t size = binding.buffer_size;
- const auto [dest_buffer, offset] = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
- glCopyNamedBufferSubData(handle, *dest_buffer, 0, offset, static_cast<GLsizeiptr>(size));
+ const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
+ glCopyNamedBufferSubData(handle, info.handle, 0, info.offset,
+ static_cast<GLsizeiptr>(size));
}
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 435da4425..1d0f585fa 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -19,10 +19,10 @@
#include "video_core/engines/const_buffer_info.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_accelerated.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/renderer_opengl/gl_fence_manager.h"
#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -33,10 +33,11 @@
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
#include "video_core/renderer_opengl/utils.h"
+#include "video_core/shader/async_shaders.h"
#include "video_core/textures/texture.h"
-namespace Core {
-class System;
+namespace Core::Memory {
+class Memory;
}
namespace Core::Frontend {
@@ -52,10 +53,18 @@ namespace OpenGL {
struct ScreenInfo;
struct DrawParameters;
+struct BindlessSSBO {
+ GLuint64EXT address;
+ GLsizei length;
+ GLsizei padding;
+};
+static_assert(sizeof(BindlessSSBO) * CHAR_BIT == 128);
+
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- ScreenInfo& info, GLShader::ProgramManager& program_manager,
+ explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ Core::Memory::Memory& cpu_memory, const Device& device,
+ ScreenInfo& screen_info, ProgramManager& program_manager,
StateTracker& state_tracker);
~RasterizerOpenGL() override;
@@ -66,8 +75,15 @@ public:
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
void FlushAll() override;
void FlushRegion(VAddr addr, u64 size) override;
+ bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
+ void OnCPUWrite(VAddr addr, u64 size) override;
+ void SyncGuestHost() override;
+ void SignalSemaphore(GPUVAddr addr, u32 value) override;
+ void SignalSyncPoint(u32 value) override;
+ void ReleaseFences() override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void WaitForIdle() override;
void FlushCommands() override;
void TickFrame() override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
@@ -75,56 +91,65 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void LoadDiskResources(const std::atomic_bool& stop_loading,
+ void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
- void SetupDirtyFlags() override;
/// Returns true when there are commands queued to the OpenGL server.
bool AnyCommandQueued() const {
return num_queued_commands > 0;
}
+ VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
+ return async_shaders;
+ }
+
+ const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const {
+ return async_shaders;
+ }
+
private:
/// Configures the color and depth framebuffer states.
void ConfigureFramebuffers();
- void ConfigureClearFramebuffer(bool using_color_fb, bool using_depth_fb, bool using_stencil_fb);
+ /// Configures the color and depth framebuffer for clearing.
+ void ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil);
/// Configures the current constbuffers to use for the draw command.
- void SetupDrawConstBuffers(std::size_t stage_index, const Shader& shader);
+ void SetupDrawConstBuffers(std::size_t stage_index, Shader* shader);
/// Configures the current constbuffers to use for the kernel invocation.
- void SetupComputeConstBuffers(const Shader& kernel);
+ void SetupComputeConstBuffers(Shader* kernel);
/// Configures a constant buffer.
- void SetupConstBuffer(u32 binding, const Tegra::Engines::ConstBufferInfo& buffer,
- const ConstBufferEntry& entry);
+ void SetupConstBuffer(GLenum stage, u32 binding, const Tegra::Engines::ConstBufferInfo& buffer,
+ const ConstBufferEntry& entry, bool use_unified,
+ std::size_t unified_offset);
/// Configures the current global memory entries to use for the draw command.
- void SetupDrawGlobalMemory(std::size_t stage_index, const Shader& shader);
+ void SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader);
/// Configures the current global memory entries to use for the kernel invocation.
- void SetupComputeGlobalMemory(const Shader& kernel);
+ void SetupComputeGlobalMemory(Shader* kernel);
- /// Configures a constant buffer.
+ /// Configures a global memory buffer.
void SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, GPUVAddr gpu_addr,
- std::size_t size);
+ size_t size, BindlessSSBO* ssbo);
/// Configures the current textures to use for the draw command.
- void SetupDrawTextures(std::size_t stage_index, const Shader& shader);
+ void SetupDrawTextures(std::size_t stage_index, Shader* shader);
/// Configures the textures used in a compute shader.
- void SetupComputeTextures(const Shader& kernel);
+ void SetupComputeTextures(Shader* kernel);
/// Configures a texture.
void SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
const SamplerEntry& entry);
/// Configures images in a graphics shader.
- void SetupDrawImages(std::size_t stage_index, const Shader& shader);
+ void SetupDrawImages(std::size_t stage_index, Shader* shader);
/// Configures images in a compute shader.
- void SetupComputeImages(const Shader& shader);
+ void SetupComputeImages(Shader* shader);
/// Configures an image.
void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, const ImageEntry& entry);
@@ -192,6 +217,10 @@ private:
/// Syncs the framebuffer sRGB state to match the guest state
void SyncFramebufferSRGB();
+ /// Syncs transform feedback state to match guest state
+ /// @note Only valid on assembly shaders
+ void SyncTransformFeedback();
+
/// Begin a transform feedback
void BeginTransformFeedback(GLenum primitive_mode);
@@ -215,31 +244,42 @@ private:
void SetupShaders(GLenum primitive_mode);
- const Device device;
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+
+ const Device& device;
+ ScreenInfo& screen_info;
+ ProgramManager& program_manager;
+ StateTracker& state_tracker;
TextureCacheOpenGL texture_cache;
ShaderCacheOpenGL shader_cache;
SamplerCacheOpenGL sampler_cache;
FramebufferCacheOpenGL framebuffer_cache;
QueryCache query_cache;
+ OGLBufferCache buffer_cache;
+ FenceManagerOpenGL fence_manager;
- Core::System& system;
- ScreenInfo& screen_info;
- GLShader::ProgramManager& program_manager;
- StateTracker& state_tracker;
+ VideoCommon::Shader::AsyncShaders async_shaders;
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
- OGLBufferCache buffer_cache;
- VertexArrayPushBuffer vertex_array_pushbuffer{state_tracker};
- BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
- BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
+ GLint vertex_binding = 0;
std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
transform_feedback_buffers;
std::bitset<Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
enabled_transform_feedback_buffers;
+ static constexpr std::size_t NUM_CONSTANT_BUFFERS =
+ Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers *
+ Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram;
+ std::array<GLuint, NUM_CONSTANT_BUFFERS> staging_cbufs{};
+ std::size_t current_cbuf = 0;
+ OGLBuffer unified_uniform_buffer;
+
/// Number of commands queued to the OpenGL driver. Reseted on flush.
std::size_t num_queued_commands = 0;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 97803d480..0ebcec427 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
#include <utility>
#include <glad/glad.h>
#include "common/common_types.h"
@@ -82,11 +83,13 @@ void OGLSampler::Release() {
handle = 0;
}
-void OGLShader::Create(const char* source, GLenum type) {
- if (handle != 0)
+void OGLShader::Create(std::string_view source, GLenum type) {
+ if (handle != 0) {
return;
- if (source == nullptr)
+ }
+ if (source.empty()) {
return;
+ }
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
handle = GLShader::LoadShader(source, type);
@@ -125,6 +128,15 @@ void OGLProgram::Release() {
handle = 0;
}
+void OGLAssemblyProgram::Release() {
+ if (handle == 0) {
+ return;
+ }
+ MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
+ glDeleteProgramsARB(1, &handle);
+ handle = 0;
+}
+
void OGLPipeline::Create() {
if (handle != 0)
return;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index de93f4212..f48398669 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -4,6 +4,7 @@
#pragma once
+#include <string_view>
#include <utility>
#include <glad/glad.h>
#include "common/common_types.h"
@@ -127,7 +128,7 @@ public:
return *this;
}
- void Create(const char* source, GLenum type);
+ void Create(std::string_view source, GLenum type);
void Release();
@@ -167,6 +168,28 @@ public:
GLuint handle = 0;
};
+class OGLAssemblyProgram : private NonCopyable {
+public:
+ OGLAssemblyProgram() = default;
+
+ OGLAssemblyProgram(OGLAssemblyProgram&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
+ ~OGLAssemblyProgram() {
+ Release();
+ }
+
+ OGLAssemblyProgram& operator=(OGLAssemblyProgram&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
+ return *this;
+ }
+
+ /// Deletes the internal OpenGL resource
+ void Release();
+
+ GLuint handle = 0;
+};
+
class OGLPipeline : private NonCopyable {
public:
OGLPipeline() = default;
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 12c6dcfde..bd56bed0c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -10,8 +10,6 @@
#include <thread>
#include <unordered_set>
-#include <boost/functional/hash.hpp>
-
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
@@ -22,83 +20,35 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_type.h"
#include "video_core/memory_manager.h"
+#include "video_core/renderer_opengl/gl_arb_decompiler.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/utils.h"
+#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
+#include "video_core/shader_cache.h"
+#include "video_core/shader_notify.h"
namespace OpenGL {
using Tegra::Engines::ShaderType;
-using VideoCommon::Shader::CompileDepth;
-using VideoCommon::Shader::CompilerSettings;
+using VideoCommon::Shader::GetShaderAddress;
+using VideoCommon::Shader::GetShaderCode;
+using VideoCommon::Shader::GetUniqueIdentifier;
+using VideoCommon::Shader::KERNEL_MAIN_OFFSET;
using VideoCommon::Shader::ProgramCode;
using VideoCommon::Shader::Registry;
using VideoCommon::Shader::ShaderIR;
+using VideoCommon::Shader::STAGE_MAIN_OFFSET;
namespace {
-constexpr u32 STAGE_MAIN_OFFSET = 10;
-constexpr u32 KERNEL_MAIN_OFFSET = 0;
-
-constexpr CompilerSettings COMPILER_SETTINGS{CompileDepth::FullDecompile};
-
-/// Gets the address for the specified shader stage program
-GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program) {
- const auto& gpu{system.GPU().Maxwell3D()};
- const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
- return gpu.regs.code_address.CodeAddress() + shader_config.offset;
-}
-
-/// Gets if the current instruction offset is a scheduler instruction
-constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
- // Sched instructions appear once every 4 instructions.
- constexpr std::size_t SchedPeriod = 4;
- const std::size_t absolute_offset = offset - main_offset;
- return (absolute_offset % SchedPeriod) == 0;
-}
-
-/// Calculates the size of a program stream
-std::size_t CalculateProgramSize(const ProgramCode& program) {
- constexpr std::size_t start_offset = 10;
- // This is the encoded version of BRA that jumps to itself. All Nvidia
- // shaders end with one.
- constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL;
- constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL;
- std::size_t offset = start_offset;
- while (offset < program.size()) {
- const u64 instruction = program[offset];
- if (!IsSchedInstruction(offset, start_offset)) {
- if ((instruction & mask) == self_jumping_branch) {
- // End on Maxwell's "nop" instruction
- break;
- }
- if (instruction == 0) {
- break;
- }
- }
- offset++;
- }
- // The last instruction is included in the program size
- return std::min(offset + 1, program.size());
-}
-
-/// Gets the shader program code from memory for the specified address
-ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr,
- const u8* host_ptr) {
- ProgramCode code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
- ASSERT_OR_EXECUTE(host_ptr != nullptr, {
- std::fill(code.begin(), code.end(), 0);
- return code;
- });
- memory_manager.ReadBlockUnsafe(gpu_addr, code.data(), code.size() * sizeof(u64));
- code.resize(CalculateProgramSize(code));
- return code;
-}
+constexpr VideoCommon::Shader::CompilerSettings COMPILER_SETTINGS{};
/// Gets the shader type from a Maxwell program type
constexpr GLenum GetGLShaderType(ShaderType shader_type) {
@@ -116,17 +66,6 @@ constexpr GLenum GetGLShaderType(ShaderType shader_type) {
}
}
-/// Hashes one (or two) program streams
-u64 GetUniqueIdentifier(ShaderType shader_type, bool is_a, const ProgramCode& code,
- const ProgramCode& code_b = {}) {
- u64 unique_identifier = boost::hash_value(code);
- if (is_a) {
- // VertexA programs include two programs
- boost::hash_combine(unique_identifier, boost::hash_value(code_b));
- }
- return unique_identifier;
-}
-
constexpr const char* GetShaderTypeName(ShaderType shader_type) {
switch (shader_type) {
case ShaderType::Vertex:
@@ -162,6 +101,24 @@ constexpr ShaderType GetShaderType(Maxwell::ShaderProgram program_type) {
return {};
}
+constexpr GLenum AssemblyEnum(ShaderType shader_type) {
+ switch (shader_type) {
+ case ShaderType::Vertex:
+ return GL_VERTEX_PROGRAM_NV;
+ case ShaderType::TesselationControl:
+ return GL_TESS_CONTROL_PROGRAM_NV;
+ case ShaderType::TesselationEval:
+ return GL_TESS_EVALUATION_PROGRAM_NV;
+ case ShaderType::Geometry:
+ return GL_GEOMETRY_PROGRAM_NV;
+ case ShaderType::Fragment:
+ return GL_FRAGMENT_PROGRAM_NV;
+ case ShaderType::Compute:
+ return GL_COMPUTE_PROGRAM_NV;
+ }
+ return {};
+}
+
std::string MakeShaderID(u64 unique_identifier, ShaderType shader_type) {
return fmt::format("{}{:016X}", GetShaderTypeName(shader_type), unique_identifier);
}
@@ -170,7 +127,7 @@ std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) {
const VideoCore::GuestDriverProfile guest_profile{entry.texture_handler_size};
const VideoCommon::Shader::SerializedRegistryInfo info{guest_profile, entry.bound_buffer,
entry.graphics_info, entry.compute_info};
- const auto registry = std::make_shared<Registry>(entry.type, info);
+ auto registry = std::make_shared<Registry>(entry.type, info);
for (const auto& [address, value] : entry.keys) {
const auto [buffer, offset] = address;
registry->InsertKey(buffer, offset, value);
@@ -185,21 +142,6 @@ std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) {
return registry;
}
-std::shared_ptr<OGLProgram> BuildShader(const Device& device, ShaderType shader_type,
- u64 unique_identifier, const ShaderIR& ir,
- const Registry& registry, bool hint_retrievable = false) {
- const std::string shader_id = MakeShaderID(unique_identifier, shader_type);
- LOG_INFO(Render_OpenGL, "{}", shader_id);
-
- const std::string glsl = DecompileShader(device, ir, registry, shader_type, shader_id);
- OGLShader shader;
- shader.Create(glsl.c_str(), GetGLShaderType(shader_type));
-
- auto program = std::make_shared<OGLProgram>();
- program->Create(true, hint_retrievable, shader.handle);
- return program;
-}
-
std::unordered_set<GLenum> GetSupportedFormats() {
GLint num_formats;
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
@@ -216,55 +158,138 @@ std::unordered_set<GLenum> GetSupportedFormats() {
} // Anonymous namespace
-CachedShader::CachedShader(VAddr cpu_addr, std::size_t size_in_bytes,
- std::shared_ptr<VideoCommon::Shader::Registry> registry,
- ShaderEntries entries, std::shared_ptr<OGLProgram> program)
- : RasterizerCacheObject{cpu_addr}, registry{std::move(registry)}, entries{std::move(entries)},
- size_in_bytes{size_in_bytes}, program{std::move(program)} {}
+ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 unique_identifier,
+ const ShaderIR& ir, const Registry& registry, bool hint_retrievable) {
+ const std::string shader_id = MakeShaderID(unique_identifier, shader_type);
+ LOG_INFO(Render_OpenGL, "{}", shader_id);
+
+ auto program = std::make_shared<ProgramHandle>();
+
+ if (device.UseAssemblyShaders()) {
+ const std::string arb =
+ DecompileAssemblyShader(device, ir, registry, shader_type, shader_id);
+
+ GLuint& arb_prog = program->assembly_program.handle;
+
+// Commented out functions signal OpenGL errors but are compatible with apitrace.
+// Use them only to capture and replay on apitrace.
+#if 0
+ glGenProgramsNV(1, &arb_prog);
+ glLoadProgramNV(AssemblyEnum(shader_type), arb_prog, static_cast<GLsizei>(arb.size()),
+ reinterpret_cast<const GLubyte*>(arb.data()));
+#else
+ glGenProgramsARB(1, &arb_prog);
+ glNamedProgramStringEXT(arb_prog, AssemblyEnum(shader_type), GL_PROGRAM_FORMAT_ASCII_ARB,
+ static_cast<GLsizei>(arb.size()), arb.data());
+#endif
+ const auto err = reinterpret_cast<const char*>(glGetString(GL_PROGRAM_ERROR_STRING_NV));
+ if (err && *err) {
+ LOG_CRITICAL(Render_OpenGL, "{}", err);
+ LOG_INFO(Render_OpenGL, "\n{}", arb);
+ }
+ } else {
+ const std::string glsl = DecompileShader(device, ir, registry, shader_type, shader_id);
+ OGLShader shader;
+ shader.Create(glsl.c_str(), GetGLShaderType(shader_type));
+
+ program->source_program.Create(true, hint_retrievable, shader.handle);
+ }
-CachedShader::~CachedShader() = default;
+ return program;
+}
-GLuint CachedShader::GetHandle() const {
+Shader::Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry_, ShaderEntries entries_,
+ ProgramSharedPtr program_, bool is_built)
+ : registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)},
+ is_built(is_built) {
+ handle = program->assembly_program.handle;
+ if (handle == 0) {
+ handle = program->source_program.handle;
+ }
+ if (is_built) {
+ ASSERT(handle != 0);
+ }
+}
+
+Shader::~Shader() = default;
+
+GLuint Shader::GetHandle() const {
DEBUG_ASSERT(registry->IsConsistent());
- return program->handle;
+ return handle;
}
-Shader CachedShader::CreateStageFromMemory(const ShaderParameters& params,
- Maxwell::ShaderProgram program_type, ProgramCode code,
- ProgramCode code_b) {
+bool Shader::IsBuilt() const {
+ return is_built;
+}
+
+void Shader::AsyncOpenGLBuilt(OGLProgram new_program) {
+ program->source_program = std::move(new_program);
+ handle = program->source_program.handle;
+ is_built = true;
+}
+
+void Shader::AsyncGLASMBuilt(OGLAssemblyProgram new_program) {
+ program->assembly_program = std::move(new_program);
+ handle = program->assembly_program.handle;
+ is_built = true;
+}
+
+std::unique_ptr<Shader> Shader::CreateStageFromMemory(
+ const ShaderParameters& params, Maxwell::ShaderProgram program_type, ProgramCode code,
+ ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) {
const auto shader_type = GetShaderType(program_type);
- const std::size_t size_in_bytes = code.size() * sizeof(u64);
- auto registry = std::make_shared<Registry>(shader_type, params.system.GPU().Maxwell3D());
- const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
- // TODO(Rodrigo): Handle VertexA shaders
- // std::optional<ShaderIR> ir_b;
- // if (!code_b.empty()) {
- // ir_b.emplace(code_b, STAGE_MAIN_OFFSET);
- // }
- auto program = BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry);
+ auto& gpu = params.gpu;
+ gpu.ShaderNotify().MarkSharderBuilding();
+
+ auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D());
+ if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) {
+ const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
+ // TODO(Rodrigo): Handle VertexA shaders
+ // std::optional<ShaderIR> ir_b;
+ // if (!code_b.empty()) {
+ // ir_b.emplace(code_b, STAGE_MAIN_OFFSET);
+ // }
+ auto program =
+ BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry);
+ ShaderDiskCacheEntry entry;
+ entry.type = shader_type;
+ entry.code = std::move(code);
+ entry.code_b = std::move(code_b);
+ entry.unique_identifier = params.unique_identifier;
+ entry.bound_buffer = registry->GetBoundBuffer();
+ entry.graphics_info = registry->GetGraphicsInfo();
+ entry.keys = registry->GetKeys();
+ entry.bound_samplers = registry->GetBoundSamplers();
+ entry.bindless_samplers = registry->GetBindlessSamplers();
+ params.disk_cache.SaveEntry(std::move(entry));
+
+ gpu.ShaderNotify().MarkShaderComplete();
+
+ return std::unique_ptr<Shader>(new Shader(std::move(registry),
+ MakeEntries(params.device, ir, shader_type),
+ std::move(program), true));
+ } else {
+ // Required for entries
+ const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
+ auto entries = MakeEntries(params.device, ir, shader_type);
- ShaderDiskCacheEntry entry;
- entry.type = shader_type;
- entry.code = std::move(code);
- entry.code_b = std::move(code_b);
- entry.unique_identifier = params.unique_identifier;
- entry.bound_buffer = registry->GetBoundBuffer();
- entry.graphics_info = registry->GetGraphicsInfo();
- entry.keys = registry->GetKeys();
- entry.bound_samplers = registry->GetBoundSamplers();
- entry.bindless_samplers = registry->GetBindlessSamplers();
- params.disk_cache.SaveEntry(std::move(entry));
+ async_shaders.QueueOpenGLShader(params.device, shader_type, params.unique_identifier,
+ std::move(code), std::move(code_b), STAGE_MAIN_OFFSET,
+ COMPILER_SETTINGS, *registry, cpu_addr);
- return std::shared_ptr<CachedShader>(new CachedShader(
- params.cpu_addr, size_in_bytes, std::move(registry), MakeEntries(ir), std::move(program)));
+ auto program = std::make_shared<ProgramHandle>();
+ return std::unique_ptr<Shader>(
+ new Shader(std::move(registry), std::move(entries), std::move(program), false));
+ }
}
-Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code) {
- const std::size_t size_in_bytes = code.size() * sizeof(u64);
+std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params,
+ ProgramCode code) {
+ auto& gpu = params.gpu;
+ gpu.ShaderNotify().MarkSharderBuilding();
- auto& engine = params.system.GPU().KeplerCompute();
- auto registry = std::make_shared<Registry>(ShaderType::Compute, engine);
+ auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine);
const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
const u64 uid = params.unique_identifier;
auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry);
@@ -280,31 +305,43 @@ Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, Prog
entry.bindless_samplers = registry->GetBindlessSamplers();
params.disk_cache.SaveEntry(std::move(entry));
- return std::shared_ptr<CachedShader>(new CachedShader(
- params.cpu_addr, size_in_bytes, std::move(registry), MakeEntries(ir), std::move(program)));
+ gpu.ShaderNotify().MarkShaderComplete();
+
+ return std::unique_ptr<Shader>(new Shader(std::move(registry),
+ MakeEntries(params.device, ir, ShaderType::Compute),
+ std::move(program)));
}
-Shader CachedShader::CreateFromCache(const ShaderParameters& params,
- const PrecompiledShader& precompiled_shader,
- std::size_t size_in_bytes) {
- return std::shared_ptr<CachedShader>(
- new CachedShader(params.cpu_addr, size_in_bytes, precompiled_shader.registry,
- precompiled_shader.entries, precompiled_shader.program));
+std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params,
+ const PrecompiledShader& precompiled_shader) {
+ return std::unique_ptr<Shader>(new Shader(
+ precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program));
}
-ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device)
- : RasterizerCache{rasterizer}, system{system}, emu_window{emu_window}, device{device},
- disk_cache{system} {}
+ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer,
+ Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_)
+ : VideoCommon::ShaderCache<Shader>{rasterizer}, emu_window{emu_window_}, gpu{gpu_},
+ gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, device{device_} {}
-void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
+ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
+
+void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
+ disk_cache.BindTitleID(title_id);
const std::optional transferable = disk_cache.LoadTransferable();
if (!transferable) {
return;
}
- const std::vector gl_cache = disk_cache.LoadPrecompiled();
+ std::vector<ShaderDiskCachePrecompiled> gl_cache;
+ if (!device.UseAssemblyShaders()) {
+ // Only load precompiled cache when we are not using assembly shaders
+ gl_cache = disk_cache.LoadPrecompiled();
+ }
const auto supported_formats = GetSupportedFormats();
// Track if precompiled cache was altered during loading to know if we have to
@@ -343,7 +380,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
auto registry = MakeRegistry(entry);
const ShaderIR ir(entry.code, main_offset, COMPILER_SETTINGS, *registry);
- std::shared_ptr<OGLProgram> program;
+ ProgramSharedPtr program;
if (precompiled_entry) {
// If the shader is precompiled, attempt to load it with
program = GeneratePrecompiledProgram(entry, *precompiled_entry, supported_formats);
@@ -359,7 +396,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
PrecompiledShader shader;
shader.program = std::move(program);
shader.registry = std::move(registry);
- shader.entries = MakeEntries(ir);
+ shader.entries = MakeEntries(device, ir, entry.type);
std::scoped_lock lock{mutex};
if (callback) {
@@ -370,7 +407,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
}
};
- const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1ULL)};
+ const std::size_t num_workers{std::max(1U, std::thread::hardware_concurrency())};
const std::size_t bucket_size{transferable->size() / num_workers};
std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
std::vector<std::thread> threads(num_workers);
@@ -397,6 +434,11 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
return;
}
+ if (device.UseAssemblyShaders()) {
+ // Don't store precompiled binaries for assembly shaders.
+ return;
+ }
+
// TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw
// before precompiling them
@@ -404,7 +446,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
const u64 id = (*transferable)[i].unique_identifier;
const auto it = find_precompiled(id);
if (it == gl_cache.end()) {
- const GLuint program = runtime_cache.at(id).program->handle;
+ const GLuint program = runtime_cache.at(id).program->source_program.handle;
disk_cache.SavePrecompiled(id, program);
precompiled_cache_altered = true;
}
@@ -415,7 +457,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
}
}
-std::shared_ptr<OGLProgram> ShaderCacheOpenGL::GeneratePrecompiledProgram(
+ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
const std::unordered_set<GLenum>& supported_formats) {
if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) {
@@ -423,15 +465,15 @@ std::shared_ptr<OGLProgram> ShaderCacheOpenGL::GeneratePrecompiledProgram(
return {};
}
- auto program = std::make_shared<OGLProgram>();
- program->handle = glCreateProgram();
- glProgramParameteri(program->handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
- glProgramBinary(program->handle, precompiled_entry.binary_format,
- precompiled_entry.binary.data(),
+ auto program = std::make_shared<ProgramHandle>();
+ GLuint& handle = program->source_program.handle;
+ handle = glCreateProgram();
+ glProgramParameteri(handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
+ glProgramBinary(handle, precompiled_entry.binary_format, precompiled_entry.binary.data(),
static_cast<GLsizei>(precompiled_entry.binary.size()));
GLint link_status;
- glGetProgramiv(program->handle, GL_LINK_STATUS, &link_status);
+ glGetProgramiv(handle, GL_LINK_STATUS, &link_status);
if (link_status == GL_FALSE) {
LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver, removing");
return {};
@@ -440,77 +482,122 @@ std::shared_ptr<OGLProgram> ShaderCacheOpenGL::GeneratePrecompiledProgram(
return program;
}
-Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
- if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) {
- return last_shaders[static_cast<std::size_t>(program)];
+Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
+ VideoCommon::Shader::AsyncShaders& async_shaders) {
+ if (!maxwell3d.dirty.flags[Dirty::Shaders]) {
+ auto* last_shader = last_shaders[static_cast<std::size_t>(program)];
+ if (last_shader->IsBuilt()) {
+ return last_shader;
+ }
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr address{GetShaderAddress(system, program)};
+ const GPUVAddr address{GetShaderAddress(maxwell3d, program)};
+
+ if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) {
+ auto completed_work = async_shaders.GetCompletedWork();
+ for (auto& work : completed_work) {
+ Shader* shader = TryGet(work.cpu_address);
+ gpu.ShaderNotify().MarkShaderComplete();
+ if (shader == nullptr) {
+ continue;
+ }
+ using namespace VideoCommon::Shader;
+ if (work.backend == AsyncShaders::Backend::OpenGL) {
+ shader->AsyncOpenGLBuilt(std::move(work.program.opengl));
+ } else if (work.backend == AsyncShaders::Backend::GLASM) {
+ shader->AsyncGLASMBuilt(std::move(work.program.glasm));
+ }
+
+ auto& registry = shader->GetRegistry();
+
+ ShaderDiskCacheEntry entry;
+ entry.type = work.shader_type;
+ entry.code = std::move(work.code);
+ entry.code_b = std::move(work.code_b);
+ entry.unique_identifier = work.uid;
+ entry.bound_buffer = registry.GetBoundBuffer();
+ entry.graphics_info = registry.GetGraphicsInfo();
+ entry.keys = registry.GetKeys();
+ entry.bound_samplers = registry.GetBoundSamplers();
+ entry.bindless_samplers = registry.GetBindlessSamplers();
+ disk_cache.SaveEntry(std::move(entry));
+ }
+ }
// Look up shader in the cache based on address
- const auto cpu_addr{memory_manager.GpuToCpuAddress(address)};
- Shader shader{cpu_addr ? TryGet(*cpu_addr) : nullptr};
- if (shader) {
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)};
+ if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) {
return last_shaders[static_cast<std::size_t>(program)] = shader;
}
- const auto host_ptr{memory_manager.GetPointer(address)};
+ const u8* const host_ptr{gpu_memory.GetPointer(address)};
// No shader found - create a new one
- ProgramCode code{GetShaderCode(memory_manager, address, host_ptr)};
+ ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)};
ProgramCode code_b;
if (program == Maxwell::ShaderProgram::VertexA) {
- const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
- code_b = GetShaderCode(memory_manager, address_b, memory_manager.GetPointer(address_b));
+ const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)};
+ const u8* host_ptr_b = gpu_memory.GetPointer(address_b);
+ code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false);
}
+ const std::size_t code_size = code.size() * sizeof(u64);
- const auto unique_identifier = GetUniqueIdentifier(
+ const u64 unique_identifier = GetUniqueIdentifier(
GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b);
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, maxwell3d, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
+ std::unique_ptr<Shader> shader;
const auto found = runtime_cache.find(unique_identifier);
if (found == runtime_cache.end()) {
- shader = CachedShader::CreateStageFromMemory(params, program, std::move(code),
- std::move(code_b));
+ shader = Shader::CreateStageFromMemory(params, program, std::move(code), std::move(code_b),
+ async_shaders, cpu_addr.value_or(0));
} else {
- const std::size_t size_in_bytes = code.size() * sizeof(u64);
- shader = CachedShader::CreateFromCache(params, found->second, size_in_bytes);
+ shader = Shader::CreateFromCache(params, found->second);
}
- Register(shader);
- return last_shaders[static_cast<std::size_t>(program)] = shader;
+ Shader* const result = shader.get();
+ if (cpu_addr) {
+ Register(std::move(shader), *cpu_addr, code_size);
+ } else {
+ null_shader = std::move(shader);
+ }
+
+ return last_shaders[static_cast<std::size_t>(program)] = result;
}
-Shader ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto cpu_addr{memory_manager.GpuToCpuAddress(code_addr)};
+Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)};
- auto kernel = cpu_addr ? TryGet(*cpu_addr) : nullptr;
- if (kernel) {
+ if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) {
return kernel;
}
- const auto host_ptr{memory_manager.GetPointer(code_addr)};
// No kernel found, create a new one
- auto code{GetShaderCode(memory_manager, code_addr, host_ptr)};
- const auto unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
+ const u8* host_ptr{gpu_memory.GetPointer(code_addr)};
+ ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)};
+ const std::size_t code_size{code.size() * sizeof(u64)};
+ const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, kepler_compute, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
+ std::unique_ptr<Shader> kernel;
const auto found = runtime_cache.find(unique_identifier);
if (found == runtime_cache.end()) {
- kernel = CachedShader::CreateKernelFromMemory(params, std::move(code));
+ kernel = Shader::CreateKernelFromMemory(params, std::move(code));
} else {
- const std::size_t size_in_bytes = code.size() * sizeof(u64);
- kernel = CachedShader::CreateFromCache(params, found->second, size_in_bytes);
+ kernel = Shader::CreateFromCache(params, found->second);
}
- Register(kernel);
- return kernel;
+ Shader* const result = kernel.get();
+ if (cpu_addr) {
+ Register(std::move(kernel), *cpu_addr, code_size);
+ } else {
+ null_kernel = std::move(kernel);
+ }
+ return result;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index c836df5bd..1708af06a 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -18,114 +18,143 @@
#include "common/common_types.h"
#include "video_core/engines/shader_type.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
+#include "video_core/shader_cache.h"
-namespace Core {
-class System;
+namespace Tegra {
+class MemoryManager;
}
namespace Core::Frontend {
class EmuWindow;
}
+namespace VideoCommon::Shader {
+class AsyncShaders;
+}
+
namespace OpenGL {
-class CachedShader;
class Device;
class RasterizerOpenGL;
-struct UnspecializedShader;
-using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+struct ProgramHandle {
+ OGLProgram source_program;
+ OGLAssemblyProgram assembly_program;
+};
+using ProgramSharedPtr = std::shared_ptr<ProgramHandle>;
+
struct PrecompiledShader {
- std::shared_ptr<OGLProgram> program;
+ ProgramSharedPtr program;
std::shared_ptr<VideoCommon::Shader::Registry> registry;
ShaderEntries entries;
};
struct ShaderParameters {
- Core::System& system;
+ Tegra::GPU& gpu;
+ Tegra::Engines::ConstBufferEngineInterface& engine;
ShaderDiskCacheOpenGL& disk_cache;
const Device& device;
VAddr cpu_addr;
- u8* host_ptr;
+ const u8* host_ptr;
u64 unique_identifier;
};
-class CachedShader final : public RasterizerCacheObject {
+ProgramSharedPtr BuildShader(const Device& device, Tegra::Engines::ShaderType shader_type,
+ u64 unique_identifier, const VideoCommon::Shader::ShaderIR& ir,
+ const VideoCommon::Shader::Registry& registry,
+ bool hint_retrievable = false);
+
+class Shader final {
public:
- ~CachedShader();
+ ~Shader();
/// Gets the GL program handle for the shader
GLuint GetHandle() const;
- /// Returns the size in bytes of the shader
- std::size_t GetSizeInBytes() const override {
- return size_in_bytes;
- }
+ bool IsBuilt() const;
/// Gets the shader entries for the shader
const ShaderEntries& GetEntries() const {
return entries;
}
- static Shader CreateStageFromMemory(const ShaderParameters& params,
- Maxwell::ShaderProgram program_type,
- ProgramCode program_code, ProgramCode program_code_b);
- static Shader CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code);
+ const VideoCommon::Shader::Registry& GetRegistry() const {
+ return *registry;
+ }
+
+ /// Mark a OpenGL shader as built
+ void AsyncOpenGLBuilt(OGLProgram new_program);
+
+ /// Mark a GLASM shader as built
+ void AsyncGLASMBuilt(OGLAssemblyProgram new_program);
+
+ static std::unique_ptr<Shader> CreateStageFromMemory(
+ const ShaderParameters& params, Maxwell::ShaderProgram program_type,
+ ProgramCode program_code, ProgramCode program_code_b,
+ VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr);
+
+ static std::unique_ptr<Shader> CreateKernelFromMemory(const ShaderParameters& params,
+ ProgramCode code);
- static Shader CreateFromCache(const ShaderParameters& params,
- const PrecompiledShader& precompiled_shader,
- std::size_t size_in_bytes);
+ static std::unique_ptr<Shader> CreateFromCache(const ShaderParameters& params,
+ const PrecompiledShader& precompiled_shader);
private:
- explicit CachedShader(VAddr cpu_addr, std::size_t size_in_bytes,
- std::shared_ptr<VideoCommon::Shader::Registry> registry,
- ShaderEntries entries, std::shared_ptr<OGLProgram> program);
+ explicit Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry, ShaderEntries entries,
+ ProgramSharedPtr program, bool is_built = true);
std::shared_ptr<VideoCommon::Shader::Registry> registry;
ShaderEntries entries;
- std::size_t size_in_bytes = 0;
- std::shared_ptr<OGLProgram> program;
+ ProgramSharedPtr program;
+ GLuint handle = 0;
+ bool is_built{};
};
-class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
+class ShaderCacheOpenGL final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device);
+ explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::Frontend::EmuWindow& emu_window,
+ Tegra::GPU& gpu, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::KeplerCompute& kepler_compute,
+ Tegra::MemoryManager& gpu_memory, const Device& device);
+ ~ShaderCacheOpenGL() override;
/// Loads disk cache for the current game
- void LoadDiskCache(const std::atomic_bool& stop_loading,
+ void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
/// Gets the current specified shader stage program
- Shader GetStageProgram(Maxwell::ShaderProgram program);
+ Shader* GetStageProgram(Maxwell::ShaderProgram program,
+ VideoCommon::Shader::AsyncShaders& async_shaders);
/// Gets a compute kernel in the passed address
- Shader GetComputeKernel(GPUVAddr code_addr);
-
-protected:
- // We do not have to flush this cache as things in it are never modified by us.
- void FlushObjectInner(const Shader& object) override {}
+ Shader* GetComputeKernel(GPUVAddr code_addr);
private:
- std::shared_ptr<OGLProgram> GeneratePrecompiledProgram(
+ ProgramSharedPtr GeneratePrecompiledProgram(
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
const std::unordered_set<GLenum>& supported_formats);
- Core::System& system;
Core::Frontend::EmuWindow& emu_window;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
const Device& device;
+
ShaderDiskCacheOpenGL disk_cache;
std::unordered_map<u64, PrecompiledShader> runtime_cache;
- std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
+ std::unique_ptr<Shader> null_shader;
+ std::unique_ptr<Shader> null_kernel;
+
+ std::array<Shader*, Maxwell::MaxShaderProgram> last_shaders{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index b1804e9ea..95ca96c8e 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -37,6 +37,7 @@ using Tegra::Shader::IpaMode;
using Tegra::Shader::IpaSampleMode;
using Tegra::Shader::PixelImap;
using Tegra::Shader::Register;
+using Tegra::Shader::TextureType;
using VideoCommon::Shader::BuildTransformFeedback;
using VideoCommon::Shader::Registry;
@@ -61,8 +62,8 @@ struct TextureDerivates {};
using TextureArgument = std::pair<Type, Node>;
using TextureIR = std::variant<TextureOffset, TextureDerivates, TextureArgument>;
-constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
- static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float));
+constexpr u32 MAX_CONSTBUFFER_SCALARS = static_cast<u32>(Maxwell::MaxConstBufferSize) / sizeof(u32);
+constexpr u32 MAX_CONSTBUFFER_ELEMENTS = MAX_CONSTBUFFER_SCALARS / sizeof(u32);
constexpr std::string_view CommonDeclarations = R"(#define ftoi floatBitsToInt
#define ftou floatBitsToUint
@@ -402,6 +403,13 @@ std::string FlowStackTopName(MetaStackClass stack) {
return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack));
}
+bool UseUnifiedUniforms(const Device& device, const ShaderIR& ir, ShaderType stage) {
+ const u32 num_ubos = static_cast<u32>(ir.GetConstantBuffers().size());
+ // We waste one UBO for emulation
+ const u32 num_available_ubos = device.GetMaxUniformBuffers(stage) - 1;
+ return num_ubos > num_available_ubos;
+}
+
struct GenericVaryingDescription {
std::string name;
u8 first_element = 0;
@@ -412,8 +420,9 @@ class GLSLDecompiler final {
public:
explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
ShaderType stage, std::string_view identifier, std::string_view suffix)
- : device{device}, ir{ir}, registry{registry}, stage{stage},
- identifier{identifier}, suffix{suffix}, header{ir.GetHeader()} {
+ : device{device}, ir{ir}, registry{registry}, stage{stage}, identifier{identifier},
+ suffix{suffix}, header{ir.GetHeader()}, use_unified_uniforms{
+ UseUnifiedUniforms(device, ir, stage)} {
if (stage != ShaderType::Compute) {
transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo());
}
@@ -484,7 +493,7 @@ private:
code.AddLine("switch (jmp_to) {{");
for (const auto& pair : ir.GetBasicBlocks()) {
- const auto [address, bb] = pair;
+ const auto& [address, bb] = pair;
code.AddLine("case 0x{:X}U: {{", address);
++code.scope;
@@ -518,6 +527,9 @@ private:
if (device.HasImageLoadFormatted()) {
code.AddLine("#extension GL_EXT_shader_image_load_formatted : require");
}
+ if (device.HasTextureShadowLod()) {
+ code.AddLine("#extension GL_EXT_texture_shadow_lod : require");
+ }
if (device.HasWarpIntrinsics()) {
code.AddLine("#extension GL_NV_gpu_shader5 : require");
code.AddLine("#extension GL_NV_shader_thread_group : require");
@@ -590,8 +602,15 @@ private:
return;
}
const auto& info = registry.GetComputeInfo();
- if (const u32 size = info.shared_memory_size_in_words; size > 0) {
- code.AddLine("shared uint smem[{}];", size);
+ if (u32 size = info.shared_memory_size_in_words * 4; size > 0) {
+ const u32 limit = device.GetMaxComputeSharedMemorySize();
+ if (size > limit) {
+ LOG_ERROR(Render_OpenGL, "Shared memory size {} is clamped to host's limit {}",
+ size, limit);
+ size = limit;
+ }
+
+ code.AddLine("shared uint smem[{}];", size / 4);
code.AddNewLine();
}
code.AddLine("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;",
@@ -618,7 +637,9 @@ private:
break;
}
}
- if (stage != ShaderType::Vertex || device.HasVertexViewportLayer()) {
+
+ if (stage != ShaderType::Geometry &&
+ (stage != ShaderType::Vertex || device.HasVertexViewportLayer())) {
if (ir.UsesLayer()) {
code.AddLine("int gl_Layer;");
}
@@ -647,6 +668,16 @@ private:
--code.scope;
code.AddLine("}};");
code.AddNewLine();
+
+ if (stage == ShaderType::Geometry) {
+ if (ir.UsesLayer()) {
+ code.AddLine("out int gl_Layer;");
+ }
+ if (ir.UsesViewportIndex()) {
+ code.AddLine("out int gl_ViewportIndex;");
+ }
+ }
+ code.AddNewLine();
}
void DeclareRegisters() {
@@ -782,7 +813,7 @@ private:
const u8 location = static_cast<u8>(static_cast<u32>(index) * 4 + element);
const auto it = transform_feedback.find(location);
if (it == transform_feedback.end()) {
- return {};
+ return std::nullopt;
}
return it->second.components;
}
@@ -834,11 +865,24 @@ private:
}
void DeclareConstantBuffers() {
+ if (use_unified_uniforms) {
+ const u32 binding = device.GetBaseBindings(stage).shader_storage_buffer +
+ static_cast<u32>(ir.GetGlobalMemory().size());
+ code.AddLine("layout (std430, binding = {}) readonly buffer UnifiedUniforms {{",
+ binding);
+ code.AddLine(" uint cbufs[];");
+ code.AddLine("}};");
+ code.AddNewLine();
+ return;
+ }
+
u32 binding = device.GetBaseBindings(stage).uniform_buffer;
- for (const auto& [index, cbuf] : ir.GetConstantBuffers()) {
+ for (const auto [index, info] : ir.GetConstantBuffers()) {
+ const u32 num_elements = Common::AlignUp(info.GetSize(), 4) / 4;
+ const u32 size = info.IsIndirect() ? MAX_CONSTBUFFER_ELEMENTS : num_elements;
code.AddLine("layout (std140, binding = {}) uniform {} {{", binding++,
GetConstBufferBlock(index));
- code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS);
+ code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), size);
code.AddLine("}};");
code.AddNewLine();
}
@@ -869,37 +913,37 @@ private:
for (const auto& sampler : ir.GetSamplers()) {
const std::string name = GetSampler(sampler);
const std::string description = fmt::format("layout (binding = {}) uniform", binding);
- binding += sampler.IsIndexed() ? sampler.Size() : 1;
+ binding += sampler.is_indexed ? sampler.size : 1;
std::string sampler_type = [&]() {
- if (sampler.IsBuffer()) {
+ if (sampler.is_buffer) {
return "samplerBuffer";
}
- switch (sampler.GetType()) {
- case Tegra::Shader::TextureType::Texture1D:
+ switch (sampler.type) {
+ case TextureType::Texture1D:
return "sampler1D";
- case Tegra::Shader::TextureType::Texture2D:
+ case TextureType::Texture2D:
return "sampler2D";
- case Tegra::Shader::TextureType::Texture3D:
+ case TextureType::Texture3D:
return "sampler3D";
- case Tegra::Shader::TextureType::TextureCube:
+ case TextureType::TextureCube:
return "samplerCube";
default:
UNREACHABLE();
return "sampler2D";
}
}();
- if (sampler.IsArray()) {
+ if (sampler.is_array) {
sampler_type += "Array";
}
- if (sampler.IsShadow()) {
+ if (sampler.is_shadow) {
sampler_type += "Shadow";
}
- if (!sampler.IsIndexed()) {
+ if (!sampler.is_indexed) {
code.AddLine("{} {} {};", description, sampler_type, name);
} else {
- code.AddLine("{} {} {}[{}];", description, sampler_type, name, sampler.Size());
+ code.AddLine("{} {} {}[{}];", description, sampler_type, name, sampler.size);
}
}
if (!ir.GetSamplers().empty()) {
@@ -945,14 +989,14 @@ private:
u32 binding = device.GetBaseBindings(stage).image;
for (const auto& image : ir.GetImages()) {
std::string qualifier = "coherent volatile";
- if (image.IsRead() && !image.IsWritten()) {
+ if (image.is_read && !image.is_written) {
qualifier += " readonly";
- } else if (image.IsWritten() && !image.IsRead()) {
+ } else if (image.is_written && !image.is_read) {
qualifier += " writeonly";
}
- const char* format = image.IsAtomic() ? "r32ui, " : "";
- const char* type_declaration = GetImageTypeDeclaration(image.GetType());
+ const char* format = image.is_atomic ? "r32ui, " : "";
+ const char* type_declaration = GetImageTypeDeclaration(image.type);
code.AddLine("layout ({}binding = {}) {} uniform uimage{} {};", format, binding++,
qualifier, type_declaration, GetImage(image));
}
@@ -1037,42 +1081,51 @@ private:
if (const auto cbuf = std::get_if<CbufNode>(&*node)) {
const Node offset = cbuf->GetOffset();
+ const u32 base_unified_offset = cbuf->GetIndex() * MAX_CONSTBUFFER_SCALARS;
+
if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
// Direct access
const u32 offset_imm = immediate->GetValue();
ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
- return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
- offset_imm / (4 * 4), (offset_imm / 4) % 4),
- Type::Uint};
+ if (use_unified_uniforms) {
+ return {fmt::format("cbufs[{}]", base_unified_offset + offset_imm / 4),
+ Type::Uint};
+ } else {
+ return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
+ offset_imm / (4 * 4), (offset_imm / 4) % 4),
+ Type::Uint};
+ }
}
- if (std::holds_alternative<OperationNode>(*offset)) {
- // Indirect access
- const std::string final_offset = code.GenerateTemporary();
- code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
+ // Indirect access
+ if (use_unified_uniforms) {
+ return {fmt::format("cbufs[{} + ({} >> 2)]", base_unified_offset,
+ Visit(offset).AsUint()),
+ Type::Uint};
+ }
- if (!device.HasComponentIndexingBug()) {
- return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
- final_offset, final_offset),
- Type::Uint};
- }
+ const std::string final_offset = code.GenerateTemporary();
+ code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
- // AMD's proprietary GLSL compiler emits ill code for variable component access.
- // To bypass this driver bug generate 4 ifs, one per each component.
- const std::string pack = code.GenerateTemporary();
- code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
- final_offset);
-
- const std::string result = code.GenerateTemporary();
- code.AddLine("uint {};", result);
- for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
- code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
- pack, GetSwizzle(swizzle));
- }
- return {result, Type::Uint};
+ if (!device.HasComponentIndexingBug()) {
+ return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
+ final_offset, final_offset),
+ Type::Uint};
}
- UNREACHABLE_MSG("Unmanaged offset node type");
+ // AMD's proprietary GLSL compiler emits ill code for variable component access.
+ // To bypass this driver bug generate 4 ifs, one per each component.
+ const std::string pack = code.GenerateTemporary();
+ code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
+ final_offset);
+
+ const std::string result = code.GenerateTemporary();
+ code.AddLine("uint {};", result);
+ for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
+ code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, pack,
+ GetSwizzle(swizzle));
+ }
+ return {result, Type::Uint};
}
if (const auto gmem = std::get_if<GmemNode>(&*node)) {
@@ -1144,6 +1197,7 @@ private:
return {"gl_FragCoord"s + GetSwizzle(element), Type::Float};
default:
UNREACHABLE();
+ return {"0", Type::Int};
}
case Attribute::Index::FrontColor:
return {"gl_Color"s + GetSwizzle(element), Type::Float};
@@ -1241,21 +1295,21 @@ private:
switch (element) {
case 0:
UNIMPLEMENTED();
- return {};
+ return std::nullopt;
case 1:
if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
- return {};
+ return std::nullopt;
}
return {{"gl_Layer", Type::Int}};
case 2:
if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
- return {};
+ return std::nullopt;
}
return {{"gl_ViewportIndex", Type::Int}};
case 3:
return {{"gl_PointSize", Type::Float}};
}
- return {};
+ return std::nullopt;
case Attribute::Index::FrontColor:
return {{"gl_FrontColor"s + GetSwizzle(element), Type::Float}};
case Attribute::Index::FrontSecondaryColor:
@@ -1278,7 +1332,7 @@ private:
Type::Float}};
}
UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
- return {};
+ return std::nullopt;
}
}
@@ -1335,16 +1389,27 @@ private:
ASSERT(meta);
const std::size_t count = operation.GetOperandsCount();
- const bool has_array = meta->sampler.IsArray();
- const bool has_shadow = meta->sampler.IsShadow();
+ const bool has_array = meta->sampler.is_array;
+ const bool has_shadow = meta->sampler.is_shadow;
+ const bool workaround_lod_array_shadow_as_grad =
+ !device.HasTextureShadowLod() && function_suffix == "Lod" && meta->sampler.is_shadow &&
+ ((meta->sampler.type == TextureType::Texture2D && meta->sampler.is_array) ||
+ meta->sampler.type == TextureType::TextureCube);
+
+ std::string expr = "texture";
+
+ if (workaround_lod_array_shadow_as_grad) {
+ expr += "Grad";
+ } else {
+ expr += function_suffix;
+ }
- std::string expr = "texture" + function_suffix;
if (!meta->aoffi.empty()) {
expr += "Offset";
} else if (!meta->ptp.empty()) {
expr += "Offsets";
}
- if (!meta->sampler.IsIndexed()) {
+ if (!meta->sampler.is_indexed) {
expr += '(' + GetSampler(meta->sampler) + ", ";
} else {
expr += '(' + GetSampler(meta->sampler) + '[' + Visit(meta->index).AsUint() + "], ";
@@ -1372,6 +1437,18 @@ private:
expr += ')';
}
+ if (workaround_lod_array_shadow_as_grad) {
+ switch (meta->sampler.type) {
+ case TextureType::Texture2D:
+ return expr + ", vec2(0.0), vec2(0.0))";
+ case TextureType::TextureCube:
+ return expr + ", vec3(0.0), vec3(0.0))";
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+
for (const auto& variant : extras) {
if (const auto argument = std::get_if<TextureArgument>(&variant)) {
expr += GenerateTextureArgument(*argument);
@@ -1482,8 +1559,8 @@ private:
dy += '(';
for (std::size_t index = 0; index < components; ++index) {
- const auto operand_x{derivates.at(index * 2)};
- const auto operand_y{derivates.at(index * 2 + 1)};
+ const auto& operand_x{derivates.at(index * 2)};
+ const auto& operand_y{derivates.at(index * 2 + 1)};
dx += Visit(operand_x).AsFloat();
dy += Visit(operand_y).AsFloat();
@@ -1536,7 +1613,9 @@ private:
Expression target;
if (const auto gpr = std::get_if<GprNode>(&*dest)) {
if (gpr->GetIndex() == Register::ZeroIndex) {
- // Writing to Register::ZeroIndex is a no op
+ // Writing to Register::ZeroIndex is a no op but we still have to visit the source
+ // as it might have side effects.
+ code.AddLine("{};", Visit(src).GetCode());
return {};
}
target = {GetRegister(gpr->GetIndex()), Type::Float};
@@ -1838,38 +1917,48 @@ private:
Type::HalfFloat};
}
- template <Type type>
- Expression LogicalLessThan(Operation operation) {
- return GenerateBinaryInfix(operation, "<", Type::Bool, type, type);
- }
-
- template <Type type>
- Expression LogicalEqual(Operation operation) {
- return GenerateBinaryInfix(operation, "==", Type::Bool, type, type);
- }
+ template <const std::string_view& op, Type type, bool unordered = false>
+ Expression Comparison(Operation operation) {
+ static_assert(!unordered || type == Type::Float);
- template <Type type>
- Expression LogicalLessEqual(Operation operation) {
- return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type);
- }
+ Expression expr = GenerateBinaryInfix(operation, op, Type::Bool, type, type);
- template <Type type>
- Expression LogicalGreaterThan(Operation operation) {
- return GenerateBinaryInfix(operation, ">", Type::Bool, type, type);
+ if constexpr (op.compare("!=") == 0 && type == Type::Float && !unordered) {
+ // GLSL's operator!=(float, float) doesn't seem be ordered. This happens on both AMD's
+ // and Nvidia's proprietary stacks. Manually force an ordered comparison.
+ return {fmt::format("({} && !isnan({}) && !isnan({}))", expr.AsBool(),
+ VisitOperand(operation, 0).AsFloat(),
+ VisitOperand(operation, 1).AsFloat()),
+ Type::Bool};
+ }
+ if constexpr (!unordered) {
+ return expr;
+ }
+ // Unordered comparisons are always true for NaN operands.
+ return {fmt::format("({} || isnan({}) || isnan({}))", expr.AsBool(),
+ VisitOperand(operation, 0).AsFloat(),
+ VisitOperand(operation, 1).AsFloat()),
+ Type::Bool};
}
- template <Type type>
- Expression LogicalNotEqual(Operation operation) {
- return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type);
+ Expression FOrdered(Operation operation) {
+ return {fmt::format("(!isnan({}) && !isnan({}))", VisitOperand(operation, 0).AsFloat(),
+ VisitOperand(operation, 1).AsFloat()),
+ Type::Bool};
}
- template <Type type>
- Expression LogicalGreaterEqual(Operation operation) {
- return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type);
+ Expression FUnordered(Operation operation) {
+ return {fmt::format("(isnan({}) || isnan({}))", VisitOperand(operation, 0).AsFloat(),
+ VisitOperand(operation, 1).AsFloat()),
+ Type::Bool};
}
- Expression LogicalFIsNan(Operation operation) {
- return GenerateUnary(operation, "isnan", Type::Bool, Type::Float);
+ Expression LogicalAddCarry(Operation operation) {
+ const std::string carry = code.GenerateTemporary();
+ code.AddLine("uint {};", carry);
+ code.AddLine("uaddCarry({}, {}, {});", VisitOperand(operation, 0).AsUint(),
+ VisitOperand(operation, 1).AsUint(), carry);
+ return {fmt::format("({} != 0)", carry), Type::Bool};
}
Expression LogicalAssign(Operation operation) {
@@ -1967,24 +2056,39 @@ private:
}
Expression Texture(Operation operation) {
- const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
- ASSERT(meta);
-
- std::string expr = GenerateTexture(
- operation, "", {TextureOffset{}, TextureArgument{Type::Float, meta->bias}});
- if (meta->sampler.IsShadow()) {
- expr = "vec4(" + expr + ')';
+ const auto meta = std::get<MetaTexture>(operation.GetMeta());
+ const bool separate_dc = meta.sampler.type == TextureType::TextureCube &&
+ meta.sampler.is_array && meta.sampler.is_shadow;
+ // TODO: Replace this with an array and make GenerateTexture use C++20 std::span
+ const std::vector<TextureIR> extras{
+ TextureOffset{},
+ TextureArgument{Type::Float, meta.bias},
+ };
+ std::string expr = GenerateTexture(operation, "", extras, separate_dc);
+ if (meta.sampler.is_shadow) {
+ expr = fmt::format("vec4({})", expr);
}
- return {expr + GetSwizzle(meta->element), Type::Float};
+ return {expr + GetSwizzle(meta.element), Type::Float};
}
Expression TextureLod(Operation operation) {
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- std::string expr = GenerateTexture(
- operation, "Lod", {TextureArgument{Type::Float, meta->lod}, TextureOffset{}});
- if (meta->sampler.IsShadow()) {
+ std::string expr{};
+
+ if (!device.HasTextureShadowLod() && meta->sampler.is_shadow &&
+ ((meta->sampler.type == TextureType::Texture2D && meta->sampler.is_array) ||
+ meta->sampler.type == TextureType::TextureCube)) {
+ LOG_ERROR(Render_OpenGL,
+ "Device lacks GL_EXT_texture_shadow_lod, using textureGrad as a workaround");
+ expr = GenerateTexture(operation, "Lod", {});
+ } else {
+ expr = GenerateTexture(operation, "Lod",
+ {TextureArgument{Type::Float, meta->lod}, TextureOffset{}});
+ }
+
+ if (meta->sampler.is_shadow) {
expr = "vec4(" + expr + ')';
}
return {expr + GetSwizzle(meta->element), Type::Float};
@@ -1993,11 +2097,11 @@ private:
Expression TextureGather(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
- const auto type = meta.sampler.IsShadow() ? Type::Float : Type::Int;
- const bool separate_dc = meta.sampler.IsShadow();
+ const auto type = meta.sampler.is_shadow ? Type::Float : Type::Int;
+ const bool separate_dc = meta.sampler.is_shadow;
std::vector<TextureIR> ir;
- if (meta.sampler.IsShadow()) {
+ if (meta.sampler.is_shadow) {
ir = {TextureOffset{}};
} else {
ir = {TextureOffset{}, TextureArgument{type, meta.component}};
@@ -2042,7 +2146,7 @@ private:
constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"};
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- UNIMPLEMENTED_IF(meta->sampler.IsArray());
+ UNIMPLEMENTED_IF(meta->sampler.is_array);
const std::size_t count = operation.GetOperandsCount();
std::string expr = "texelFetch(";
@@ -2063,7 +2167,7 @@ private:
}
expr += ')';
- if (meta->lod && !meta->sampler.IsBuffer()) {
+ if (meta->lod && !meta->sampler.is_buffer) {
expr += ", ";
expr += Visit(meta->lod).AsInt();
}
@@ -2074,12 +2178,10 @@ private:
}
Expression TextureGradient(Operation operation) {
- const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
- ASSERT(meta);
-
+ const auto& meta = std::get<MetaTexture>(operation.GetMeta());
std::string expr =
GenerateTexture(operation, "Grad", {TextureDerivates{}, TextureOffset{}});
- return {std::move(expr) + GetSwizzle(meta->element), Type::Float};
+ return {std::move(expr) + GetSwizzle(meta.element), Type::Float};
}
Expression ImageLoad(Operation operation) {
@@ -2295,6 +2397,18 @@ private:
return {"gl_SubGroupInvocationARB", Type::Uint};
}
+ template <const std::string_view& comparison>
+ Expression ThreadMask(Operation) {
+ if (device.HasWarpIntrinsics()) {
+ return {fmt::format("gl_Thread{}MaskNV", comparison), Type::Uint};
+ }
+ if (device.HasShaderBallot()) {
+ return {fmt::format("uint(gl_SubGroup{}MaskARB)", comparison), Type::Uint};
+ }
+ LOG_ERROR(Render_OpenGL, "Thread mask intrinsics are required by the shader");
+ return {"0U", Type::Uint};
+ }
+
Expression ShuffleIndexed(Operation operation) {
std::string value = VisitOperand(operation, 0).AsFloat();
@@ -2307,7 +2421,21 @@ private:
return {fmt::format("readInvocationARB({}, {})", value, index), Type::Float};
}
- Expression MemoryBarrierGL(Operation) {
+ Expression Barrier(Operation) {
+ if (!ir.IsDecompiled()) {
+ LOG_ERROR(Render_OpenGL, "barrier() used but shader is not decompiled");
+ return {};
+ }
+ code.AddLine("barrier();");
+ return {};
+ }
+
+ Expression MemoryBarrierGroup(Operation) {
+ code.AddLine("groupMemoryBarrier();");
+ return {};
+ }
+
+ Expression MemoryBarrierGlobal(Operation) {
code.AddLine("memoryBarrier();");
return {};
}
@@ -2316,6 +2444,19 @@ private:
Func() = delete;
~Func() = delete;
+ static constexpr std::string_view LessThan = "<";
+ static constexpr std::string_view Equal = "==";
+ static constexpr std::string_view LessEqual = "<=";
+ static constexpr std::string_view GreaterThan = ">";
+ static constexpr std::string_view NotEqual = "!=";
+ static constexpr std::string_view GreaterEqual = ">=";
+
+ static constexpr std::string_view Eq = "Eq";
+ static constexpr std::string_view Ge = "Ge";
+ static constexpr std::string_view Gt = "Gt";
+ static constexpr std::string_view Le = "Le";
+ static constexpr std::string_view Lt = "Lt";
+
static constexpr std::string_view Add = "Add";
static constexpr std::string_view Min = "Min";
static constexpr std::string_view Max = "Max";
@@ -2417,27 +2558,36 @@ private:
&GLSLDecompiler::LogicalPick2,
&GLSLDecompiler::LogicalAnd2,
- &GLSLDecompiler::LogicalLessThan<Type::Float>,
- &GLSLDecompiler::LogicalEqual<Type::Float>,
- &GLSLDecompiler::LogicalLessEqual<Type::Float>,
- &GLSLDecompiler::LogicalGreaterThan<Type::Float>,
- &GLSLDecompiler::LogicalNotEqual<Type::Float>,
- &GLSLDecompiler::LogicalGreaterEqual<Type::Float>,
- &GLSLDecompiler::LogicalFIsNan,
-
- &GLSLDecompiler::LogicalLessThan<Type::Int>,
- &GLSLDecompiler::LogicalEqual<Type::Int>,
- &GLSLDecompiler::LogicalLessEqual<Type::Int>,
- &GLSLDecompiler::LogicalGreaterThan<Type::Int>,
- &GLSLDecompiler::LogicalNotEqual<Type::Int>,
- &GLSLDecompiler::LogicalGreaterEqual<Type::Int>,
-
- &GLSLDecompiler::LogicalLessThan<Type::Uint>,
- &GLSLDecompiler::LogicalEqual<Type::Uint>,
- &GLSLDecompiler::LogicalLessEqual<Type::Uint>,
- &GLSLDecompiler::LogicalGreaterThan<Type::Uint>,
- &GLSLDecompiler::LogicalNotEqual<Type::Uint>,
- &GLSLDecompiler::LogicalGreaterEqual<Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::LessThan, Type::Float, false>,
+ &GLSLDecompiler::Comparison<Func::Equal, Type::Float, false>,
+ &GLSLDecompiler::Comparison<Func::LessEqual, Type::Float, false>,
+ &GLSLDecompiler::Comparison<Func::GreaterThan, Type::Float, false>,
+ &GLSLDecompiler::Comparison<Func::NotEqual, Type::Float, false>,
+ &GLSLDecompiler::Comparison<Func::GreaterEqual, Type::Float, false>,
+ &GLSLDecompiler::FOrdered,
+ &GLSLDecompiler::FUnordered,
+ &GLSLDecompiler::Comparison<Func::LessThan, Type::Float, true>,
+ &GLSLDecompiler::Comparison<Func::Equal, Type::Float, true>,
+ &GLSLDecompiler::Comparison<Func::LessEqual, Type::Float, true>,
+ &GLSLDecompiler::Comparison<Func::GreaterThan, Type::Float, true>,
+ &GLSLDecompiler::Comparison<Func::NotEqual, Type::Float, true>,
+ &GLSLDecompiler::Comparison<Func::GreaterEqual, Type::Float, true>,
+
+ &GLSLDecompiler::Comparison<Func::LessThan, Type::Int>,
+ &GLSLDecompiler::Comparison<Func::Equal, Type::Int>,
+ &GLSLDecompiler::Comparison<Func::LessEqual, Type::Int>,
+ &GLSLDecompiler::Comparison<Func::GreaterThan, Type::Int>,
+ &GLSLDecompiler::Comparison<Func::NotEqual, Type::Int>,
+ &GLSLDecompiler::Comparison<Func::GreaterEqual, Type::Int>,
+
+ &GLSLDecompiler::Comparison<Func::LessThan, Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::Equal, Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::LessEqual, Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::GreaterThan, Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::NotEqual, Type::Uint>,
+ &GLSLDecompiler::Comparison<Func::GreaterEqual, Type::Uint>,
+
+ &GLSLDecompiler::LogicalAddCarry,
&GLSLDecompiler::Logical2HLessThan<false>,
&GLSLDecompiler::Logical2HEqual<false>,
@@ -2524,9 +2674,16 @@ private:
&GLSLDecompiler::VoteEqual,
&GLSLDecompiler::ThreadId,
+ &GLSLDecompiler::ThreadMask<Func::Eq>,
+ &GLSLDecompiler::ThreadMask<Func::Ge>,
+ &GLSLDecompiler::ThreadMask<Func::Gt>,
+ &GLSLDecompiler::ThreadMask<Func::Le>,
+ &GLSLDecompiler::ThreadMask<Func::Lt>,
&GLSLDecompiler::ShuffleIndexed,
- &GLSLDecompiler::MemoryBarrierGL,
+ &GLSLDecompiler::Barrier,
+ &GLSLDecompiler::MemoryBarrierGroup,
+ &GLSLDecompiler::MemoryBarrierGlobal,
};
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
@@ -2596,11 +2753,11 @@ private:
}
std::string GetSampler(const Sampler& sampler) const {
- return AppendSuffix(static_cast<u32>(sampler.GetIndex()), "sampler");
+ return AppendSuffix(sampler.index, "sampler");
}
std::string GetImage(const Image& image) const {
- return AppendSuffix(static_cast<u32>(image.GetIndex()), "image");
+ return AppendSuffix(image.index, "image");
}
std::string AppendSuffix(u32 index, std::string_view name) const {
@@ -2623,15 +2780,6 @@ private:
return std::min<u32>(device.GetMaxVaryings(), Maxwell::NumVaryings);
}
- bool IsRenderTargetEnabled(u32 render_target) const {
- for (u32 component = 0; component < 4; ++component) {
- if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
- return true;
- }
- }
- return false;
- }
-
const Device& device;
const ShaderIR& ir;
const Registry& registry;
@@ -2639,6 +2787,7 @@ private:
const std::string_view identifier;
const std::string_view suffix;
const Header header;
+ const bool use_unified_uniforms;
std::unordered_map<u8, VaryingTFB> transform_feedback;
ShaderWriter code;
@@ -2834,7 +2983,7 @@ void GLSLDecompiler::DecompileAST() {
} // Anonymous namespace
-ShaderEntries MakeEntries(const VideoCommon::Shader::ShaderIR& ir) {
+ShaderEntries MakeEntries(const Device& device, const ShaderIR& ir, ShaderType stage) {
ShaderEntries entries;
for (const auto& cbuf : ir.GetConstantBuffers()) {
entries.const_buffers.emplace_back(cbuf.second.GetMaxOffset(), cbuf.second.IsIndirect(),
@@ -2855,6 +3004,7 @@ ShaderEntries MakeEntries(const VideoCommon::Shader::ShaderIR& ir) {
entries.clip_distances = (clip_distances[i] ? 1U : 0U) << i;
}
entries.shader_length = ir.GetLength();
+ entries.use_unified_uniforms = UseUnifiedUniforms(device, ir, stage);
return entries;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index e7dbd810c..451c9689a 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -33,36 +33,19 @@ public:
}
private:
- u32 index{};
+ u32 index = 0;
};
-class GlobalMemoryEntry {
-public:
- explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, bool is_read, bool is_written)
+struct GlobalMemoryEntry {
+ constexpr explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, bool is_read,
+ bool is_written)
: cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_read{is_read}, is_written{
is_written} {}
- u32 GetCbufIndex() const {
- return cbuf_index;
- }
-
- u32 GetCbufOffset() const {
- return cbuf_offset;
- }
-
- bool IsRead() const {
- return is_read;
- }
-
- bool IsWritten() const {
- return is_written;
- }
-
-private:
- u32 cbuf_index{};
- u32 cbuf_offset{};
- bool is_read{};
- bool is_written{};
+ u32 cbuf_index = 0;
+ u32 cbuf_offset = 0;
+ bool is_read = false;
+ bool is_written = false;
};
struct ShaderEntries {
@@ -70,11 +53,13 @@ struct ShaderEntries {
std::vector<GlobalMemoryEntry> global_memory_entries;
std::vector<SamplerEntry> samplers;
std::vector<ImageEntry> images;
- u32 clip_distances{};
std::size_t shader_length{};
+ u32 clip_distances{};
+ bool use_unified_uniforms{};
};
-ShaderEntries MakeEntries(const VideoCommon::Shader::ShaderIR& ir);
+ShaderEntries MakeEntries(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
+ Tegra::Engines::ShaderType stage);
std::string DecompileShader(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
const VideoCommon::Shader::Registry& registry,
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 9e95a122b..70dd0c3c6 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -29,6 +29,8 @@ using VideoCommon::Shader::KeyMap;
namespace {
+using VideoCommon::Shader::SeparateSamplerKey;
+
using ShaderCacheVersionHash = std::array<u8, 64>;
struct ConstBufferKey {
@@ -37,18 +39,26 @@ struct ConstBufferKey {
u32 value = 0;
};
-struct BoundSamplerKey {
+struct BoundSamplerEntry {
u32 offset = 0;
Tegra::Engines::SamplerDescriptor sampler;
};
-struct BindlessSamplerKey {
+struct SeparateSamplerEntry {
+ u32 cbuf1 = 0;
+ u32 cbuf2 = 0;
+ u32 offset1 = 0;
+ u32 offset2 = 0;
+ Tegra::Engines::SamplerDescriptor sampler;
+};
+
+struct BindlessSamplerEntry {
u32 cbuf = 0;
u32 offset = 0;
Tegra::Engines::SamplerDescriptor sampler;
};
-constexpr u32 NativeVersion = 20;
+constexpr u32 NativeVersion = 21;
ShaderCacheVersionHash GetShaderCacheVersionHash() {
ShaderCacheVersionHash hash{};
@@ -63,7 +73,7 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default;
ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default;
-bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
+bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) {
return false;
}
@@ -87,12 +97,14 @@ bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
u32 texture_handler_size_value;
u32 num_keys;
u32 num_bound_samplers;
+ u32 num_separate_samplers;
u32 num_bindless_samplers;
if (file.ReadArray(&unique_identifier, 1) != 1 || file.ReadArray(&bound_buffer, 1) != 1 ||
file.ReadArray(&is_texture_handler_size_known, 1) != 1 ||
file.ReadArray(&texture_handler_size_value, 1) != 1 ||
file.ReadArray(&graphics_info, 1) != 1 || file.ReadArray(&compute_info, 1) != 1 ||
file.ReadArray(&num_keys, 1) != 1 || file.ReadArray(&num_bound_samplers, 1) != 1 ||
+ file.ReadArray(&num_separate_samplers, 1) != 1 ||
file.ReadArray(&num_bindless_samplers, 1) != 1) {
return false;
}
@@ -101,29 +113,38 @@ bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
}
std::vector<ConstBufferKey> flat_keys(num_keys);
- std::vector<BoundSamplerKey> flat_bound_samplers(num_bound_samplers);
- std::vector<BindlessSamplerKey> flat_bindless_samplers(num_bindless_samplers);
+ std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers);
+ std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers);
+ std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers);
if (file.ReadArray(flat_keys.data(), flat_keys.size()) != flat_keys.size() ||
file.ReadArray(flat_bound_samplers.data(), flat_bound_samplers.size()) !=
flat_bound_samplers.size() ||
+ file.ReadArray(flat_separate_samplers.data(), flat_separate_samplers.size()) !=
+ flat_separate_samplers.size() ||
file.ReadArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) !=
flat_bindless_samplers.size()) {
return false;
}
- for (const auto& key : flat_keys) {
- keys.insert({{key.cbuf, key.offset}, key.value});
+ for (const auto& entry : flat_keys) {
+ keys.insert({{entry.cbuf, entry.offset}, entry.value});
+ }
+ for (const auto& entry : flat_bound_samplers) {
+ bound_samplers.emplace(entry.offset, entry.sampler);
}
- for (const auto& key : flat_bound_samplers) {
- bound_samplers.emplace(key.offset, key.sampler);
+ for (const auto& entry : flat_separate_samplers) {
+ SeparateSamplerKey key;
+ key.buffers = {entry.cbuf1, entry.cbuf2};
+ key.offsets = {entry.offset1, entry.offset2};
+ separate_samplers.emplace(key, entry.sampler);
}
- for (const auto& key : flat_bindless_samplers) {
- bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler});
+ for (const auto& entry : flat_bindless_samplers) {
+ bindless_samplers.insert({{entry.cbuf, entry.offset}, entry.sampler});
}
return true;
}
-bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const {
+bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
if (file.WriteObject(static_cast<u32>(type)) != 1 ||
file.WriteObject(static_cast<u32>(code.size())) != 1 ||
file.WriteObject(static_cast<u32>(code_b.size())) != 1) {
@@ -142,6 +163,7 @@ bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const {
file.WriteObject(graphics_info) != 1 || file.WriteObject(compute_info) != 1 ||
file.WriteObject(static_cast<u32>(keys.size())) != 1 ||
file.WriteObject(static_cast<u32>(bound_samplers.size())) != 1 ||
+ file.WriteObject(static_cast<u32>(separate_samplers.size())) != 1 ||
file.WriteObject(static_cast<u32>(bindless_samplers.size())) != 1) {
return false;
}
@@ -152,48 +174,64 @@ bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const {
flat_keys.push_back(ConstBufferKey{address.first, address.second, value});
}
- std::vector<BoundSamplerKey> flat_bound_samplers;
+ std::vector<BoundSamplerEntry> flat_bound_samplers;
flat_bound_samplers.reserve(bound_samplers.size());
for (const auto& [address, sampler] : bound_samplers) {
- flat_bound_samplers.push_back(BoundSamplerKey{address, sampler});
+ flat_bound_samplers.push_back(BoundSamplerEntry{address, sampler});
}
- std::vector<BindlessSamplerKey> flat_bindless_samplers;
+ std::vector<SeparateSamplerEntry> flat_separate_samplers;
+ flat_separate_samplers.reserve(separate_samplers.size());
+ for (const auto& [key, sampler] : separate_samplers) {
+ SeparateSamplerEntry entry;
+ std::tie(entry.cbuf1, entry.cbuf2) = key.buffers;
+ std::tie(entry.offset1, entry.offset2) = key.offsets;
+ entry.sampler = sampler;
+ flat_separate_samplers.push_back(entry);
+ }
+
+ std::vector<BindlessSamplerEntry> flat_bindless_samplers;
flat_bindless_samplers.reserve(bindless_samplers.size());
for (const auto& [address, sampler] : bindless_samplers) {
flat_bindless_samplers.push_back(
- BindlessSamplerKey{address.first, address.second, sampler});
+ BindlessSamplerEntry{address.first, address.second, sampler});
}
return file.WriteArray(flat_keys.data(), flat_keys.size()) == flat_keys.size() &&
file.WriteArray(flat_bound_samplers.data(), flat_bound_samplers.size()) ==
flat_bound_samplers.size() &&
+ file.WriteArray(flat_separate_samplers.data(), flat_separate_samplers.size()) ==
+ flat_separate_samplers.size() &&
file.WriteArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) ==
flat_bindless_samplers.size();
}
-ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
+ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default;
ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
+void ShaderDiskCacheOpenGL::BindTitleID(u64 title_id_) {
+ title_id = title_id_;
+}
+
std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() {
// Skip games without title id
- const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
- if (!Settings::values.use_disk_shader_cache || !has_title_id) {
- return {};
+ const bool has_title_id = title_id != 0;
+ if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
+ return std::nullopt;
}
- FileUtil::IOFile file(GetTransferablePath(), "rb");
+ Common::FS::IOFile file(GetTransferablePath(), "rb");
if (!file.IsOpen()) {
LOG_INFO(Render_OpenGL, "No transferable shader cache found");
is_usable = true;
- return {};
+ return std::nullopt;
}
u32 version{};
if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) {
LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it");
- return {};
+ return std::nullopt;
}
if (version < NativeVersion) {
@@ -201,12 +239,12 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
file.Close();
InvalidateTransferable();
is_usable = true;
- return {};
+ return std::nullopt;
}
if (version > NativeVersion) {
LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version "
"of the emulator, skipping");
- return {};
+ return std::nullopt;
}
// Version is valid, load the shaders
@@ -215,7 +253,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
ShaderDiskCacheEntry& entry = entries.emplace_back();
if (!entry.Load(file)) {
LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping");
- return {};
+ return std::nullopt;
}
}
@@ -228,7 +266,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
return {};
}
- FileUtil::IOFile file(GetPrecompiledPath(), "rb");
+ Common::FS::IOFile file(GetPrecompiledPath(), "rb");
if (!file.IsOpen()) {
LOG_INFO(Render_OpenGL, "No precompiled shader cache found");
return {};
@@ -245,7 +283,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
}
std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile(
- FileUtil::IOFile& file) {
+ Common::FS::IOFile& file) {
// Read compressed file from disk and decompress to virtual precompiled cache file
std::vector<u8> compressed(file.GetSize());
file.ReadBytes(compressed.data(), compressed.size());
@@ -256,12 +294,12 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
ShaderCacheVersionHash file_hash{};
if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
precompiled_cache_virtual_file_offset = 0;
- return {};
+ return std::nullopt;
}
if (GetShaderCacheVersionHash() != file_hash) {
LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
precompiled_cache_virtual_file_offset = 0;
- return {};
+ return std::nullopt;
}
std::vector<ShaderDiskCachePrecompiled> entries;
@@ -271,19 +309,19 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
if (!LoadObjectFromPrecompiled(entry.unique_identifier) ||
!LoadObjectFromPrecompiled(entry.binary_format) ||
!LoadObjectFromPrecompiled(binary_size)) {
- return {};
+ return std::nullopt;
}
entry.binary.resize(binary_size);
if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) {
- return {};
+ return std::nullopt;
}
}
return entries;
}
void ShaderDiskCacheOpenGL::InvalidateTransferable() {
- if (!FileUtil::Delete(GetTransferablePath())) {
+ if (!Common::FS::Delete(GetTransferablePath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
GetTransferablePath());
}
@@ -294,7 +332,7 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
// Clear virtaul precompiled cache file
precompiled_cache_virtual_file.Resize(0);
- if (!FileUtil::Delete(GetPrecompiledPath())) {
+ if (!Common::FS::Delete(GetPrecompiledPath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
}
}
@@ -310,7 +348,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) {
return;
}
- FileUtil::IOFile file = AppendTransferableFile();
+ Common::FS::IOFile file = AppendTransferableFile();
if (!file.IsOpen()) {
return;
}
@@ -352,15 +390,15 @@ void ShaderDiskCacheOpenGL::SavePrecompiled(u64 unique_identifier, GLuint progra
}
}
-FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
+Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
if (!EnsureDirectories()) {
return {};
}
const auto transferable_path{GetTransferablePath()};
- const bool existed = FileUtil::Exists(transferable_path);
+ const bool existed = Common::FS::Exists(transferable_path);
- FileUtil::IOFile file(transferable_path, "ab");
+ Common::FS::IOFile file(transferable_path, "ab");
if (!file.IsOpen()) {
LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path);
return {};
@@ -392,7 +430,7 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
const auto precompiled_path{GetPrecompiledPath()};
- FileUtil::IOFile file(precompiled_path, "wb");
+ Common::FS::IOFile file(precompiled_path, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
@@ -406,24 +444,24 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
const auto CreateDir = [](const std::string& dir) {
- if (!FileUtil::CreateDir(dir)) {
+ if (!Common::FS::CreateDir(dir)) {
LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir);
return false;
}
return true;
};
- return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
+ return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) &&
CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) &&
CreateDir(GetPrecompiledDir());
}
std::string ShaderDiskCacheOpenGL::GetTransferablePath() const {
- return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
+ return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
}
std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const {
- return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
+ return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
}
std::string ShaderDiskCacheOpenGL::GetTransferableDir() const {
@@ -435,11 +473,11 @@ std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const {
}
std::string ShaderDiskCacheOpenGL::GetBaseDir() const {
- return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl";
+ return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl";
}
std::string ShaderDiskCacheOpenGL::GetTitleID() const {
- return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID());
+ return fmt::format("{:016X}", title_id);
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index d5be52e40..aef841c1d 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -21,11 +21,7 @@
#include "video_core/engines/shader_type.h"
#include "video_core/shader/registry.h"
-namespace Core {
-class System;
-}
-
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
@@ -38,9 +34,9 @@ struct ShaderDiskCacheEntry {
ShaderDiskCacheEntry();
~ShaderDiskCacheEntry();
- bool Load(FileUtil::IOFile& file);
+ bool Load(Common::FS::IOFile& file);
- bool Save(FileUtil::IOFile& file) const;
+ bool Save(Common::FS::IOFile& file) const;
bool HasProgramA() const {
return !code.empty() && !code_b.empty();
@@ -57,6 +53,7 @@ struct ShaderDiskCacheEntry {
VideoCommon::Shader::ComputeInfo compute_info;
VideoCommon::Shader::KeyMap keys;
VideoCommon::Shader::BoundSamplerMap bound_samplers;
+ VideoCommon::Shader::SeparateSamplerMap separate_samplers;
VideoCommon::Shader::BindlessSamplerMap bindless_samplers;
};
@@ -69,9 +66,12 @@ struct ShaderDiskCachePrecompiled {
class ShaderDiskCacheOpenGL {
public:
- explicit ShaderDiskCacheOpenGL(Core::System& system);
+ explicit ShaderDiskCacheOpenGL();
~ShaderDiskCacheOpenGL();
+ /// Binds a title ID for all future operations.
+ void BindTitleID(u64 title_id);
+
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable();
@@ -96,10 +96,10 @@ public:
private:
/// Loads the transferable cache. Returns empty on failure.
std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile(
- FileUtil::IOFile& file);
+ Common::FS::IOFile& file);
/// Opens current game's transferable file and write it's header if it doesn't exist
- FileUtil::IOFile AppendTransferableFile() const;
+ Common::FS::IOFile AppendTransferableFile() const;
/// Save precompiled header to precompiled_cache_in_memory
void SavePrecompiledHeaderToVirtualPrecompiledCache();
@@ -156,8 +156,6 @@ private:
return LoadArrayFromPrecompiled(&object, 1);
}
- Core::System& system;
-
// Stores whole precompiled cache which will be read from or saved to the precompiled chache
// file
FileSys::VectorVfsFile precompiled_cache_virtual_file;
@@ -167,8 +165,11 @@ private:
// Stored transferable shaders
std::unordered_set<u64> stored_transferable;
+ /// Title ID to operate on
+ u64 title_id = 0;
+
// The cache has been loaded at boot
- bool is_usable{};
+ bool is_usable = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 9c7b0adbd..691c6c79b 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -6,47 +6,124 @@
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
-namespace OpenGL::GLShader {
+namespace OpenGL {
-ProgramManager::ProgramManager() = default;
+namespace {
+
+void BindProgram(GLenum stage, GLuint current, GLuint old, bool& enabled) {
+ if (current == old) {
+ return;
+ }
+ if (current == 0) {
+ if (enabled) {
+ enabled = false;
+ glDisable(stage);
+ }
+ return;
+ }
+ if (!enabled) {
+ enabled = true;
+ glEnable(stage);
+ }
+ glBindProgramARB(stage, current);
+}
+
+} // Anonymous namespace
+
+ProgramManager::ProgramManager(const Device& device)
+ : use_assembly_programs{device.UseAssemblyShaders()} {
+ if (use_assembly_programs) {
+ glEnable(GL_COMPUTE_PROGRAM_NV);
+ } else {
+ graphics_pipeline.Create();
+ glBindProgramPipeline(graphics_pipeline.handle);
+ }
+}
ProgramManager::~ProgramManager() = default;
-void ProgramManager::Create() {
- graphics_pipeline.Create();
- glBindProgramPipeline(graphics_pipeline.handle);
+void ProgramManager::BindCompute(GLuint program) {
+ if (use_assembly_programs) {
+ glBindProgramARB(GL_COMPUTE_PROGRAM_NV, program);
+ } else {
+ is_graphics_bound = false;
+ glUseProgram(program);
+ }
}
void ProgramManager::BindGraphicsPipeline() {
+ if (!use_assembly_programs) {
+ UpdateSourcePrograms();
+ }
+}
+
+void ProgramManager::BindHostPipeline(GLuint pipeline) {
+ if (use_assembly_programs) {
+ if (geometry_enabled) {
+ geometry_enabled = false;
+ old_state.geometry = 0;
+ glDisable(GL_GEOMETRY_PROGRAM_NV);
+ }
+ } else {
+ if (!is_graphics_bound) {
+ glUseProgram(0);
+ }
+ }
+ glBindProgramPipeline(pipeline);
+}
+
+void ProgramManager::RestoreGuestPipeline() {
+ if (use_assembly_programs) {
+ glBindProgramPipeline(0);
+ } else {
+ glBindProgramPipeline(graphics_pipeline.handle);
+ }
+}
+
+void ProgramManager::UseVertexShader(GLuint program) {
+ if (use_assembly_programs) {
+ BindProgram(GL_VERTEX_PROGRAM_NV, program, current_state.vertex, vertex_enabled);
+ }
+ current_state.vertex = program;
+}
+
+void ProgramManager::UseGeometryShader(GLuint program) {
+ if (use_assembly_programs) {
+ BindProgram(GL_GEOMETRY_PROGRAM_NV, program, current_state.vertex, geometry_enabled);
+ }
+ current_state.geometry = program;
+}
+
+void ProgramManager::UseFragmentShader(GLuint program) {
+ if (use_assembly_programs) {
+ BindProgram(GL_FRAGMENT_PROGRAM_NV, program, current_state.vertex, fragment_enabled);
+ }
+ current_state.fragment = program;
+}
+
+void ProgramManager::UpdateSourcePrograms() {
if (!is_graphics_bound) {
is_graphics_bound = true;
glUseProgram(0);
}
- // Avoid updating the pipeline when values have no changed
- if (old_state == current_state) {
- return;
- }
-
- // Workaround for AMD bug
- static constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
- GL_FRAGMENT_SHADER_BIT};
const GLuint handle = graphics_pipeline.handle;
- glUseProgramStages(handle, all_used_stages, 0);
- glUseProgramStages(handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
- glUseProgramStages(handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
- glUseProgramStages(handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
+ const auto update_state = [handle](GLenum stage, GLuint current, GLuint old) {
+ if (current == old) {
+ return;
+ }
+ glUseProgramStages(handle, stage, current);
+ };
+ update_state(GL_VERTEX_SHADER_BIT, current_state.vertex, old_state.vertex);
+ update_state(GL_GEOMETRY_SHADER_BIT, current_state.geometry, old_state.geometry);
+ update_state(GL_FRAGMENT_SHADER_BIT, current_state.fragment, old_state.fragment);
old_state = current_state;
}
-void ProgramManager::BindComputeShader(GLuint program) {
- is_graphics_bound = false;
- glUseProgram(program);
-}
-
void MaxwellUniformData::SetFromRegs(const Tegra::Engines::Maxwell3D& maxwell) {
const auto& regs = maxwell.regs;
@@ -54,4 +131,4 @@ void MaxwellUniformData::SetFromRegs(const Tegra::Engines::Maxwell3D& maxwell) {
y_direction = regs.screen_y_control.y_negate == 0 ? 1.0f : -1.0f;
}
-} // namespace OpenGL::GLShader
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index d2e47f2a9..950e0dfcb 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -11,7 +11,9 @@
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
-namespace OpenGL::GLShader {
+namespace OpenGL {
+
+class Device;
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
/// @note Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
@@ -28,50 +30,47 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
class ProgramManager {
public:
- explicit ProgramManager();
+ explicit ProgramManager(const Device& device);
~ProgramManager();
- void Create();
+ /// Binds a compute program
+ void BindCompute(GLuint program);
- /// Updates the graphics pipeline and binds it.
+ /// Updates bound programs.
void BindGraphicsPipeline();
- /// Binds a compute shader.
- void BindComputeShader(GLuint program);
-
- void UseVertexShader(GLuint program) {
- current_state.vertex_shader = program;
- }
+ /// Binds an OpenGL pipeline object unsynchronized with the guest state.
+ void BindHostPipeline(GLuint pipeline);
- void UseGeometryShader(GLuint program) {
- current_state.geometry_shader = program;
- }
+ /// Rewinds BindHostPipeline state changes.
+ void RestoreGuestPipeline();
- void UseFragmentShader(GLuint program) {
- current_state.fragment_shader = program;
- }
+ void UseVertexShader(GLuint program);
+ void UseGeometryShader(GLuint program);
+ void UseFragmentShader(GLuint program);
private:
struct PipelineState {
- bool operator==(const PipelineState& rhs) const noexcept {
- return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
- geometry_shader == rhs.geometry_shader;
- }
-
- bool operator!=(const PipelineState& rhs) const noexcept {
- return !operator==(rhs);
- }
-
- GLuint vertex_shader = 0;
- GLuint fragment_shader = 0;
- GLuint geometry_shader = 0;
+ GLuint vertex = 0;
+ GLuint geometry = 0;
+ GLuint fragment = 0;
};
+ /// Update GLSL programs.
+ void UpdateSourcePrograms();
+
OGLPipeline graphics_pipeline;
- OGLPipeline compute_pipeline;
+
PipelineState current_state;
PipelineState old_state;
+
+ bool use_assembly_programs = false;
+
bool is_graphics_bound = true;
+
+ bool vertex_enabled = false;
+ bool geometry_enabled = false;
+ bool fragment_enabled = false;
};
-} // namespace OpenGL::GLShader
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 9e74eda0d..4bf0d6090 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
#include <vector>
#include <glad/glad.h>
#include "common/assert.h"
@@ -11,7 +12,8 @@
namespace OpenGL::GLShader {
namespace {
-const char* GetStageDebugName(GLenum type) {
+
+std::string_view StageDebugName(GLenum type) {
switch (type) {
case GL_VERTEX_SHADER:
return "vertex";
@@ -25,12 +27,17 @@ const char* GetStageDebugName(GLenum type) {
UNIMPLEMENTED();
return "unknown";
}
+
} // Anonymous namespace
-GLuint LoadShader(const char* source, GLenum type) {
- const char* debug_type = GetStageDebugName(type);
+GLuint LoadShader(std::string_view source, GLenum type) {
+ const std::string_view debug_type = StageDebugName(type);
const GLuint shader_id = glCreateShader(type);
- glShaderSource(shader_id, 1, &source, nullptr);
+
+ const GLchar* source_string = source.data();
+ const GLint source_length = static_cast<GLint>(source.size());
+
+ glShaderSource(shader_id, 1, &source_string, &source_length);
LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
glCompileShader(shader_id);
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 03b7548c2..1b770532e 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -38,7 +38,7 @@ void LogShaderSource(T... shaders) {
* @param source String of the GLSL shader program
* @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER)
*/
-GLuint LoadShader(const char* source, GLenum type);
+GLuint LoadShader(std::string_view source, GLenum type);
/**
* Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp
index d24fad3de..6bcf831f2 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.cpp
+++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp
@@ -214,10 +214,8 @@ void SetupDirtyMisc(Tables& tables) {
} // Anonymous namespace
-StateTracker::StateTracker(Core::System& system) : system{system} {}
-
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
+StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} {
+ auto& dirty = gpu.Maxwell3D().dirty;
auto& tables = dirty.tables;
SetupDirtyRenderTargets(tables);
SetupDirtyColorMasks(tables);
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h
index 0f823288e..9d127548f 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.h
+++ b/src/video_core/renderer_opengl/gl_state_tracker.h
@@ -13,8 +13,8 @@
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
-namespace Core {
-class System;
+namespace Tegra {
+class GPU;
}
namespace OpenGL {
@@ -90,9 +90,7 @@ static_assert(Last <= std::numeric_limits<u8>::max());
class StateTracker {
public:
- explicit StateTracker(Core::System& system);
-
- void Initialize();
+ explicit StateTracker(Tegra::GPU& gpu);
void BindIndexBuffer(GLuint new_index_buffer) {
if (index_buffer == new_index_buffer) {
@@ -103,7 +101,6 @@ public:
}
void NotifyScreenDrawVertexArray() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::VertexFormats] = true;
flags[OpenGL::Dirty::VertexFormat0 + 0] = true;
flags[OpenGL::Dirty::VertexFormat0 + 1] = true;
@@ -117,98 +114,81 @@ public:
}
void NotifyPolygonModes() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonModes] = true;
flags[OpenGL::Dirty::PolygonModeFront] = true;
flags[OpenGL::Dirty::PolygonModeBack] = true;
}
void NotifyViewport0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Viewports] = true;
flags[OpenGL::Dirty::Viewport0] = true;
}
void NotifyScissor0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Scissors] = true;
flags[OpenGL::Dirty::Scissor0] = true;
}
void NotifyColorMask0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::ColorMasks] = true;
flags[OpenGL::Dirty::ColorMask0] = true;
}
void NotifyBlend0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::BlendStates] = true;
flags[OpenGL::Dirty::BlendState0] = true;
}
void NotifyFramebuffer() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[VideoCommon::Dirty::RenderTargets] = true;
}
void NotifyFrontFace() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FrontFace] = true;
}
void NotifyCullTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::CullTest] = true;
}
void NotifyDepthMask() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthMask] = true;
}
void NotifyDepthTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthTest] = true;
}
void NotifyStencilTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::StencilTest] = true;
}
void NotifyPolygonOffset() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonOffset] = true;
}
void NotifyRasterizeEnable() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::RasterizeEnable] = true;
}
void NotifyFramebufferSRGB() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FramebufferSRGB] = true;
}
void NotifyLogicOp() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::LogicOp] = true;
}
void NotifyClipControl() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::ClipControl] = true;
}
void NotifyAlphaTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::AlphaTest] = true;
}
private:
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
GLuint index_buffer = 0;
};
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
index 6ec328c53..887995cf4 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -2,11 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <deque>
+#include <tuple>
#include <vector>
+
#include "common/alignment.h"
#include "common/assert.h"
#include "common/microprofile.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
@@ -14,8 +16,7 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
namespace OpenGL {
-OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent,
- bool use_persistent)
+OGLStreamBuffer::OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage)
: buffer_size(size) {
gl_buffer.Create();
@@ -29,34 +30,22 @@ OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool p
allocate_size *= 2;
}
- if (use_persistent) {
- persistent = true;
- coherent = prefer_coherent;
- const GLbitfield flags =
- GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0);
- glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags);
- mapped_ptr = static_cast<u8*>(glMapNamedBufferRange(
- gl_buffer.handle, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT)));
- } else {
- glNamedBufferData(gl_buffer.handle, allocate_size, nullptr, GL_STREAM_DRAW);
+ static constexpr GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
+ glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags);
+ mapped_ptr = static_cast<u8*>(
+ glMapNamedBufferRange(gl_buffer.handle, 0, buffer_size, flags | GL_MAP_FLUSH_EXPLICIT_BIT));
+
+ if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) {
+ glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_ONLY);
+ glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address);
}
}
OGLStreamBuffer::~OGLStreamBuffer() {
- if (persistent) {
- glUnmapNamedBuffer(gl_buffer.handle);
- }
+ glUnmapNamedBuffer(gl_buffer.handle);
gl_buffer.Release();
}
-GLuint OGLStreamBuffer::GetHandle() const {
- return gl_buffer.handle;
-}
-
-GLsizeiptr OGLStreamBuffer::GetSize() const {
- return buffer_size;
-}
-
std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr alignment) {
ASSERT(size <= buffer_size);
ASSERT(alignment <= buffer_size);
@@ -68,36 +57,21 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a
bool invalidate = false;
if (buffer_pos + size > buffer_size) {
+ MICROPROFILE_SCOPE(OpenGL_StreamBuffer);
+ glInvalidateBufferData(gl_buffer.handle);
+
buffer_pos = 0;
invalidate = true;
-
- if (persistent) {
- glUnmapNamedBuffer(gl_buffer.handle);
- }
}
- if (invalidate || !persistent) {
- MICROPROFILE_SCOPE(OpenGL_StreamBuffer);
- GLbitfield flags = GL_MAP_WRITE_BIT | (persistent ? GL_MAP_PERSISTENT_BIT : 0) |
- (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) |
- (invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT);
- mapped_ptr = static_cast<u8*>(
- glMapNamedBufferRange(gl_buffer.handle, buffer_pos, buffer_size - buffer_pos, flags));
- mapped_offset = buffer_pos;
- }
-
- return std::make_tuple(mapped_ptr + buffer_pos - mapped_offset, buffer_pos, invalidate);
+ return std::make_tuple(mapped_ptr + buffer_pos, buffer_pos, invalidate);
}
void OGLStreamBuffer::Unmap(GLsizeiptr size) {
ASSERT(size <= mapped_size);
- if (!coherent && size > 0) {
- glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos - mapped_offset, size);
- }
-
- if (!persistent) {
- glUnmapNamedBuffer(gl_buffer.handle);
+ if (size > 0) {
+ glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos, size);
}
buffer_pos += size;
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h
index f8383cbd4..307a67113 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.h
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.h
@@ -11,15 +11,13 @@
namespace OpenGL {
+class Device;
+
class OGLStreamBuffer : private NonCopyable {
public:
- explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false,
- bool use_persistent = true);
+ explicit OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage);
~OGLStreamBuffer();
- GLuint GetHandle() const;
- GLsizeiptr GetSize() const;
-
/*
* Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes
* and the optional alignment requirement.
@@ -32,15 +30,24 @@ public:
void Unmap(GLsizeiptr size);
+ GLuint Handle() const {
+ return gl_buffer.handle;
+ }
+
+ u64 Address() const {
+ return gpu_address;
+ }
+
+ GLsizeiptr Size() const noexcept {
+ return buffer_size;
+ }
+
private:
OGLBuffer gl_buffer;
- bool coherent = false;
- bool persistent = false;
-
+ GLuint64EXT gpu_address = 0;
GLintptr buffer_pos = 0;
GLsizeiptr buffer_size = 0;
- GLintptr mapped_offset = 0;
GLsizeiptr mapped_size = 0;
u8* mapped_ptr = nullptr;
};
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 2729d1265..a863ef218 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -35,96 +35,109 @@ MICROPROFILE_DEFINE(OpenGL_Texture_Buffer_Copy, "OpenGL", "Texture Buffer Copy",
namespace {
struct FormatTuple {
- GLint internal_format;
+ GLenum internal_format;
GLenum format = GL_NONE;
GLenum type = GL_NONE;
};
constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
- {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // ABGR8U
- {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // ABGR8S
- {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, // ABGR8UI
- {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, // B5G6R5U
- {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10U
- {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5U
- {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8U
- {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, // R8UI
- {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16F
- {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // RGBA16U
- {GL_RGBA16_SNORM, GL_RGBA, GL_SHORT}, // RGBA16S
- {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, // RGBA16UI
- {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, // R11FG11FB10F
- {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, // RGBA32UI
- {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}, // DXT1
- {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}, // DXT23
- {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}, // DXT45
- {GL_COMPRESSED_RED_RGTC1}, // DXN1
- {GL_COMPRESSED_RG_RGTC2}, // DXN2UNORM
- {GL_COMPRESSED_SIGNED_RG_RGTC2}, // DXN2SNORM
- {GL_COMPRESSED_RGBA_BPTC_UNORM}, // BC7U
- {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UF16
- {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SF16
- {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4
- {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
- {GL_RGBA32F, GL_RGBA, GL_FLOAT}, // RGBA32F
- {GL_RG32F, GL_RG, GL_FLOAT}, // RG32F
- {GL_R32F, GL_RED, GL_FLOAT}, // R32F
- {GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F
- {GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16U
- {GL_R16_SNORM, GL_RED, GL_SHORT}, // R16S
- {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16UI
- {GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I
- {GL_RG16, GL_RG, GL_UNSIGNED_SHORT}, // RG16
- {GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F
- {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, // RG16UI
- {GL_RG16I, GL_RG_INTEGER, GL_SHORT}, // RG16I
- {GL_RG16_SNORM, GL_RG, GL_SHORT}, // RG16S
- {GL_RGB32F, GL_RGB, GL_FLOAT}, // RGB32F
- {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // RGBA8_SRGB
- {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // RG8U
- {GL_RG8_SNORM, GL_RG, GL_BYTE}, // RG8S
- {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // RG32UI
- {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT}, // RGBX16F
- {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32UI
- {GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I
- {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8
- {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5
- {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4
- {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_UNORM
+ {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // A8B8G8R8_SNORM
+ {GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE}, // A8B8G8R8_SINT
+ {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, // A8B8G8R8_UINT
+ {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // R5G6B5_UNORM
+ {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, // B5G6R5_UNORM
+ {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1R5G5B5_UNORM
+ {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM
+ {GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT
+ {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM
+ {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM
+ {GL_R8_SNORM, GL_RED, GL_BYTE}, // R8_SNORM
+ {GL_R8I, GL_RED_INTEGER, GL_BYTE}, // R8_SINT
+ {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, // R8_UINT
+ {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16A16_FLOAT
+ {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // R16G16B16A16_UNORM
+ {GL_RGBA16_SNORM, GL_RGBA, GL_SHORT}, // R16G16B16A16_SNORM
+ {GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT}, // R16G16B16A16_SINT
+ {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, // R16G16B16A16_UINT
+ {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, // B10G11R11_FLOAT
+ {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, // R32G32B32A32_UINT
+ {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}, // BC1_RGBA_UNORM
+ {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}, // BC2_UNORM
+ {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}, // BC3_UNORM
+ {GL_COMPRESSED_RED_RGTC1}, // BC4_UNORM
+ {GL_COMPRESSED_SIGNED_RED_RGTC1}, // BC4_SNORM
+ {GL_COMPRESSED_RG_RGTC2}, // BC5_UNORM
+ {GL_COMPRESSED_SIGNED_RG_RGTC2}, // BC5_SNORM
+ {GL_COMPRESSED_RGBA_BPTC_UNORM}, // BC7_UNORM
+ {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT
+ {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT
+ {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM
+ {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
+ {GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT
+ {GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT
+ {GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT
+ {GL_RG32I, GL_RG_INTEGER, GL_INT}, // R32G32_SINT
+ {GL_R32F, GL_RED, GL_FLOAT}, // R32_FLOAT
+ {GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16_FLOAT
+ {GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16_UNORM
+ {GL_R16_SNORM, GL_RED, GL_SHORT}, // R16_SNORM
+ {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16_UINT
+ {GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16_SINT
+ {GL_RG16, GL_RG, GL_UNSIGNED_SHORT}, // R16G16_UNORM
+ {GL_RG16F, GL_RG, GL_HALF_FLOAT}, // R16G16_FLOAT
+ {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, // R16G16_UINT
+ {GL_RG16I, GL_RG_INTEGER, GL_SHORT}, // R16G16_SINT
+ {GL_RG16_SNORM, GL_RG, GL_SHORT}, // R16G16_SNORM
+ {GL_RGB32F, GL_RGB, GL_FLOAT}, // R32G32B32_FLOAT
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_SRGB
+ {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // R8G8_UNORM
+ {GL_RG8_SNORM, GL_RG, GL_BYTE}, // R8G8_SNORM
+ {GL_RG8I, GL_RG_INTEGER, GL_BYTE}, // R8G8_SINT
+ {GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, // R8G8_UINT
+ {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // R32G32_UINT
+ {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16X16_FLOAT
+ {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32_UINT
+ {GL_R32I, GL_RED_INTEGER, GL_INT}, // R32_SINT
+ {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM
+ {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM
+ {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM
+ {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
// Compressed sRGB formats
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // DXT1_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // DXT23_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // DXT45_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7U_SRGB
- {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // R4G4B4A4U
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB
+ {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB
- {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5
+ {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB
- {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8
+ {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB
- {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6
+ {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
- {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10
+ {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
- {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12
+ {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
- {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6
+ {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB
- {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5
+ {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB
- {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9F
+ {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
// Depth formats
- {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // Z32F
- {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // Z16
+ {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
+ {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
// DepthStencil formats
- {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // Z24S8
- {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8Z24
- {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // Z32FS8
+ {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
+ {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
+ {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL,
+ GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // D32_FLOAT_S8_UINT
}};
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
@@ -177,10 +190,10 @@ GLint GetSwizzleSource(SwizzleSource source) {
GLenum GetComponent(PixelFormat format, bool is_first) {
switch (format) {
- case PixelFormat::Z24S8:
- case PixelFormat::Z32FS8:
+ case PixelFormat::D24_UNORM_S8_UINT:
+ case PixelFormat::D32_FLOAT_S8_UINT:
return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
- case PixelFormat::S8Z24:
+ case PixelFormat::S8_UINT_D24_UNORM:
return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
default:
UNREACHABLE();
@@ -237,6 +250,12 @@ OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum inte
return texture;
}
+constexpr u32 EncodeSwizzle(SwizzleSource x_source, SwizzleSource y_source, SwizzleSource z_source,
+ SwizzleSource w_source) {
+ return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
+ (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
+}
+
} // Anonymous namespace
CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params,
@@ -256,9 +275,14 @@ CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& param
target = GetTextureTarget(params.target);
texture = CreateTexture(params, target, internal_format, texture_buffer);
DecorateSurfaceName();
- main_view = CreateViewInner(
- ViewParams(params.target, 0, params.is_layered ? params.depth : 1, 0, params.num_levels),
- true);
+
+ u32 num_layers = 1;
+ if (params.is_layered || params.target == SurfaceTarget::Texture3D) {
+ num_layers = params.depth;
+ }
+
+ main_view =
+ CreateViewInner(ViewParams(params.target, 0, num_layers, 0, params.num_levels), true);
}
CachedSurface::~CachedSurface() = default;
@@ -379,8 +403,8 @@ void CachedSurface::DecorateSurfaceName() {
LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName());
}
-void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) {
- LabelGLObject(GL_TEXTURE, texture_view.handle, gpu_addr, prefix);
+void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) {
+ LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix);
}
View CachedSurface::CreateView(const ViewParams& view_key) {
@@ -396,32 +420,33 @@ View CachedSurface::CreateViewInner(const ViewParams& view_key, const bool is_pr
}
CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& params,
- const bool is_proxy)
- : VideoCommon::ViewBase(params), surface{surface}, is_proxy{is_proxy} {
- target = GetTextureTarget(params.target);
- format = GetFormatTuple(surface.GetSurfaceParams().pixel_format).internal_format;
+ bool is_proxy)
+ : VideoCommon::ViewBase(params), surface{surface}, format{surface.internal_format},
+ target{GetTextureTarget(params.target)}, is_proxy{is_proxy} {
if (!is_proxy) {
- texture_view = CreateTextureView();
+ main_view = CreateTextureView();
}
- swizzle = EncodeSwizzle(SwizzleSource::R, SwizzleSource::G, SwizzleSource::B, SwizzleSource::A);
}
CachedSurfaceView::~CachedSurfaceView() = default;
-void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const {
+void CachedSurfaceView::Attach(GLenum attachment, GLenum fb_target) const {
ASSERT(params.num_levels == 1);
+ if (params.target == SurfaceTarget::Texture3D) {
+ if (params.num_layers > 1) {
+ ASSERT(params.base_layer == 0);
+ glFramebufferTexture(fb_target, attachment, surface.texture.handle, params.base_level);
+ } else {
+ glFramebufferTexture3D(fb_target, attachment, target, surface.texture.handle,
+ params.base_level, params.base_layer);
+ }
+ return;
+ }
+
if (params.num_layers > 1) {
- // Layered framebuffer attachments
UNIMPLEMENTED_IF(params.base_layer != 0);
-
- switch (params.target) {
- case SurfaceTarget::Texture2DArray:
- glFramebufferTexture(target, attachment, GetTexture(), 0);
- break;
- default:
- UNIMPLEMENTED();
- }
+ glFramebufferTexture(fb_target, attachment, GetTexture(), 0);
return;
}
@@ -429,16 +454,16 @@ void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const {
const GLuint texture = surface.GetTexture();
switch (surface.GetSurfaceParams().target) {
case SurfaceTarget::Texture1D:
- glFramebufferTexture1D(target, attachment, view_target, texture, params.base_level);
+ glFramebufferTexture1D(fb_target, attachment, view_target, texture, params.base_level);
break;
case SurfaceTarget::Texture2D:
- glFramebufferTexture2D(target, attachment, view_target, texture, params.base_level);
+ glFramebufferTexture2D(fb_target, attachment, view_target, texture, params.base_level);
break;
case SurfaceTarget::Texture1DArray:
case SurfaceTarget::Texture2DArray:
case SurfaceTarget::TextureCubemap:
case SurfaceTarget::TextureCubeArray:
- glFramebufferTextureLayer(target, attachment, texture, params.base_level,
+ glFramebufferTextureLayer(fb_target, attachment, texture, params.base_level,
params.base_layer);
break;
default:
@@ -446,44 +471,73 @@ void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const {
}
}
-void CachedSurfaceView::ApplySwizzle(SwizzleSource x_source, SwizzleSource y_source,
+GLuint CachedSurfaceView::GetTexture(SwizzleSource x_source, SwizzleSource y_source,
SwizzleSource z_source, SwizzleSource w_source) {
- u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
- if (new_swizzle == swizzle)
- return;
- swizzle = new_swizzle;
- const std::array gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source),
- GetSwizzleSource(z_source), GetSwizzleSource(w_source)};
- const GLuint handle = GetTexture();
- const PixelFormat format = surface.GetSurfaceParams().pixel_format;
- switch (format) {
- case PixelFormat::Z24S8:
- case PixelFormat::Z32FS8:
- case PixelFormat::S8Z24:
- glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
+ if (GetSurfaceParams().IsBuffer()) {
+ return GetTexture();
+ }
+ const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
+ if (current_swizzle == new_swizzle) {
+ return current_view;
+ }
+ current_swizzle = new_swizzle;
+
+ const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle);
+ OGLTextureView& view = entry->second;
+ if (!is_cache_miss) {
+ current_view = view.handle;
+ return view.handle;
+ }
+ view = CreateTextureView();
+ current_view = view.handle;
+
+ std::array swizzle{x_source, y_source, z_source, w_source};
+
+ switch (const PixelFormat format = GetSurfaceParams().pixel_format) {
+ case PixelFormat::D24_UNORM_S8_UINT:
+ case PixelFormat::D32_FLOAT_S8_UINT:
+ case PixelFormat::S8_UINT_D24_UNORM:
+ UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G);
+ glTextureParameteri(view.handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
GetComponent(format, x_source == SwizzleSource::R));
- break;
- default:
- glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
+
+ // Make sure we sample the first component
+ std::transform(swizzle.begin(), swizzle.end(), swizzle.begin(), [](SwizzleSource value) {
+ return value == SwizzleSource::G ? SwizzleSource::R : value;
+ });
+ [[fallthrough]];
+ default: {
+ const std::array gl_swizzle = {GetSwizzleSource(swizzle[0]), GetSwizzleSource(swizzle[1]),
+ GetSwizzleSource(swizzle[2]), GetSwizzleSource(swizzle[3])};
+ glTextureParameteriv(view.handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
break;
}
+ }
+ return view.handle;
}
OGLTextureView CachedSurfaceView::CreateTextureView() const {
OGLTextureView texture_view;
texture_view.Create();
- glTextureView(texture_view.handle, target, surface.texture.handle, format, params.base_level,
- params.num_levels, params.base_layer, params.num_layers);
+ if (target == GL_TEXTURE_3D) {
+ glTextureView(texture_view.handle, target, surface.texture.handle, format,
+ params.base_level, params.num_levels, 0, 1);
+ } else {
+ glTextureView(texture_view.handle, target, surface.texture.handle, format,
+ params.base_level, params.num_levels, params.base_layer, params.num_layers);
+ }
ApplyTextureDefaults(surface.GetSurfaceParams(), texture_view.handle);
return texture_view;
}
-TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system,
- VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker)
- : TextureCacheBase{system, rasterizer, device.HasASTC()}, state_tracker{state_tracker} {
+TextureCacheOpenGL::TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const Device& device,
+ StateTracker& state_tracker_)
+ : TextureCacheBase{rasterizer, maxwell3d, gpu_memory, device.HasASTC()}, state_tracker{
+ state_tracker_} {
src_framebuffer.Create();
dst_framebuffer.Create();
}
@@ -517,8 +571,8 @@ void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view,
const Tegra::Engines::Fermi2D::Config& copy_config) {
const auto& src_params{src_view->GetSurfaceParams()};
const auto& dst_params{dst_view->GetSurfaceParams()};
- UNIMPLEMENTED_IF(src_params.target == SurfaceTarget::Texture3D);
- UNIMPLEMENTED_IF(dst_params.target == SurfaceTarget::Texture3D);
+ UNIMPLEMENTED_IF(src_params.depth != 1);
+ UNIMPLEMENTED_IF(dst_params.depth != 1);
state_tracker.NotifyScissor0();
state_tracker.NotifyFramebuffer();
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 02d9981a1..7787134fc 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -80,15 +80,17 @@ public:
explicit CachedSurfaceView(CachedSurface& surface, const ViewParams& params, bool is_proxy);
~CachedSurfaceView();
- /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER
- void Attach(GLenum attachment, GLenum target) const;
+ /// @brief Attaches this texture view to the currently bound fb_target framebuffer
+ /// @param attachment Attachment to bind textures to
+ /// @param fb_target Framebuffer target to attach to (e.g. DRAW_FRAMEBUFFER)
+ void Attach(GLenum attachment, GLenum fb_target) const;
- void ApplySwizzle(Tegra::Texture::SwizzleSource x_source,
+ GLuint GetTexture(Tegra::Texture::SwizzleSource x_source,
Tegra::Texture::SwizzleSource y_source,
Tegra::Texture::SwizzleSource z_source,
Tegra::Texture::SwizzleSource w_source);
- void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
+ void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix);
void MarkAsModified(u64 tick) {
surface.MarkAsModified(true, tick);
@@ -98,7 +100,7 @@ public:
if (is_proxy) {
return surface.GetTexture();
}
- return texture_view.handle;
+ return main_view.handle;
}
GLenum GetFormat() const {
@@ -110,29 +112,27 @@ public:
}
private:
- u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source,
- Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source,
- Tegra::Texture::SwizzleSource w_source) const {
- return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
- (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
- }
-
OGLTextureView CreateTextureView() const;
CachedSurface& surface;
- GLenum target{};
- GLenum format{};
+ const GLenum format;
+ const GLenum target;
+ const bool is_proxy;
+
+ std::unordered_map<u32, OGLTextureView> view_cache;
+ OGLTextureView main_view;
- OGLTextureView texture_view;
- u32 swizzle{};
- bool is_proxy{};
+ // Use an invalid default so it always fails the comparison test
+ u32 current_swizzle = 0xffffffff;
+ GLuint current_view = 0;
};
class TextureCacheOpenGL final : public TextureCacheBase {
public:
- explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker);
+ explicit TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const Device& device,
+ StateTracker& state_tracker);
~TextureCacheOpenGL();
protected:
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index 89f0e04ef..a8be2aa37 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -24,10 +24,11 @@ namespace MaxwellToGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
+inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
switch (attrib.type) {
- case Maxwell::VertexAttribute::Type::UnsignedInt:
case Maxwell::VertexAttribute::Type::UnsignedNorm:
+ case Maxwell::VertexAttribute::Type::UnsignedScaled:
+ case Maxwell::VertexAttribute::Type::UnsignedInt:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_8:
case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -47,11 +48,12 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_UNSIGNED_INT_2_10_10_10_REV;
default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- return {};
+ break;
}
- case Maxwell::VertexAttribute::Type::SignedInt:
+ break;
case Maxwell::VertexAttribute::Type::SignedNorm:
+ case Maxwell::VertexAttribute::Type::SignedScaled:
+ case Maxwell::VertexAttribute::Type::SignedInt:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_8:
case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -71,9 +73,9 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_INT_2_10_10_10_REV;
default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- return {};
+ break;
}
+ break;
case Maxwell::VertexAttribute::Type::Float:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_16:
@@ -87,45 +89,13 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return GL_FLOAT;
default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- return {};
- }
- case Maxwell::VertexAttribute::Type::UnsignedScaled:
- switch (attrib.size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- case Maxwell::VertexAttribute::Size::Size_8_8:
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return GL_UNSIGNED_BYTE;
- case Maxwell::VertexAttribute::Size::Size_16:
- case Maxwell::VertexAttribute::Size::Size_16_16:
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return GL_UNSIGNED_SHORT;
- default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- return {};
- }
- case Maxwell::VertexAttribute::Type::SignedScaled:
- switch (attrib.size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- case Maxwell::VertexAttribute::Size::Size_8_8:
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return GL_BYTE;
- case Maxwell::VertexAttribute::Size::Size_16:
- case Maxwell::VertexAttribute::Size::Size_16_16:
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return GL_SHORT;
- default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- return {};
+ break;
}
- default:
- LOG_ERROR(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
- return {};
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", attrib.TypeString(),
+ attrib.SizeString());
+ return {};
}
inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
@@ -137,8 +107,7 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
case Maxwell::IndexFormat::UnsignedInt:
return GL_UNSIGNED_INT;
}
- LOG_CRITICAL(Render_OpenGL, "Unimplemented index_format={}", static_cast<u32>(index_format));
- UNREACHABLE();
+ UNREACHABLE_MSG("Invalid index_format={}", static_cast<u32>(index_format));
return {};
}
@@ -180,31 +149,32 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
}
inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
- Tegra::Texture::TextureMipmapFilter mip_filter_mode) {
+ Tegra::Texture::TextureMipmapFilter mipmap_filter_mode) {
switch (filter_mode) {
- case Tegra::Texture::TextureFilter::Linear: {
- switch (mip_filter_mode) {
+ case Tegra::Texture::TextureFilter::Nearest:
+ switch (mipmap_filter_mode) {
case Tegra::Texture::TextureMipmapFilter::None:
- return GL_LINEAR;
+ return GL_NEAREST;
case Tegra::Texture::TextureMipmapFilter::Nearest:
- return GL_LINEAR_MIPMAP_NEAREST;
+ return GL_NEAREST_MIPMAP_NEAREST;
case Tegra::Texture::TextureMipmapFilter::Linear:
- return GL_LINEAR_MIPMAP_LINEAR;
+ return GL_NEAREST_MIPMAP_LINEAR;
}
- }
- case Tegra::Texture::TextureFilter::Nearest: {
- switch (mip_filter_mode) {
+ break;
+ case Tegra::Texture::TextureFilter::Linear:
+ switch (mipmap_filter_mode) {
case Tegra::Texture::TextureMipmapFilter::None:
- return GL_NEAREST;
+ return GL_LINEAR;
case Tegra::Texture::TextureMipmapFilter::Nearest:
- return GL_NEAREST_MIPMAP_NEAREST;
+ return GL_LINEAR_MIPMAP_NEAREST;
case Tegra::Texture::TextureMipmapFilter::Linear:
- return GL_NEAREST_MIPMAP_LINEAR;
+ return GL_LINEAR_MIPMAP_LINEAR;
}
+ break;
}
- }
- LOG_ERROR(Render_OpenGL, "Unimplemented texture filter mode={}", static_cast<u32>(filter_mode));
- return GL_LINEAR;
+ UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}",
+ static_cast<u32>(filter_mode), static_cast<u32>(mipmap_filter_mode));
+ return GL_NEAREST;
}
inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
@@ -227,10 +197,15 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
} else {
return GL_MIRROR_CLAMP_TO_EDGE;
}
- default:
- LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
- return GL_REPEAT;
+ case Tegra::Texture::WrapMode::MirrorOnceClampOGL:
+ if (GL_EXT_texture_mirror_clamp) {
+ return GL_MIRROR_CLAMP_EXT;
+ } else {
+ return GL_MIRROR_CLAMP_TO_EDGE;
+ }
}
+ UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
+ return GL_REPEAT;
}
inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
@@ -252,8 +227,7 @@ inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
case Tegra::Texture::DepthCompareFunc::Always:
return GL_ALWAYS;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented texture depth compare function ={}",
- static_cast<u32>(func));
+ UNIMPLEMENTED_MSG("Unimplemented texture depth compare function={}", static_cast<u32>(func));
return GL_GREATER;
}
@@ -275,7 +249,7 @@ inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
case Maxwell::Blend::Equation::MaxGL:
return GL_MAX;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented blend equation={}", static_cast<u32>(equation));
+ UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation));
return GL_FUNC_ADD;
}
@@ -339,7 +313,7 @@ inline GLenum BlendFunc(Maxwell::Blend::Factor factor) {
case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
return GL_ONE_MINUS_CONSTANT_ALPHA;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented blend factor={}", static_cast<u32>(factor));
+ UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor));
return GL_ZERO;
}
@@ -359,7 +333,7 @@ inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) {
case Tegra::Texture::SwizzleSource::OneFloat:
return GL_ONE;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented swizzle source={}", static_cast<u32>(source));
+ UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(source));
return GL_ZERO;
}
@@ -390,7 +364,7 @@ inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) {
case Maxwell::ComparisonOp::AlwaysOld:
return GL_ALWAYS;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented comparison op={}", static_cast<u32>(comparison));
+ UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison));
return GL_ALWAYS;
}
@@ -421,7 +395,7 @@ inline GLenum StencilOp(Maxwell::StencilOp stencil) {
case Maxwell::StencilOp::DecrWrapOGL:
return GL_DECR_WRAP;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented stencil op={}", static_cast<u32>(stencil));
+ UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil));
return GL_KEEP;
}
@@ -432,7 +406,7 @@ inline GLenum FrontFace(Maxwell::FrontFace front_face) {
case Maxwell::FrontFace::CounterClockWise:
return GL_CCW;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented front face cull={}", static_cast<u32>(front_face));
+ UNIMPLEMENTED_MSG("Unimplemented front face cull={}", static_cast<u32>(front_face));
return GL_CCW;
}
@@ -445,7 +419,7 @@ inline GLenum CullFace(Maxwell::CullFace cull_face) {
case Maxwell::CullFace::FrontAndBack:
return GL_FRONT_AND_BACK;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented cull face={}", static_cast<u32>(cull_face));
+ UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face));
return GL_BACK;
}
@@ -484,7 +458,7 @@ inline GLenum LogicOp(Maxwell::LogicOperation operation) {
case Maxwell::LogicOperation::Set:
return GL_SET;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented logic operation={}", static_cast<u32>(operation));
+ UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(operation));
return GL_COPY;
}
@@ -501,5 +475,10 @@ inline GLenum PolygonMode(Maxwell::PolygonMode polygon_mode) {
return GL_FILL;
}
+inline GLenum ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) {
+ // Enumeration order matches register order. We can convert it arithmetically.
+ return GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV + static_cast<GLenum>(swizzle);
+}
+
} // namespace MaxwellToGL
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index b2a179746..2ccca1993 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -21,6 +21,8 @@
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "video_core/host_shaders/opengl_present_frag.h"
+#include "video_core/host_shaders/opengl_present_vert.h"
#include "video_core/morton.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -30,60 +32,6 @@ namespace OpenGL {
namespace {
-constexpr std::size_t SWAP_CHAIN_SIZE = 3;
-
-struct Frame {
- u32 width{}; /// Width of the frame (to detect resize)
- u32 height{}; /// Height of the frame
- bool color_reloaded{}; /// Texture attachment was recreated (ie: resized)
- OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
- OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
- OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
- GLsync render_fence{}; /// Fence created on the render thread
- GLsync present_fence{}; /// Fence created on the presentation thread
- bool is_srgb{}; /// Framebuffer is sRGB or RGB
-};
-
-constexpr char VERTEX_SHADER[] = R"(
-#version 430 core
-
-out gl_PerVertex {
- vec4 gl_Position;
-};
-
-layout (location = 0) in vec2 vert_position;
-layout (location = 1) in vec2 vert_tex_coord;
-layout (location = 0) out vec2 frag_tex_coord;
-
-// This is a truncated 3x3 matrix for 2D transformations:
-// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
-// The third column performs translation.
-// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
-// implicitly be [0, 0, 1]
-layout (location = 0) uniform mat3x2 modelview_matrix;
-
-void main() {
- // Multiply input position by the rotscale part of the matrix and then manually translate by
- // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
- // to `vec3(vert_position.xy, 1.0)`
- gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
- frag_tex_coord = vert_tex_coord;
-}
-)";
-
-constexpr char FRAGMENT_SHADER[] = R"(
-#version 430 core
-
-layout (location = 0) in vec2 frag_tex_coord;
-layout (location = 0) out vec4 color;
-
-layout (binding = 0) uniform sampler2D color_texture;
-
-void main() {
- color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
-}
-)";
-
constexpr GLint PositionLocation = 0;
constexpr GLint TexCoordLocation = 1;
constexpr GLint ModelViewMatrixLocation = 0;
@@ -96,24 +44,6 @@ struct ScreenRectVertex {
std::array<GLfloat, 2> tex_coord;
};
-/// Returns true if any debug tool is attached
-bool HasDebugTool() {
- const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
- if (nsight) {
- return true;
- }
-
- GLint num_extensions;
- glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
- for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) {
- const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index));
- if (!std::strcmp(name, "GL_EXT_debug_tool")) {
- return true;
- }
- }
- return false;
-}
-
/**
* Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left
* corner and (width, height) on the lower-bottom.
@@ -197,132 +127,15 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
} // Anonymous namespace
-/**
- * For smooth Vsync rendering, we want to always present the latest frame that the core generates,
- * but also make sure that rendering happens at the pace that the frontend dictates. This is a
- * helper class that the renderer uses to sync frames between the render thread and the presentation
- * thread
- */
-class FrameMailbox {
-public:
- std::mutex swap_chain_lock;
- std::condition_variable present_cv;
- std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
- std::queue<Frame*> free_queue;
- std::deque<Frame*> present_queue;
- Frame* previous_frame{};
-
- FrameMailbox() {
- for (auto& frame : swap_chain) {
- free_queue.push(&frame);
- }
- }
-
- ~FrameMailbox() {
- // lock the mutex and clear out the present and free_queues and notify any people who are
- // blocked to prevent deadlock on shutdown
- std::scoped_lock lock{swap_chain_lock};
- std::queue<Frame*>().swap(free_queue);
- present_queue.clear();
- present_cv.notify_all();
- }
-
- void ReloadPresentFrame(Frame* frame, u32 height, u32 width) {
- frame->present.Release();
- frame->present.Create();
- GLint previous_draw_fbo{};
- glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
- glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
- frame->color.handle);
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
- }
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
- frame->color_reloaded = false;
- }
-
- void ReloadRenderFrame(Frame* frame, u32 width, u32 height) {
- // Recreate the color texture attachment
- frame->color.Release();
- frame->color.Create();
- const GLenum internal_format = frame->is_srgb ? GL_SRGB8 : GL_RGB8;
- glNamedRenderbufferStorage(frame->color.handle, internal_format, width, height);
-
- // Recreate the FBO for the render target
- frame->render.Release();
- frame->render.Create();
- glBindFramebuffer(GL_FRAMEBUFFER, frame->render.handle);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
- frame->color.handle);
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
- }
-
- frame->width = width;
- frame->height = height;
- frame->color_reloaded = true;
- }
-
- Frame* GetRenderFrame() {
- std::unique_lock lock{swap_chain_lock};
-
- // If theres no free frames, we will reuse the oldest render frame
- if (free_queue.empty()) {
- auto frame = present_queue.back();
- present_queue.pop_back();
- return frame;
- }
-
- Frame* frame = free_queue.front();
- free_queue.pop();
- return frame;
- }
-
- void ReleaseRenderFrame(Frame* frame) {
- std::unique_lock lock{swap_chain_lock};
- present_queue.push_front(frame);
- present_cv.notify_one();
- }
-
- Frame* TryGetPresentFrame(int timeout_ms) {
- std::unique_lock lock{swap_chain_lock};
- // wait for new entries in the present_queue
- present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
- [&] { return !present_queue.empty(); });
- if (present_queue.empty()) {
- // timed out waiting for a frame to draw so return the previous frame
- return previous_frame;
- }
-
- // free the previous frame and add it back to the free queue
- if (previous_frame) {
- free_queue.push(previous_frame);
- }
-
- // the newest entries are pushed to the front of the queue
- Frame* frame = present_queue.front();
- present_queue.pop_front();
- // remove all old entries from the present queue and move them back to the free_queue
- for (auto f : present_queue) {
- free_queue.push(f);
- }
- present_queue.clear();
- previous_frame = frame;
- return frame;
- }
-};
-
-RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system,
- Core::Frontend::GraphicsContext& context)
- : RendererBase{emu_window}, emu_window{emu_window}, system{system}, context{context},
- has_debug_tool{HasDebugTool()} {}
+RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window_,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context)
+ : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_},
+ emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device} {}
RendererOpenGL::~RendererOpenGL() = default;
-MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
-MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
-
void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
if (!framebuffer) {
return;
@@ -331,79 +144,34 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
PrepareRendertarget(framebuffer);
RenderScreenshot();
- Frame* frame;
- {
- MICROPROFILE_SCOPE(OpenGL_WaitPresent);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ DrawScreen(emu_window.GetFramebufferLayout());
- frame = frame_mailbox->GetRenderFrame();
+ ++m_current_frame;
- // Clean up sync objects before drawing
-
- // INTEL driver workaround. We can't delete the previous render sync object until we are
- // sure that the presentation is done
- if (frame->present_fence) {
- glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
- }
-
- // delete the draw fence if the frame wasn't presented
- if (frame->render_fence) {
- glDeleteSync(frame->render_fence);
- frame->render_fence = 0;
- }
-
- // wait for the presentation to be done
- if (frame->present_fence) {
- glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
- glDeleteSync(frame->present_fence);
- frame->present_fence = 0;
- }
- }
-
- {
- MICROPROFILE_SCOPE(OpenGL_RenderFrame);
- const auto& layout = render_window.GetFramebufferLayout();
-
- // Recreate the frame if the size of the window has changed
- if (layout.width != frame->width || layout.height != frame->height ||
- screen_info.display_srgb != frame->is_srgb) {
- LOG_DEBUG(Render_OpenGL, "Reloading render frame");
- frame->is_srgb = screen_info.display_srgb;
- frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
- }
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame->render.handle);
- DrawScreen(layout);
- // Create a fence for the frontend to wait on and swap this frame to OffTex
- frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
- glFlush();
- frame_mailbox->ReleaseRenderFrame(frame);
- m_current_frame++;
- rasterizer->TickFrame();
- }
+ rasterizer->TickFrame();
render_window.PollEvents();
- if (has_debug_tool) {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
- Present(0);
- context.SwapBuffers();
- }
+ context->SwapBuffers();
}
void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
- if (framebuffer) {
- // If framebuffer is provided, reload it from memory to a texture
- if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
- screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
- screen_info.texture.pixel_format != framebuffer->pixel_format ||
- gl_framebuffer_data.empty()) {
- // Reallocate texture if the framebuffer size has changed.
- // This is expected to not happen very often and hence should not be a
- // performance problem.
- ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
- }
-
- // Load the framebuffer from memory, draw it to the screen, and swap buffers
- LoadFBToScreenInfo(*framebuffer);
+ if (!framebuffer) {
+ return;
+ }
+ // If framebuffer is provided, reload it from memory to a texture
+ if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
+ screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
+ screen_info.texture.pixel_format != framebuffer->pixel_format ||
+ gl_framebuffer_data.empty()) {
+ // Reallocate texture if the framebuffer size has changed.
+ // This is expected to not happen very often and hence should not be a
+ // performance problem.
+ ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
}
+
+ // Load the framebuffer from memory, draw it to the screen, and swap buffers
+ LoadFBToScreenInfo(*framebuffer);
}
void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -423,7 +191,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
- u8* const host_ptr{system.Memory().GetPointer(framebuffer_addr)};
+ u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)};
rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes);
// TODO(Rodrigo): Read this from HLE
@@ -453,23 +221,22 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
}
void RendererOpenGL::InitOpenGLObjects() {
- frame_mailbox = std::make_unique<FrameMailbox>();
-
- glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
- 0.0f);
+ glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue(), 0.0f);
// Create shader programs
OGLShader vertex_shader;
- vertex_shader.Create(VERTEX_SHADER, GL_VERTEX_SHADER);
+ vertex_shader.Create(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER);
OGLShader fragment_shader;
- fragment_shader.Create(FRAGMENT_SHADER, GL_FRAGMENT_SHADER);
+ fragment_shader.Create(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER);
vertex_program.Create(true, false, vertex_shader.handle);
fragment_program.Create(true, false, fragment_shader.handle);
- // Create program pipeline
- program_manager.Create();
+ pipeline.Create();
+ glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex_program.handle);
+ glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment_program.handle);
// Generate VBO handle for drawing
vertex_buffer.Create();
@@ -487,6 +254,15 @@ void RendererOpenGL::InitOpenGLObjects() {
// Clear screen to black
LoadColorToActiveGLTexture(0, 0, 0, 0, screen_info.texture);
+
+ // Enable unified vertex attributes and query vertex buffer address when the driver supports it
+ if (device.HasVertexBufferUnifiedMemory()) {
+ glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
+
+ glMakeNamedBufferResidentNV(vertex_buffer.handle, GL_READ_ONLY);
+ glGetNamedBufferParameterui64vNV(vertex_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV,
+ &vertex_buffer_address);
+ }
}
void RendererOpenGL::AddTelemetryFields() {
@@ -498,18 +274,18 @@ void RendererOpenGL::AddTelemetryFields() {
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
- auto& telemetry_session = system.TelemetrySession();
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version);
+ constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
+ telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor);
+ telemetry_session.AddField(user_system, "GPU_Model", gpu_model);
+ telemetry_session.AddField(user_system, "GPU_OpenGL_Version", gl_version);
}
void RendererOpenGL::CreateRasterizer() {
if (rasterizer) {
return;
}
- rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, screen_info,
- program_manager, state_tracker);
+ rasterizer = std::make_unique<RasterizerOpenGL>(emu_window, gpu, cpu_memory, device,
+ screen_info, program_manager, state_tracker);
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
@@ -525,12 +301,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
GLint internal_format;
switch (framebuffer.pixel_format) {
- case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
internal_format = GL_RGBA8;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
break;
- case Tegra::FramebufferConfig::PixelFormat::RGB565:
+ case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
internal_format = GL_RGB565;
texture.gl_format = GL_RGB;
texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
@@ -551,8 +327,8 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
if (renderer_settings.set_background_color) {
// Update background color before drawing
- glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
- 0.0f);
+ glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue(), 0.0f);
}
// Set projection matrix
@@ -620,10 +396,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
state_tracker.NotifyClipControl();
state_tracker.NotifyAlphaTest();
- program_manager.UseVertexShader(vertex_program.handle);
- program_manager.UseGeometryShader(0);
- program_manager.UseFragmentShader(fragment_program.handle);
- program_manager.BindGraphicsPipeline();
+ program_manager.BindHostPipeline(pipeline.handle);
glEnable(GL_CULL_FACE);
if (screen_info.display_srgb) {
@@ -658,58 +431,21 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
offsetof(ScreenRectVertex, tex_coord));
glVertexAttribBinding(PositionLocation, 0);
glVertexAttribBinding(TexCoordLocation, 0);
- glBindVertexBuffer(0, vertex_buffer.handle, 0, sizeof(ScreenRectVertex));
+ if (device.HasVertexBufferUnifiedMemory()) {
+ glBindVertexBuffer(0, 0, 0, sizeof(ScreenRectVertex));
+ glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, vertex_buffer_address,
+ sizeof(vertices));
+ } else {
+ glBindVertexBuffer(0, vertex_buffer.handle, 0, sizeof(ScreenRectVertex));
+ }
glBindTextureUnit(0, screen_info.display_texture);
glBindSampler(0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-}
-bool RendererOpenGL::TryPresent(int timeout_ms) {
- if (has_debug_tool) {
- LOG_DEBUG(Render_OpenGL,
- "Skipping presentation because we are presenting on the main context");
- return false;
- }
- return Present(timeout_ms);
-}
-
-bool RendererOpenGL::Present(int timeout_ms) {
- const auto& layout = render_window.GetFramebufferLayout();
- auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms);
- if (!frame) {
- LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
- return false;
- }
-
- // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
- // readback since we won't be doing any blending
- glClear(GL_COLOR_BUFFER_BIT);
-
- // Recreate the presentation FBO if the color attachment was changed
- if (frame->color_reloaded) {
- LOG_DEBUG(Render_OpenGL, "Reloading present frame");
- frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
- }
- glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
- // INTEL workaround.
- // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
- // it on the emulation thread without too much penalty
- // glDeleteSync(frame.render_sync);
- // frame.render_sync = 0;
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
- glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
- GL_COLOR_BUFFER_BIT, GL_LINEAR);
-
- // Insert fence for the main thread to block on
- frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
- glFlush();
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
- return true;
+ program_manager.RestoreGuestPipeline();
}
void RendererOpenGL::RenderScreenshot() {
@@ -726,7 +462,7 @@ void RendererOpenGL::RenderScreenshot() {
screenshot_framebuffer.Create();
glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
- Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
+ const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
@@ -751,8 +487,9 @@ void RendererOpenGL::RenderScreenshot() {
}
bool RendererOpenGL::Init() {
- if (GLAD_GL_KHR_debug) {
+ if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(DebugHandler, nullptr);
}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 50b647661..9ef181f95 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -9,22 +9,32 @@
#include "common/common_types.h"
#include "common/math_util.h"
#include "video_core/renderer_base.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
namespace Core {
class System;
-}
+class TelemetrySession;
+} // namespace Core
namespace Core::Frontend {
class EmuWindow;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Layout {
struct FramebufferLayout;
}
+namespace Tegra {
+class GPU;
+}
+
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -45,24 +55,17 @@ struct ScreenInfo {
TextureInfo texture;
};
-struct PresentationTexture {
- u32 width = 0;
- u32 height = 0;
- OGLTexture texture;
-};
-
-class FrameMailbox;
-
class RendererOpenGL final : public VideoCore::RendererBase {
public:
- explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system,
- Core::Frontend::GraphicsContext& context);
+ explicit RendererOpenGL(Core::TelemetrySession& telemetry_session,
+ Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
+ Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
~RendererOpenGL() override;
bool Init() override;
void ShutDown() override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- bool TryPresent(int timeout_ms) override;
private:
/// Initializes the OpenGL state and creates persistent objects.
@@ -90,37 +93,36 @@ private:
void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
- bool Present(int timeout_ms);
-
+ Core::TelemetrySession& telemetry_session;
Core::Frontend::EmuWindow& emu_window;
- Core::System& system;
- Core::Frontend::GraphicsContext& context;
+ Core::Memory::Memory& cpu_memory;
+ Tegra::GPU& gpu;
- StateTracker state_tracker{system};
+ const Device device;
+ StateTracker state_tracker{gpu};
// OpenGL object IDs
OGLBuffer vertex_buffer;
OGLProgram vertex_program;
OGLProgram fragment_program;
+ OGLPipeline pipeline;
OGLFramebuffer screenshot_framebuffer;
+ // GPU address of the vertex buffer
+ GLuint64EXT vertex_buffer_address = 0;
+
/// Display information for Switch screen
ScreenInfo screen_info;
/// Global dummy shader pipeline
- GLShader::ProgramManager program_manager;
+ ProgramManager program_manager;
/// OpenGL framebuffer data
std::vector<u8> gl_framebuffer_data;
/// Used for transforming the framebuffer orientation
- Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
+ Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
Common::Rectangle<int> framebuffer_crop_rect;
-
- /// Frame presentation mailbox
- std::unique_ptr<FrameMailbox> frame_mailbox;
-
- bool has_debug_tool = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
index b751086fa..6d7bb16b2 100644
--- a/src/video_core/renderer_opengl/utils.cpp
+++ b/src/video_core/renderer_opengl/utils.cpp
@@ -14,68 +14,6 @@
namespace OpenGL {
-struct VertexArrayPushBuffer::Entry {
- GLuint binding_index{};
- const GLuint* buffer{};
- GLintptr offset{};
- GLsizei stride{};
-};
-
-VertexArrayPushBuffer::VertexArrayPushBuffer(StateTracker& state_tracker)
- : state_tracker{state_tracker} {}
-
-VertexArrayPushBuffer::~VertexArrayPushBuffer() = default;
-
-void VertexArrayPushBuffer::Setup() {
- index_buffer = nullptr;
- vertex_buffers.clear();
-}
-
-void VertexArrayPushBuffer::SetIndexBuffer(const GLuint* buffer) {
- index_buffer = buffer;
-}
-
-void VertexArrayPushBuffer::SetVertexBuffer(GLuint binding_index, const GLuint* buffer,
- GLintptr offset, GLsizei stride) {
- vertex_buffers.push_back(Entry{binding_index, buffer, offset, stride});
-}
-
-void VertexArrayPushBuffer::Bind() {
- if (index_buffer) {
- state_tracker.BindIndexBuffer(*index_buffer);
- }
-
- for (const auto& entry : vertex_buffers) {
- glBindVertexBuffer(entry.binding_index, *entry.buffer, entry.offset, entry.stride);
- }
-}
-
-struct BindBuffersRangePushBuffer::Entry {
- GLuint binding;
- const GLuint* buffer;
- GLintptr offset;
- GLsizeiptr size;
-};
-
-BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {}
-
-BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default;
-
-void BindBuffersRangePushBuffer::Setup() {
- entries.clear();
-}
-
-void BindBuffersRangePushBuffer::Push(GLuint binding, const GLuint* buffer, GLintptr offset,
- GLsizeiptr size) {
- entries.push_back(Entry{binding, buffer, offset, size});
-}
-
-void BindBuffersRangePushBuffer::Bind() {
- for (const Entry& entry : entries) {
- glBindBufferRange(target, entry.binding, *entry.buffer, entry.offset, entry.size);
- }
-}
-
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info) {
if (!GLAD_GL_KHR_debug) {
// We don't need to throw an error as this is just for debugging
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
index 47ee3177b..9c09ee12c 100644
--- a/src/video_core/renderer_opengl/utils.h
+++ b/src/video_core/renderer_opengl/utils.h
@@ -11,49 +11,6 @@
namespace OpenGL {
-class StateTracker;
-
-class VertexArrayPushBuffer final {
-public:
- explicit VertexArrayPushBuffer(StateTracker& state_tracker);
- ~VertexArrayPushBuffer();
-
- void Setup();
-
- void SetIndexBuffer(const GLuint* buffer);
-
- void SetVertexBuffer(GLuint binding_index, const GLuint* buffer, GLintptr offset,
- GLsizei stride);
-
- void Bind();
-
-private:
- struct Entry;
-
- StateTracker& state_tracker;
-
- const GLuint* index_buffer{};
- std::vector<Entry> vertex_buffers;
-};
-
-class BindBuffersRangePushBuffer final {
-public:
- explicit BindBuffersRangePushBuffer(GLenum target);
- ~BindBuffersRangePushBuffer();
-
- void Setup();
-
- void Push(GLuint binding, const GLuint* buffer, GLintptr offset, GLsizeiptr size);
-
- void Bind();
-
-private:
- struct Entry;
-
- GLenum target;
- std::vector<Entry> entries;
-};
-
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {});
} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index 2bb376555..da5c550ea 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -2,10 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <cstring>
#include <tuple>
#include <boost/functional/hash.hpp>
+#include "common/cityhash.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
@@ -13,289 +16,375 @@ namespace Vulkan {
namespace {
-constexpr FixedPipelineState::DepthStencil GetDepthStencilState(const Maxwell& regs) {
- const FixedPipelineState::StencilFace front_stencil(
- regs.stencil_front_op_fail, regs.stencil_front_op_zfail, regs.stencil_front_op_zpass,
- regs.stencil_front_func_func);
- const FixedPipelineState::StencilFace back_stencil =
- regs.stencil_two_side_enable
- ? FixedPipelineState::StencilFace(regs.stencil_back_op_fail, regs.stencil_back_op_zfail,
- regs.stencil_back_op_zpass,
- regs.stencil_back_func_func)
- : front_stencil;
- return FixedPipelineState::DepthStencil(
- regs.depth_test_enable == 1, regs.depth_write_enabled == 1, regs.depth_bounds_enable == 1,
- regs.stencil_enable == 1, regs.depth_test_func, front_stencil, back_stencil);
-}
+constexpr std::size_t POINT = 0;
+constexpr std::size_t LINE = 1;
+constexpr std::size_t POLYGON = 2;
+constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
+ POINT, // Points
+ LINE, // Lines
+ LINE, // LineLoop
+ LINE, // LineStrip
+ POLYGON, // Triangles
+ POLYGON, // TriangleStrip
+ POLYGON, // TriangleFan
+ POLYGON, // Quads
+ POLYGON, // QuadStrip
+ POLYGON, // Polygon
+ LINE, // LinesAdjacency
+ LINE, // LineStripAdjacency
+ POLYGON, // TrianglesAdjacency
+ POLYGON, // TriangleStripAdjacency
+ POLYGON, // Patches
+};
-constexpr FixedPipelineState::InputAssembly GetInputAssemblyState(const Maxwell& regs) {
- return FixedPipelineState::InputAssembly(
- regs.draw.topology, regs.primitive_restart.enabled,
- regs.draw.topology == Maxwell::PrimitiveTopology::Points ? regs.point_size : 0.0f);
-}
+} // Anonymous namespace
-constexpr FixedPipelineState::BlendingAttachment GetBlendingAttachmentState(
- const Maxwell& regs, std::size_t render_target) {
- const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : render_target];
- const std::array components = {mask.R != 0, mask.G != 0, mask.B != 0, mask.A != 0};
-
- const FixedPipelineState::BlendingAttachment default_blending(
- false, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
- Maxwell::Blend::Factor::Zero, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
- Maxwell::Blend::Factor::Zero, components);
- if (render_target >= regs.rt_control.count) {
- return default_blending;
+void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) {
+ const std::array enabled_lut = {regs.polygon_offset_point_enable,
+ regs.polygon_offset_line_enable,
+ regs.polygon_offset_fill_enable};
+ const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
+
+ raw = 0;
+ primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
+ depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
+ depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
+ ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
+ polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
+ patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
+ tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value()));
+ tessellation_spacing.Assign(static_cast<u32>(regs.tess_mode.spacing.Value()));
+ tessellation_clockwise.Assign(regs.tess_mode.cw.Value());
+ logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
+ logic_op.Assign(PackLogicOp(regs.logic_op.operation));
+ rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
+ topology.Assign(regs.draw.topology);
+
+ std::memcpy(&point_size, &regs.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
+
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ binding_divisors[index] =
+ regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0;
}
- if (!regs.independent_blend_enable) {
- const auto& src = regs.blend;
- if (!src.enable[render_target]) {
- return default_blending;
- }
- return FixedPipelineState::BlendingAttachment(
- true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
- src.factor_source_a, src.factor_dest_a, components);
+ for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
+ const auto& input = regs.vertex_attrib_format[index];
+ auto& attribute = attributes[index];
+ attribute.raw = 0;
+ attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
+ attribute.buffer.Assign(input.buffer);
+ attribute.offset.Assign(input.offset);
+ attribute.type.Assign(static_cast<u32>(input.type.Value()));
+ attribute.size.Assign(static_cast<u32>(input.size.Value()));
}
- if (!regs.blend.enable[render_target]) {
- return default_blending;
+ for (std::size_t index = 0; index < std::size(attachments); ++index) {
+ attachments[index].Fill(regs, index);
}
- const auto& src = regs.independent_blend[render_target];
- return FixedPipelineState::BlendingAttachment(
- true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
- src.factor_source_a, src.factor_dest_a, components);
-}
-constexpr FixedPipelineState::ColorBlending GetColorBlendingState(const Maxwell& regs) {
- return FixedPipelineState::ColorBlending(
- {regs.blend_color.r, regs.blend_color.g, regs.blend_color.b, regs.blend_color.a},
- regs.rt_control.count,
- {GetBlendingAttachmentState(regs, 0), GetBlendingAttachmentState(regs, 1),
- GetBlendingAttachmentState(regs, 2), GetBlendingAttachmentState(regs, 3),
- GetBlendingAttachmentState(regs, 4), GetBlendingAttachmentState(regs, 5),
- GetBlendingAttachmentState(regs, 6), GetBlendingAttachmentState(regs, 7)});
-}
+ const auto& transform = regs.viewport_transform;
+ std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
+ [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
-constexpr FixedPipelineState::Tessellation GetTessellationState(const Maxwell& regs) {
- return FixedPipelineState::Tessellation(regs.patch_vertices, regs.tess_mode.prim,
- regs.tess_mode.spacing, regs.tess_mode.cw != 0);
+ if (!has_extended_dynamic_state) {
+ no_extended_dynamic_state.Assign(1);
+ dynamic_state.Fill(regs);
+ }
}
-constexpr std::size_t Point = 0;
-constexpr std::size_t Line = 1;
-constexpr std::size_t Polygon = 2;
-constexpr std::array PolygonOffsetEnableLUT = {
- Point, // Points
- Line, // Lines
- Line, // LineLoop
- Line, // LineStrip
- Polygon, // Triangles
- Polygon, // TriangleStrip
- Polygon, // TriangleFan
- Polygon, // Quads
- Polygon, // QuadStrip
- Polygon, // Polygon
- Line, // LinesAdjacency
- Line, // LineStripAdjacency
- Polygon, // TrianglesAdjacency
- Polygon, // TriangleStripAdjacency
- Polygon, // Patches
-};
+void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
+ const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
-constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) {
- const std::array enabled_lut = {regs.polygon_offset_point_enable,
- regs.polygon_offset_line_enable,
- regs.polygon_offset_fill_enable};
- const auto topology = static_cast<std::size_t>(regs.draw.topology.Value());
- const bool depth_bias_enabled = enabled_lut[PolygonOffsetEnableLUT[topology]];
-
- const auto& clip = regs.view_volume_clip_control;
- const bool depth_clamp_enabled = clip.depth_clamp_near == 1 || clip.depth_clamp_far == 1;
-
- Maxwell::FrontFace front_face = regs.front_face;
- if (regs.screen_y_control.triangle_rast_flip != 0 &&
- regs.viewport_transform[0].scale_y > 0.0f) {
- if (front_face == Maxwell::FrontFace::CounterClockWise)
- front_face = Maxwell::FrontFace::ClockWise;
- else if (front_face == Maxwell::FrontFace::ClockWise)
- front_face = Maxwell::FrontFace::CounterClockWise;
- }
+ raw = 0;
+ mask_r.Assign(mask.R);
+ mask_g.Assign(mask.G);
+ mask_b.Assign(mask.B);
+ mask_a.Assign(mask.A);
- const bool gl_ndc = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne;
- return FixedPipelineState::Rasterizer(regs.cull_test_enabled, depth_bias_enabled,
- depth_clamp_enabled, gl_ndc, regs.cull_face, front_face);
-}
+ // TODO: C++20 Use templated lambda to deduplicate code
-} // Anonymous namespace
-
-std::size_t FixedPipelineState::VertexBinding::Hash() const noexcept {
- return (index << stride) ^ divisor;
-}
+ if (!regs.independent_blend_enable) {
+ const auto& src = regs.blend;
+ if (!src.enable[index]) {
+ return;
+ }
+ equation_rgb.Assign(PackBlendEquation(src.equation_rgb));
+ equation_a.Assign(PackBlendEquation(src.equation_a));
+ factor_source_rgb.Assign(PackBlendFactor(src.factor_source_rgb));
+ factor_dest_rgb.Assign(PackBlendFactor(src.factor_dest_rgb));
+ factor_source_a.Assign(PackBlendFactor(src.factor_source_a));
+ factor_dest_a.Assign(PackBlendFactor(src.factor_dest_a));
+ enable.Assign(1);
+ return;
+ }
-bool FixedPipelineState::VertexBinding::operator==(const VertexBinding& rhs) const noexcept {
- return std::tie(index, stride, divisor) == std::tie(rhs.index, rhs.stride, rhs.divisor);
+ if (!regs.blend.enable[index]) {
+ return;
+ }
+ const auto& src = regs.independent_blend[index];
+ equation_rgb.Assign(PackBlendEquation(src.equation_rgb));
+ equation_a.Assign(PackBlendEquation(src.equation_a));
+ factor_source_rgb.Assign(PackBlendFactor(src.factor_source_rgb));
+ factor_dest_rgb.Assign(PackBlendFactor(src.factor_dest_rgb));
+ factor_source_a.Assign(PackBlendFactor(src.factor_source_a));
+ factor_dest_a.Assign(PackBlendFactor(src.factor_dest_a));
+ enable.Assign(1);
}
-std::size_t FixedPipelineState::VertexAttribute::Hash() const noexcept {
- return static_cast<std::size_t>(index) ^ (static_cast<std::size_t>(buffer) << 13) ^
- (static_cast<std::size_t>(type) << 22) ^ (static_cast<std::size_t>(size) << 31) ^
- (static_cast<std::size_t>(offset) << 36);
-}
+void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
+ u32 packed_front_face = PackFrontFace(regs.front_face);
+ if (regs.screen_y_control.triangle_rast_flip != 0) {
+ // Flip front face
+ packed_front_face = 1 - packed_front_face;
+ }
-bool FixedPipelineState::VertexAttribute::operator==(const VertexAttribute& rhs) const noexcept {
- return std::tie(index, buffer, type, size, offset) ==
- std::tie(rhs.index, rhs.buffer, rhs.type, rhs.size, rhs.offset);
+ raw1 = 0;
+ raw2 = 0;
+ front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
+ front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
+ front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
+ front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
+ if (regs.stencil_two_side_enable) {
+ back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
+ back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
+ back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
+ back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
+ } else {
+ back.action_stencil_fail.Assign(front.action_stencil_fail);
+ back.action_depth_fail.Assign(front.action_depth_fail);
+ back.action_depth_pass.Assign(front.action_depth_pass);
+ back.test_func.Assign(front.test_func);
+ }
+ stencil_enable.Assign(regs.stencil_enable);
+ depth_write_enable.Assign(regs.depth_write_enabled);
+ depth_bounds_enable.Assign(regs.depth_bounds_enable);
+ depth_test_enable.Assign(regs.depth_test_enable);
+ front_face.Assign(packed_front_face);
+ depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
+ cull_face.Assign(PackCullFace(regs.cull_face));
+ cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
+
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& input = regs.vertex_array[index];
+ VertexBinding& binding = vertex_bindings[index];
+ binding.raw = 0;
+ binding.enabled.Assign(input.IsEnabled() ? 1 : 0);
+ binding.stride.Assign(static_cast<u16>(input.stride.Value()));
+ }
}
-std::size_t FixedPipelineState::StencilFace::Hash() const noexcept {
- return static_cast<std::size_t>(action_stencil_fail) ^
- (static_cast<std::size_t>(action_depth_fail) << 4) ^
- (static_cast<std::size_t>(action_depth_fail) << 20) ^
- (static_cast<std::size_t>(action_depth_pass) << 36);
+std::size_t FixedPipelineState::Hash() const noexcept {
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
+ return static_cast<std::size_t>(hash);
}
-bool FixedPipelineState::StencilFace::operator==(const StencilFace& rhs) const noexcept {
- return std::tie(action_stencil_fail, action_depth_fail, action_depth_pass, test_func) ==
- std::tie(rhs.action_stencil_fail, rhs.action_depth_fail, rhs.action_depth_pass,
- rhs.test_func);
+bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
+ return std::memcmp(this, &rhs, Size()) == 0;
}
-std::size_t FixedPipelineState::BlendingAttachment::Hash() const noexcept {
- return static_cast<std::size_t>(enable) ^ (static_cast<std::size_t>(rgb_equation) << 5) ^
- (static_cast<std::size_t>(src_rgb_func) << 10) ^
- (static_cast<std::size_t>(dst_rgb_func) << 15) ^
- (static_cast<std::size_t>(a_equation) << 20) ^
- (static_cast<std::size_t>(src_a_func) << 25) ^
- (static_cast<std::size_t>(dst_a_func) << 30) ^
- (static_cast<std::size_t>(components[0]) << 35) ^
- (static_cast<std::size_t>(components[1]) << 36) ^
- (static_cast<std::size_t>(components[2]) << 37) ^
- (static_cast<std::size_t>(components[3]) << 38);
+u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept {
+ // OpenGL enums go from 0x200 to 0x207 and the others from 1 to 8
+ // If we substract 0x200 to OpenGL enums and 1 to the others we get a 0-7 range.
+ // Perfect for a hash.
+ const u32 value = static_cast<u32>(op);
+ return value - (value >= 0x200 ? 0x200 : 1);
}
-bool FixedPipelineState::BlendingAttachment::operator==(const BlendingAttachment& rhs) const
- noexcept {
- return std::tie(enable, rgb_equation, src_rgb_func, dst_rgb_func, a_equation, src_a_func,
- dst_a_func, components) ==
- std::tie(rhs.enable, rhs.rgb_equation, rhs.src_rgb_func, rhs.dst_rgb_func,
- rhs.a_equation, rhs.src_a_func, rhs.dst_a_func, rhs.components);
+Maxwell::ComparisonOp FixedPipelineState::UnpackComparisonOp(u32 packed) noexcept {
+ // Read PackComparisonOp for the logic behind this.
+ return static_cast<Maxwell::ComparisonOp>(packed + 1);
}
-std::size_t FixedPipelineState::VertexInput::Hash() const noexcept {
- std::size_t hash = num_bindings ^ (num_attributes << 32);
- for (std::size_t i = 0; i < num_bindings; ++i) {
- boost::hash_combine(hash, bindings[i].Hash());
+u32 FixedPipelineState::PackStencilOp(Maxwell::StencilOp op) noexcept {
+ switch (op) {
+ case Maxwell::StencilOp::Keep:
+ case Maxwell::StencilOp::KeepOGL:
+ return 0;
+ case Maxwell::StencilOp::Zero:
+ case Maxwell::StencilOp::ZeroOGL:
+ return 1;
+ case Maxwell::StencilOp::Replace:
+ case Maxwell::StencilOp::ReplaceOGL:
+ return 2;
+ case Maxwell::StencilOp::Incr:
+ case Maxwell::StencilOp::IncrOGL:
+ return 3;
+ case Maxwell::StencilOp::Decr:
+ case Maxwell::StencilOp::DecrOGL:
+ return 4;
+ case Maxwell::StencilOp::Invert:
+ case Maxwell::StencilOp::InvertOGL:
+ return 5;
+ case Maxwell::StencilOp::IncrWrap:
+ case Maxwell::StencilOp::IncrWrapOGL:
+ return 6;
+ case Maxwell::StencilOp::DecrWrap:
+ case Maxwell::StencilOp::DecrWrapOGL:
+ return 7;
}
- for (std::size_t i = 0; i < num_attributes; ++i) {
- boost::hash_combine(hash, attributes[i].Hash());
- }
- return hash;
+ return 0;
}
-bool FixedPipelineState::VertexInput::operator==(const VertexInput& rhs) const noexcept {
- return std::equal(bindings.begin(), bindings.begin() + num_bindings, rhs.bindings.begin(),
- rhs.bindings.begin() + rhs.num_bindings) &&
- std::equal(attributes.begin(), attributes.begin() + num_attributes,
- rhs.attributes.begin(), rhs.attributes.begin() + rhs.num_attributes);
+Maxwell::StencilOp FixedPipelineState::UnpackStencilOp(u32 packed) noexcept {
+ static constexpr std::array LUT = {Maxwell::StencilOp::Keep, Maxwell::StencilOp::Zero,
+ Maxwell::StencilOp::Replace, Maxwell::StencilOp::Incr,
+ Maxwell::StencilOp::Decr, Maxwell::StencilOp::Invert,
+ Maxwell::StencilOp::IncrWrap, Maxwell::StencilOp::DecrWrap};
+ return LUT[packed];
}
-std::size_t FixedPipelineState::InputAssembly::Hash() const noexcept {
- std::size_t point_size_int = 0;
- std::memcpy(&point_size_int, &point_size, sizeof(point_size));
- return (static_cast<std::size_t>(topology) << 24) ^ (point_size_int << 32) ^
- static_cast<std::size_t>(primitive_restart_enable);
+u32 FixedPipelineState::PackCullFace(Maxwell::CullFace cull) noexcept {
+ // FrontAndBack is 0x408, by substracting 0x406 in it we get 2.
+ // Individual cull faces are in 0x404 and 0x405, substracting 0x404 we get 0 and 1.
+ const u32 value = static_cast<u32>(cull);
+ return value - (value == 0x408 ? 0x406 : 0x404);
}
-bool FixedPipelineState::InputAssembly::operator==(const InputAssembly& rhs) const noexcept {
- return std::tie(topology, primitive_restart_enable, point_size) ==
- std::tie(rhs.topology, rhs.primitive_restart_enable, rhs.point_size);
+Maxwell::CullFace FixedPipelineState::UnpackCullFace(u32 packed) noexcept {
+ static constexpr std::array LUT = {Maxwell::CullFace::Front, Maxwell::CullFace::Back,
+ Maxwell::CullFace::FrontAndBack};
+ return LUT[packed];
}
-std::size_t FixedPipelineState::Tessellation::Hash() const noexcept {
- return static_cast<std::size_t>(patch_control_points) ^
- (static_cast<std::size_t>(primitive) << 6) ^ (static_cast<std::size_t>(spacing) << 8) ^
- (static_cast<std::size_t>(clockwise) << 10);
+u32 FixedPipelineState::PackFrontFace(Maxwell::FrontFace face) noexcept {
+ return static_cast<u32>(face) - 0x900;
}
-bool FixedPipelineState::Tessellation::operator==(const Tessellation& rhs) const noexcept {
- return std::tie(patch_control_points, primitive, spacing, clockwise) ==
- std::tie(rhs.patch_control_points, rhs.primitive, rhs.spacing, rhs.clockwise);
+Maxwell::FrontFace FixedPipelineState::UnpackFrontFace(u32 packed) noexcept {
+ return static_cast<Maxwell::FrontFace>(packed + 0x900);
}
-std::size_t FixedPipelineState::Rasterizer::Hash() const noexcept {
- return static_cast<std::size_t>(cull_enable) ^
- (static_cast<std::size_t>(depth_bias_enable) << 1) ^
- (static_cast<std::size_t>(depth_clamp_enable) << 2) ^
- (static_cast<std::size_t>(ndc_minus_one_to_one) << 3) ^
- (static_cast<std::size_t>(cull_face) << 24) ^
- (static_cast<std::size_t>(front_face) << 48);
+u32 FixedPipelineState::PackPolygonMode(Maxwell::PolygonMode mode) noexcept {
+ return static_cast<u32>(mode) - 0x1B00;
}
-bool FixedPipelineState::Rasterizer::operator==(const Rasterizer& rhs) const noexcept {
- return std::tie(cull_enable, depth_bias_enable, depth_clamp_enable, ndc_minus_one_to_one,
- cull_face, front_face) ==
- std::tie(rhs.cull_enable, rhs.depth_bias_enable, rhs.depth_clamp_enable,
- rhs.ndc_minus_one_to_one, rhs.cull_face, rhs.front_face);
+Maxwell::PolygonMode FixedPipelineState::UnpackPolygonMode(u32 packed) noexcept {
+ return static_cast<Maxwell::PolygonMode>(packed + 0x1B00);
}
-std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept {
- std::size_t hash = static_cast<std::size_t>(depth_test_enable) ^
- (static_cast<std::size_t>(depth_write_enable) << 1) ^
- (static_cast<std::size_t>(depth_bounds_enable) << 2) ^
- (static_cast<std::size_t>(stencil_enable) << 3) ^
- (static_cast<std::size_t>(depth_test_function) << 4);
- boost::hash_combine(hash, front_stencil.Hash());
- boost::hash_combine(hash, back_stencil.Hash());
- return hash;
+u32 FixedPipelineState::PackLogicOp(Maxwell::LogicOperation op) noexcept {
+ return static_cast<u32>(op) - 0x1500;
}
-bool FixedPipelineState::DepthStencil::operator==(const DepthStencil& rhs) const noexcept {
- return std::tie(depth_test_enable, depth_write_enable, depth_bounds_enable, depth_test_function,
- stencil_enable, front_stencil, back_stencil) ==
- std::tie(rhs.depth_test_enable, rhs.depth_write_enable, rhs.depth_bounds_enable,
- rhs.depth_test_function, rhs.stencil_enable, rhs.front_stencil,
- rhs.back_stencil);
+Maxwell::LogicOperation FixedPipelineState::UnpackLogicOp(u32 packed) noexcept {
+ return static_cast<Maxwell::LogicOperation>(packed + 0x1500);
}
-std::size_t FixedPipelineState::ColorBlending::Hash() const noexcept {
- std::size_t hash = attachments_count << 13;
- for (std::size_t rt = 0; rt < static_cast<std::size_t>(attachments_count); ++rt) {
- boost::hash_combine(hash, attachments[rt].Hash());
+u32 FixedPipelineState::PackBlendEquation(Maxwell::Blend::Equation equation) noexcept {
+ switch (equation) {
+ case Maxwell::Blend::Equation::Add:
+ case Maxwell::Blend::Equation::AddGL:
+ return 0;
+ case Maxwell::Blend::Equation::Subtract:
+ case Maxwell::Blend::Equation::SubtractGL:
+ return 1;
+ case Maxwell::Blend::Equation::ReverseSubtract:
+ case Maxwell::Blend::Equation::ReverseSubtractGL:
+ return 2;
+ case Maxwell::Blend::Equation::Min:
+ case Maxwell::Blend::Equation::MinGL:
+ return 3;
+ case Maxwell::Blend::Equation::Max:
+ case Maxwell::Blend::Equation::MaxGL:
+ return 4;
}
- return hash;
+ return 0;
}
-bool FixedPipelineState::ColorBlending::operator==(const ColorBlending& rhs) const noexcept {
- return std::equal(attachments.begin(), attachments.begin() + attachments_count,
- rhs.attachments.begin(), rhs.attachments.begin() + rhs.attachments_count);
+Maxwell::Blend::Equation FixedPipelineState::UnpackBlendEquation(u32 packed) noexcept {
+ static constexpr std::array LUT = {
+ Maxwell::Blend::Equation::Add, Maxwell::Blend::Equation::Subtract,
+ Maxwell::Blend::Equation::ReverseSubtract, Maxwell::Blend::Equation::Min,
+ Maxwell::Blend::Equation::Max};
+ return LUT[packed];
}
-std::size_t FixedPipelineState::Hash() const noexcept {
- std::size_t hash = 0;
- boost::hash_combine(hash, vertex_input.Hash());
- boost::hash_combine(hash, input_assembly.Hash());
- boost::hash_combine(hash, tessellation.Hash());
- boost::hash_combine(hash, rasterizer.Hash());
- boost::hash_combine(hash, depth_stencil.Hash());
- boost::hash_combine(hash, color_blending.Hash());
- return hash;
-}
-
-bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
- return std::tie(vertex_input, input_assembly, tessellation, rasterizer, depth_stencil,
- color_blending) == std::tie(rhs.vertex_input, rhs.input_assembly,
- rhs.tessellation, rhs.rasterizer, rhs.depth_stencil,
- rhs.color_blending);
+u32 FixedPipelineState::PackBlendFactor(Maxwell::Blend::Factor factor) noexcept {
+ switch (factor) {
+ case Maxwell::Blend::Factor::Zero:
+ case Maxwell::Blend::Factor::ZeroGL:
+ return 0;
+ case Maxwell::Blend::Factor::One:
+ case Maxwell::Blend::Factor::OneGL:
+ return 1;
+ case Maxwell::Blend::Factor::SourceColor:
+ case Maxwell::Blend::Factor::SourceColorGL:
+ return 2;
+ case Maxwell::Blend::Factor::OneMinusSourceColor:
+ case Maxwell::Blend::Factor::OneMinusSourceColorGL:
+ return 3;
+ case Maxwell::Blend::Factor::SourceAlpha:
+ case Maxwell::Blend::Factor::SourceAlphaGL:
+ return 4;
+ case Maxwell::Blend::Factor::OneMinusSourceAlpha:
+ case Maxwell::Blend::Factor::OneMinusSourceAlphaGL:
+ return 5;
+ case Maxwell::Blend::Factor::DestAlpha:
+ case Maxwell::Blend::Factor::DestAlphaGL:
+ return 6;
+ case Maxwell::Blend::Factor::OneMinusDestAlpha:
+ case Maxwell::Blend::Factor::OneMinusDestAlphaGL:
+ return 7;
+ case Maxwell::Blend::Factor::DestColor:
+ case Maxwell::Blend::Factor::DestColorGL:
+ return 8;
+ case Maxwell::Blend::Factor::OneMinusDestColor:
+ case Maxwell::Blend::Factor::OneMinusDestColorGL:
+ return 9;
+ case Maxwell::Blend::Factor::SourceAlphaSaturate:
+ case Maxwell::Blend::Factor::SourceAlphaSaturateGL:
+ return 10;
+ case Maxwell::Blend::Factor::Source1Color:
+ case Maxwell::Blend::Factor::Source1ColorGL:
+ return 11;
+ case Maxwell::Blend::Factor::OneMinusSource1Color:
+ case Maxwell::Blend::Factor::OneMinusSource1ColorGL:
+ return 12;
+ case Maxwell::Blend::Factor::Source1Alpha:
+ case Maxwell::Blend::Factor::Source1AlphaGL:
+ return 13;
+ case Maxwell::Blend::Factor::OneMinusSource1Alpha:
+ case Maxwell::Blend::Factor::OneMinusSource1AlphaGL:
+ return 14;
+ case Maxwell::Blend::Factor::ConstantColor:
+ case Maxwell::Blend::Factor::ConstantColorGL:
+ return 15;
+ case Maxwell::Blend::Factor::OneMinusConstantColor:
+ case Maxwell::Blend::Factor::OneMinusConstantColorGL:
+ return 16;
+ case Maxwell::Blend::Factor::ConstantAlpha:
+ case Maxwell::Blend::Factor::ConstantAlphaGL:
+ return 17;
+ case Maxwell::Blend::Factor::OneMinusConstantAlpha:
+ case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
+ return 18;
+ }
+ return 0;
}
-FixedPipelineState GetFixedPipelineState(const Maxwell& regs) {
- FixedPipelineState fixed_state;
- fixed_state.input_assembly = GetInputAssemblyState(regs);
- fixed_state.tessellation = GetTessellationState(regs);
- fixed_state.rasterizer = GetRasterizerState(regs);
- fixed_state.depth_stencil = GetDepthStencilState(regs);
- fixed_state.color_blending = GetColorBlendingState(regs);
- return fixed_state;
+Maxwell::Blend::Factor FixedPipelineState::UnpackBlendFactor(u32 packed) noexcept {
+ static constexpr std::array LUT = {
+ Maxwell::Blend::Factor::Zero,
+ Maxwell::Blend::Factor::One,
+ Maxwell::Blend::Factor::SourceColor,
+ Maxwell::Blend::Factor::OneMinusSourceColor,
+ Maxwell::Blend::Factor::SourceAlpha,
+ Maxwell::Blend::Factor::OneMinusSourceAlpha,
+ Maxwell::Blend::Factor::DestAlpha,
+ Maxwell::Blend::Factor::OneMinusDestAlpha,
+ Maxwell::Blend::Factor::DestColor,
+ Maxwell::Blend::Factor::OneMinusDestColor,
+ Maxwell::Blend::Factor::SourceAlphaSaturate,
+ Maxwell::Blend::Factor::Source1Color,
+ Maxwell::Blend::Factor::OneMinusSource1Color,
+ Maxwell::Blend::Factor::Source1Alpha,
+ Maxwell::Blend::Factor::OneMinusSource1Alpha,
+ Maxwell::Blend::Factor::ConstantColor,
+ Maxwell::Blend::Factor::OneMinusConstantColor,
+ Maxwell::Blend::Factor::ConstantAlpha,
+ Maxwell::Blend::Factor::OneMinusConstantAlpha,
+ };
+ return LUT[packed];
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index 4c8ba7f90..2c18eeaae 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -7,6 +7,7 @@
#include <array>
#include <type_traits>
+#include "common/bit_field.h"
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
@@ -16,230 +17,184 @@ namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-// TODO(Rodrigo): Optimize this structure.
-
struct FixedPipelineState {
- using PixelFormat = VideoCore::Surface::PixelFormat;
-
- struct VertexBinding {
- constexpr VertexBinding(u32 index, u32 stride, u32 divisor)
- : index{index}, stride{stride}, divisor{divisor} {}
- VertexBinding() = default;
-
- u32 index;
- u32 stride;
- u32 divisor;
+ static u32 PackComparisonOp(Maxwell::ComparisonOp op) noexcept;
+ static Maxwell::ComparisonOp UnpackComparisonOp(u32 packed) noexcept;
- std::size_t Hash() const noexcept;
+ static u32 PackStencilOp(Maxwell::StencilOp op) noexcept;
+ static Maxwell::StencilOp UnpackStencilOp(u32 packed) noexcept;
- bool operator==(const VertexBinding& rhs) const noexcept;
+ static u32 PackCullFace(Maxwell::CullFace cull) noexcept;
+ static Maxwell::CullFace UnpackCullFace(u32 packed) noexcept;
- bool operator!=(const VertexBinding& rhs) const noexcept {
- return !operator==(rhs);
- }
- };
+ static u32 PackFrontFace(Maxwell::FrontFace face) noexcept;
+ static Maxwell::FrontFace UnpackFrontFace(u32 packed) noexcept;
- struct VertexAttribute {
- constexpr VertexAttribute(u32 index, u32 buffer, Maxwell::VertexAttribute::Type type,
- Maxwell::VertexAttribute::Size size, u32 offset)
- : index{index}, buffer{buffer}, type{type}, size{size}, offset{offset} {}
- VertexAttribute() = default;
+ static u32 PackPolygonMode(Maxwell::PolygonMode mode) noexcept;
+ static Maxwell::PolygonMode UnpackPolygonMode(u32 packed) noexcept;
- u32 index;
- u32 buffer;
- Maxwell::VertexAttribute::Type type;
- Maxwell::VertexAttribute::Size size;
- u32 offset;
+ static u32 PackLogicOp(Maxwell::LogicOperation op) noexcept;
+ static Maxwell::LogicOperation UnpackLogicOp(u32 packed) noexcept;
- std::size_t Hash() const noexcept;
+ static u32 PackBlendEquation(Maxwell::Blend::Equation equation) noexcept;
+ static Maxwell::Blend::Equation UnpackBlendEquation(u32 packed) noexcept;
- bool operator==(const VertexAttribute& rhs) const noexcept;
+ static u32 PackBlendFactor(Maxwell::Blend::Factor factor) noexcept;
+ static Maxwell::Blend::Factor UnpackBlendFactor(u32 packed) noexcept;
- bool operator!=(const VertexAttribute& rhs) const noexcept {
- return !operator==(rhs);
+ struct BlendingAttachment {
+ union {
+ u32 raw;
+ BitField<0, 1, u32> mask_r;
+ BitField<1, 1, u32> mask_g;
+ BitField<2, 1, u32> mask_b;
+ BitField<3, 1, u32> mask_a;
+ BitField<4, 3, u32> equation_rgb;
+ BitField<7, 3, u32> equation_a;
+ BitField<10, 5, u32> factor_source_rgb;
+ BitField<15, 5, u32> factor_dest_rgb;
+ BitField<20, 5, u32> factor_source_a;
+ BitField<25, 5, u32> factor_dest_a;
+ BitField<30, 1, u32> enable;
+ };
+
+ void Fill(const Maxwell& regs, std::size_t index);
+
+ constexpr std::array<bool, 4> Mask() const noexcept {
+ return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
}
- };
-
- struct StencilFace {
- constexpr StencilFace(Maxwell::StencilOp action_stencil_fail,
- Maxwell::StencilOp action_depth_fail,
- Maxwell::StencilOp action_depth_pass, Maxwell::ComparisonOp test_func)
- : action_stencil_fail{action_stencil_fail}, action_depth_fail{action_depth_fail},
- action_depth_pass{action_depth_pass}, test_func{test_func} {}
- StencilFace() = default;
-
- Maxwell::StencilOp action_stencil_fail;
- Maxwell::StencilOp action_depth_fail;
- Maxwell::StencilOp action_depth_pass;
- Maxwell::ComparisonOp test_func;
- std::size_t Hash() const noexcept;
-
- bool operator==(const StencilFace& rhs) const noexcept;
-
- bool operator!=(const StencilFace& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::Blend::Equation EquationRGB() const noexcept {
+ return UnpackBlendEquation(equation_rgb.Value());
}
- };
- struct BlendingAttachment {
- constexpr BlendingAttachment(bool enable, Maxwell::Blend::Equation rgb_equation,
- Maxwell::Blend::Factor src_rgb_func,
- Maxwell::Blend::Factor dst_rgb_func,
- Maxwell::Blend::Equation a_equation,
- Maxwell::Blend::Factor src_a_func,
- Maxwell::Blend::Factor dst_a_func,
- std::array<bool, 4> components)
- : enable{enable}, rgb_equation{rgb_equation}, src_rgb_func{src_rgb_func},
- dst_rgb_func{dst_rgb_func}, a_equation{a_equation}, src_a_func{src_a_func},
- dst_a_func{dst_a_func}, components{components} {}
- BlendingAttachment() = default;
-
- bool enable;
- Maxwell::Blend::Equation rgb_equation;
- Maxwell::Blend::Factor src_rgb_func;
- Maxwell::Blend::Factor dst_rgb_func;
- Maxwell::Blend::Equation a_equation;
- Maxwell::Blend::Factor src_a_func;
- Maxwell::Blend::Factor dst_a_func;
- std::array<bool, 4> components;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const BlendingAttachment& rhs) const noexcept;
-
- bool operator!=(const BlendingAttachment& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::Blend::Equation EquationAlpha() const noexcept {
+ return UnpackBlendEquation(equation_a.Value());
}
- };
- struct VertexInput {
- std::size_t num_bindings = 0;
- std::size_t num_attributes = 0;
- std::array<VertexBinding, Maxwell::NumVertexArrays> bindings;
- std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
+ Maxwell::Blend::Factor SourceRGBFactor() const noexcept {
+ return UnpackBlendFactor(factor_source_rgb.Value());
+ }
- std::size_t Hash() const noexcept;
+ Maxwell::Blend::Factor DestRGBFactor() const noexcept {
+ return UnpackBlendFactor(factor_dest_rgb.Value());
+ }
- bool operator==(const VertexInput& rhs) const noexcept;
+ Maxwell::Blend::Factor SourceAlphaFactor() const noexcept {
+ return UnpackBlendFactor(factor_source_a.Value());
+ }
- bool operator!=(const VertexInput& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::Blend::Factor DestAlphaFactor() const noexcept {
+ return UnpackBlendFactor(factor_dest_a.Value());
}
};
- struct InputAssembly {
- constexpr InputAssembly(Maxwell::PrimitiveTopology topology, bool primitive_restart_enable,
- float point_size)
- : topology{topology}, primitive_restart_enable{primitive_restart_enable},
- point_size{point_size} {}
- InputAssembly() = default;
-
- Maxwell::PrimitiveTopology topology;
- bool primitive_restart_enable;
- float point_size;
+ union VertexAttribute {
+ u32 raw;
+ BitField<0, 1, u32> enabled;
+ BitField<1, 5, u32> buffer;
+ BitField<6, 14, u32> offset;
+ BitField<20, 3, u32> type;
+ BitField<23, 6, u32> size;
- std::size_t Hash() const noexcept;
-
- bool operator==(const InputAssembly& rhs) const noexcept;
+ constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
+ return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
+ }
- bool operator!=(const InputAssembly& rhs) const noexcept {
- return !operator==(rhs);
+ constexpr Maxwell::VertexAttribute::Size Size() const noexcept {
+ return static_cast<Maxwell::VertexAttribute::Size>(size.Value());
}
};
- struct Tessellation {
- constexpr Tessellation(u32 patch_control_points, Maxwell::TessellationPrimitive primitive,
- Maxwell::TessellationSpacing spacing, bool clockwise)
- : patch_control_points{patch_control_points}, primitive{primitive}, spacing{spacing},
- clockwise{clockwise} {}
- Tessellation() = default;
+ template <std::size_t Position>
+ union StencilFace {
+ BitField<Position + 0, 3, u32> action_stencil_fail;
+ BitField<Position + 3, 3, u32> action_depth_fail;
+ BitField<Position + 6, 3, u32> action_depth_pass;
+ BitField<Position + 9, 3, u32> test_func;
- u32 patch_control_points;
- Maxwell::TessellationPrimitive primitive;
- Maxwell::TessellationSpacing spacing;
- bool clockwise;
+ Maxwell::StencilOp ActionStencilFail() const noexcept {
+ return UnpackStencilOp(action_stencil_fail);
+ }
- std::size_t Hash() const noexcept;
+ Maxwell::StencilOp ActionDepthFail() const noexcept {
+ return UnpackStencilOp(action_depth_fail);
+ }
- bool operator==(const Tessellation& rhs) const noexcept;
+ Maxwell::StencilOp ActionDepthPass() const noexcept {
+ return UnpackStencilOp(action_depth_pass);
+ }
- bool operator!=(const Tessellation& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::ComparisonOp TestFunc() const noexcept {
+ return UnpackComparisonOp(test_func);
}
};
- struct Rasterizer {
- constexpr Rasterizer(bool cull_enable, bool depth_bias_enable, bool depth_clamp_enable,
- bool ndc_minus_one_to_one, Maxwell::CullFace cull_face,
- Maxwell::FrontFace front_face)
- : cull_enable{cull_enable}, depth_bias_enable{depth_bias_enable},
- depth_clamp_enable{depth_clamp_enable}, ndc_minus_one_to_one{ndc_minus_one_to_one},
- cull_face{cull_face}, front_face{front_face} {}
- Rasterizer() = default;
-
- bool cull_enable;
- bool depth_bias_enable;
- bool depth_clamp_enable;
- bool ndc_minus_one_to_one;
- Maxwell::CullFace cull_face;
- Maxwell::FrontFace front_face;
-
- std::size_t Hash() const noexcept;
+ union VertexBinding {
+ u16 raw;
+ BitField<0, 12, u16> stride;
+ BitField<12, 1, u16> enabled;
+ };
- bool operator==(const Rasterizer& rhs) const noexcept;
+ struct DynamicState {
+ union {
+ u32 raw1;
+ StencilFace<0> front;
+ StencilFace<12> back;
+ BitField<24, 1, u32> stencil_enable;
+ BitField<25, 1, u32> depth_write_enable;
+ BitField<26, 1, u32> depth_bounds_enable;
+ BitField<27, 1, u32> depth_test_enable;
+ BitField<28, 1, u32> front_face;
+ BitField<29, 3, u32> depth_test_func;
+ };
+ union {
+ u32 raw2;
+ BitField<0, 2, u32> cull_face;
+ BitField<2, 1, u32> cull_enable;
+ };
+ std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings;
+
+ void Fill(const Maxwell& regs);
+
+ Maxwell::ComparisonOp DepthTestFunc() const noexcept {
+ return UnpackComparisonOp(depth_test_func);
+ }
- bool operator!=(const Rasterizer& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::CullFace CullFace() const noexcept {
+ return UnpackCullFace(cull_face.Value());
}
- };
- struct DepthStencil {
- constexpr DepthStencil(bool depth_test_enable, bool depth_write_enable,
- bool depth_bounds_enable, bool stencil_enable,
- Maxwell::ComparisonOp depth_test_function, StencilFace front_stencil,
- StencilFace back_stencil)
- : depth_test_enable{depth_test_enable}, depth_write_enable{depth_write_enable},
- depth_bounds_enable{depth_bounds_enable}, stencil_enable{stencil_enable},
- depth_test_function{depth_test_function}, front_stencil{front_stencil},
- back_stencil{back_stencil} {}
- DepthStencil() = default;
-
- bool depth_test_enable;
- bool depth_write_enable;
- bool depth_bounds_enable;
- bool stencil_enable;
- Maxwell::ComparisonOp depth_test_function;
- StencilFace front_stencil;
- StencilFace back_stencil;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const DepthStencil& rhs) const noexcept;
-
- bool operator!=(const DepthStencil& rhs) const noexcept {
- return !operator==(rhs);
+ Maxwell::FrontFace FrontFace() const noexcept {
+ return UnpackFrontFace(front_face.Value());
}
};
- struct ColorBlending {
- constexpr ColorBlending(
- std::array<float, 4> blend_constants, std::size_t attachments_count,
- std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments)
- : attachments_count{attachments_count}, attachments{attachments} {}
- ColorBlending() = default;
-
- std::size_t attachments_count;
- std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const ColorBlending& rhs) const noexcept;
-
- bool operator!=(const ColorBlending& rhs) const noexcept {
- return !operator==(rhs);
- }
+ union {
+ u32 raw;
+ BitField<0, 1, u32> no_extended_dynamic_state;
+ BitField<2, 1, u32> primitive_restart_enable;
+ BitField<3, 1, u32> depth_bias_enable;
+ BitField<4, 1, u32> depth_clamp_disabled;
+ BitField<5, 1, u32> ndc_minus_one_to_one;
+ BitField<6, 2, u32> polygon_mode;
+ BitField<8, 5, u32> patch_control_points_minus_one;
+ BitField<13, 2, u32> tessellation_primitive;
+ BitField<15, 2, u32> tessellation_spacing;
+ BitField<17, 1, u32> tessellation_clockwise;
+ BitField<18, 1, u32> logic_op_enable;
+ BitField<19, 4, u32> logic_op;
+ BitField<23, 1, u32> rasterize_enable;
+ BitField<24, 4, Maxwell::PrimitiveTopology> topology;
};
+ u32 point_size;
+ std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
+ std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
+ std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
+ std::array<u16, Maxwell::NumViewports> viewport_swizzles;
+ DynamicState dynamic_state;
+
+ void Fill(const Maxwell& regs, bool has_extended_dynamic_state);
std::size_t Hash() const noexcept;
@@ -249,26 +204,14 @@ struct FixedPipelineState {
return !operator==(rhs);
}
- VertexInput vertex_input;
- InputAssembly input_assembly;
- Tessellation tessellation;
- Rasterizer rasterizer;
- DepthStencil depth_stencil;
- ColorBlending color_blending;
+ std::size_t Size() const noexcept {
+ const std::size_t total_size = sizeof *this;
+ return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
+ }
};
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexBinding>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexAttribute>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::StencilFace>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::BlendingAttachment>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexInput>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::InputAssembly>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::Tessellation>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::Rasterizer>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::DepthStencil>);
-static_assert(std::is_trivially_copyable_v<FixedPipelineState::ColorBlending>);
+static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
-
-FixedPipelineState GetFixedPipelineState(const Maxwell& regs);
+static_assert(std::is_trivially_constructible_v<FixedPipelineState>);
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 8681b821f..d22de1d81 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -21,29 +21,29 @@ namespace Sampler {
VkFilter Filter(Tegra::Texture::TextureFilter filter) {
switch (filter) {
- case Tegra::Texture::TextureFilter::Linear:
- return VK_FILTER_LINEAR;
case Tegra::Texture::TextureFilter::Nearest:
return VK_FILTER_NEAREST;
+ case Tegra::Texture::TextureFilter::Linear:
+ return VK_FILTER_LINEAR;
}
- UNIMPLEMENTED_MSG("Unimplemented sampler filter={}", static_cast<u32>(filter));
+ UNREACHABLE_MSG("Invalid sampler filter={}", static_cast<u32>(filter));
return {};
}
VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter) {
switch (mipmap_filter) {
case Tegra::Texture::TextureMipmapFilter::None:
- // TODO(Rodrigo): None seems to be mapped to OpenGL's mag and min filters without mipmapping
- // (e.g. GL_NEAREST and GL_LINEAR). Vulkan doesn't have such a thing, find out if we have to
- // use an image view with a single mipmap level to emulate this.
- return VK_SAMPLER_MIPMAP_MODE_LINEAR;
- ;
- case Tegra::Texture::TextureMipmapFilter::Linear:
- return VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ // There are no Vulkan filter modes that directly correspond to OpenGL minification filters
+ // of GL_LINEAR or GL_NEAREST, but they can be emulated using
+ // VK_SAMPLER_MIPMAP_MODE_NEAREST, minLod = 0, and maxLod = 0.25, and using minFilter =
+ // VK_FILTER_LINEAR or minFilter = VK_FILTER_NEAREST, respectively.
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case Tegra::Texture::TextureMipmapFilter::Nearest:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ case Tegra::Texture::TextureMipmapFilter::Linear:
+ return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
- UNIMPLEMENTED_MSG("Unimplemented sampler mipmap mode={}", static_cast<u32>(mipmap_filter));
+ UNREACHABLE_MSG("Invalid sampler mipmap mode={}", static_cast<u32>(mipmap_filter));
return {};
}
@@ -118,89 +118,101 @@ struct FormatTuple {
VkFormat format; ///< Vulkan format
int usage = 0; ///< Describes image format usage
} constexpr tex_format_tuples[] = {
- {VK_FORMAT_A8B8G8R8_UNORM_PACK32, Attachable | Storage}, // ABGR8U
- {VK_FORMAT_A8B8G8R8_SNORM_PACK32, Attachable | Storage}, // ABGR8S
- {VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage}, // ABGR8UI
- {VK_FORMAT_B5G6R5_UNORM_PACK16}, // B5G6R5U
- {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10U
- {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5U (flipped with swizzle)
- {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8U
- {VK_FORMAT_R8_UINT, Attachable | Storage}, // R8UI
- {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // RGBA16F
- {VK_FORMAT_R16G16B16A16_UNORM, Attachable | Storage}, // RGBA16U
- {VK_FORMAT_R16G16B16A16_SNORM, Attachable | Storage}, // RGBA16S
- {VK_FORMAT_R16G16B16A16_UINT, Attachable | Storage}, // RGBA16UI
- {VK_FORMAT_B10G11R11_UFLOAT_PACK32, Attachable | Storage}, // R11FG11FB10F
- {VK_FORMAT_R32G32B32A32_UINT, Attachable | Storage}, // RGBA32UI
- {VK_FORMAT_BC1_RGBA_UNORM_BLOCK}, // DXT1
- {VK_FORMAT_BC2_UNORM_BLOCK}, // DXT23
- {VK_FORMAT_BC3_UNORM_BLOCK}, // DXT45
- {VK_FORMAT_BC4_UNORM_BLOCK}, // DXN1
- {VK_FORMAT_BC5_UNORM_BLOCK}, // DXN2UNORM
- {VK_FORMAT_BC5_SNORM_BLOCK}, // DXN2SNORM
- {VK_FORMAT_BC7_UNORM_BLOCK}, // BC7U
- {VK_FORMAT_BC6H_UFLOAT_BLOCK}, // BC6H_UF16
- {VK_FORMAT_BC6H_SFLOAT_BLOCK}, // BC6H_SF16
- {VK_FORMAT_ASTC_4x4_UNORM_BLOCK}, // ASTC_2D_4X4
- {VK_FORMAT_B8G8R8A8_UNORM}, // BGRA8
- {VK_FORMAT_R32G32B32A32_SFLOAT, Attachable | Storage}, // RGBA32F
- {VK_FORMAT_R32G32_SFLOAT, Attachable | Storage}, // RG32F
- {VK_FORMAT_R32_SFLOAT, Attachable | Storage}, // R32F
- {VK_FORMAT_R16_SFLOAT, Attachable | Storage}, // R16F
- {VK_FORMAT_R16_UNORM, Attachable | Storage}, // R16U
- {VK_FORMAT_UNDEFINED}, // R16S
- {VK_FORMAT_UNDEFINED}, // R16UI
- {VK_FORMAT_UNDEFINED}, // R16I
- {VK_FORMAT_R16G16_UNORM, Attachable | Storage}, // RG16
- {VK_FORMAT_R16G16_SFLOAT, Attachable | Storage}, // RG16F
- {VK_FORMAT_UNDEFINED}, // RG16UI
- {VK_FORMAT_UNDEFINED}, // RG16I
- {VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // RG16S
- {VK_FORMAT_UNDEFINED}, // RGB32F
- {VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // RGBA8_SRGB
- {VK_FORMAT_R8G8_UNORM, Attachable | Storage}, // RG8U
- {VK_FORMAT_R8G8_SNORM, Attachable | Storage}, // RG8S
- {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // RG32UI
- {VK_FORMAT_UNDEFINED}, // RGBX16F
- {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32UI
- {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32I
- {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8
- {VK_FORMAT_UNDEFINED}, // ASTC_2D_8X5
- {VK_FORMAT_UNDEFINED}, // ASTC_2D_5X4
- {VK_FORMAT_UNDEFINED}, // BGRA8_SRGB
- {VK_FORMAT_BC1_RGBA_SRGB_BLOCK}, // DXT1_SRGB
- {VK_FORMAT_BC2_SRGB_BLOCK}, // DXT23_SRGB
- {VK_FORMAT_BC3_SRGB_BLOCK}, // DXT45_SRGB
- {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7U_SRGB
- {VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // R4G4B4A4U
- {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
- {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
- {VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB
- {VK_FORMAT_ASTC_5x4_SRGB_BLOCK}, // ASTC_2D_5X4_SRGB
- {VK_FORMAT_ASTC_5x5_UNORM_BLOCK}, // ASTC_2D_5X5
- {VK_FORMAT_ASTC_5x5_SRGB_BLOCK}, // ASTC_2D_5X5_SRGB
- {VK_FORMAT_ASTC_10x8_UNORM_BLOCK}, // ASTC_2D_10X8
- {VK_FORMAT_ASTC_10x8_SRGB_BLOCK}, // ASTC_2D_10X8_SRGB
- {VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6
- {VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB
- {VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10
- {VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB
- {VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12
- {VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB
- {VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6
- {VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB
- {VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5
- {VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB
- {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9F
+ {VK_FORMAT_A8B8G8R8_UNORM_PACK32, Attachable | Storage}, // A8B8G8R8_UNORM
+ {VK_FORMAT_A8B8G8R8_SNORM_PACK32, Attachable | Storage}, // A8B8G8R8_SNORM
+ {VK_FORMAT_A8B8G8R8_SINT_PACK32, Attachable | Storage}, // A8B8G8R8_SINT
+ {VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage}, // A8B8G8R8_UINT
+ {VK_FORMAT_R5G6B5_UNORM_PACK16, Attachable}, // R5G6B5_UNORM
+ {VK_FORMAT_B5G6R5_UNORM_PACK16, Attachable}, // B5G6R5_UNORM
+ {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM
+ {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
+ {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
+ {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle)
+ {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM
+ {VK_FORMAT_R8_SNORM, Attachable | Storage}, // R8_SNORM
+ {VK_FORMAT_R8_SINT, Attachable | Storage}, // R8_SINT
+ {VK_FORMAT_R8_UINT, Attachable | Storage}, // R8_UINT
+ {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16A16_FLOAT
+ {VK_FORMAT_R16G16B16A16_UNORM, Attachable | Storage}, // R16G16B16A16_UNORM
+ {VK_FORMAT_R16G16B16A16_SNORM, Attachable | Storage}, // R16G16B16A16_SNORM
+ {VK_FORMAT_R16G16B16A16_SINT, Attachable | Storage}, // R16G16B16A16_SINT
+ {VK_FORMAT_R16G16B16A16_UINT, Attachable | Storage}, // R16G16B16A16_UINT
+ {VK_FORMAT_B10G11R11_UFLOAT_PACK32, Attachable | Storage}, // B10G11R11_FLOAT
+ {VK_FORMAT_R32G32B32A32_UINT, Attachable | Storage}, // R32G32B32A32_UINT
+ {VK_FORMAT_BC1_RGBA_UNORM_BLOCK}, // BC1_RGBA_UNORM
+ {VK_FORMAT_BC2_UNORM_BLOCK}, // BC2_UNORM
+ {VK_FORMAT_BC3_UNORM_BLOCK}, // BC3_UNORM
+ {VK_FORMAT_BC4_UNORM_BLOCK}, // BC4_UNORM
+ {VK_FORMAT_BC4_SNORM_BLOCK}, // BC4_SNORM
+ {VK_FORMAT_BC5_UNORM_BLOCK}, // BC5_UNORM
+ {VK_FORMAT_BC5_SNORM_BLOCK}, // BC5_SNORM
+ {VK_FORMAT_BC7_UNORM_BLOCK}, // BC7_UNORM
+ {VK_FORMAT_BC6H_UFLOAT_BLOCK}, // BC6H_UFLOAT
+ {VK_FORMAT_BC6H_SFLOAT_BLOCK}, // BC6H_SFLOAT
+ {VK_FORMAT_ASTC_4x4_UNORM_BLOCK}, // ASTC_2D_4X4_UNORM
+ {VK_FORMAT_B8G8R8A8_UNORM, Attachable}, // B8G8R8A8_UNORM
+ {VK_FORMAT_R32G32B32A32_SFLOAT, Attachable | Storage}, // R32G32B32A32_FLOAT
+ {VK_FORMAT_R32G32B32A32_SINT, Attachable | Storage}, // R32G32B32A32_SINT
+ {VK_FORMAT_R32G32_SFLOAT, Attachable | Storage}, // R32G32_FLOAT
+ {VK_FORMAT_R32G32_SINT, Attachable | Storage}, // R32G32_SINT
+ {VK_FORMAT_R32_SFLOAT, Attachable | Storage}, // R32_FLOAT
+ {VK_FORMAT_R16_SFLOAT, Attachable | Storage}, // R16_FLOAT
+ {VK_FORMAT_R16_UNORM, Attachable | Storage}, // R16_UNORM
+ {VK_FORMAT_UNDEFINED}, // R16_SNORM
+ {VK_FORMAT_R16_UINT, Attachable | Storage}, // R16_UINT
+ {VK_FORMAT_UNDEFINED}, // R16_SINT
+ {VK_FORMAT_R16G16_UNORM, Attachable | Storage}, // R16G16_UNORM
+ {VK_FORMAT_R16G16_SFLOAT, Attachable | Storage}, // R16G16_FLOAT
+ {VK_FORMAT_UNDEFINED}, // R16G16_UINT
+ {VK_FORMAT_UNDEFINED}, // R16G16_SINT
+ {VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // R16G16_SNORM
+ {VK_FORMAT_UNDEFINED}, // R32G32B32_FLOAT
+ {VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // A8B8G8R8_SRGB
+ {VK_FORMAT_R8G8_UNORM, Attachable | Storage}, // R8G8_UNORM
+ {VK_FORMAT_R8G8_SNORM, Attachable | Storage}, // R8G8_SNORM
+ {VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT
+ {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT
+ {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT
+ {VK_FORMAT_UNDEFINED}, // R16G16B16X16_FLOAT
+ {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT
+ {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT
+ {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM
+ {VK_FORMAT_UNDEFINED}, // ASTC_2D_8X5_UNORM
+ {VK_FORMAT_UNDEFINED}, // ASTC_2D_5X4_UNORM
+ {VK_FORMAT_B8G8R8A8_SRGB, Attachable}, // B8G8R8A8_SRGB
+ {VK_FORMAT_BC1_RGBA_SRGB_BLOCK}, // BC1_RGBA_SRGB
+ {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
+ {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
+ {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
+ {VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // A4B4G4R4_UNORM
+ {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
+ {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
+ {VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB
+ {VK_FORMAT_ASTC_5x4_SRGB_BLOCK}, // ASTC_2D_5X4_SRGB
+ {VK_FORMAT_ASTC_5x5_UNORM_BLOCK}, // ASTC_2D_5X5_UNORM
+ {VK_FORMAT_ASTC_5x5_SRGB_BLOCK}, // ASTC_2D_5X5_SRGB
+ {VK_FORMAT_ASTC_10x8_UNORM_BLOCK}, // ASTC_2D_10X8_UNORM
+ {VK_FORMAT_ASTC_10x8_SRGB_BLOCK}, // ASTC_2D_10X8_SRGB
+ {VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6_UNORM
+ {VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB
+ {VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10_UNORM
+ {VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB
+ {VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12_UNORM
+ {VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB
+ {VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6_UNORM
+ {VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB
+ {VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5_UNORM
+ {VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB
+ {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
// Depth formats
- {VK_FORMAT_D32_SFLOAT, Attachable}, // Z32F
- {VK_FORMAT_D16_UNORM, Attachable}, // Z16
+ {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
+ {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
// DepthStencil formats
- {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // Z24S8
- {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // S8Z24 (emulated)
- {VK_FORMAT_D32_SFLOAT_S8_UINT, Attachable}, // Z32FS8
+ {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // D24_UNORM_S8_UINT
+ {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // S8_UINT_D24_UNORM (emulated)
+ {VK_FORMAT_D32_SFLOAT_S8_UINT, Attachable}, // D32_FLOAT_S8_UINT
};
static_assert(std::size(tex_format_tuples) == VideoCore::Surface::MaxPixelFormat);
@@ -221,7 +233,7 @@ FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFo
return {VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true};
}
- // Use ABGR8 on hardware that doesn't support ASTC natively
+ // Use A8B8G8R8_UNORM on hardware that doesn't support ASTC natively
if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) {
tuple.format = VideoCore::Surface::IsPixelFormatSRGB(pixel_format)
? VK_FORMAT_A8B8G8R8_SRGB_PACK32
@@ -295,6 +307,30 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device,
VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
switch (type) {
+ case Maxwell::VertexAttribute::Type::UnsignedNorm:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8:
+ return VK_FORMAT_R8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8:
+ return VK_FORMAT_R8G8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8:
+ return VK_FORMAT_R8G8B8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_UNORM;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ default:
+ break;
+ }
+ break;
case Maxwell::VertexAttribute::Type::SignedNorm:
switch (size) {
case Maxwell::VertexAttribute::Size::Size_8:
@@ -319,44 +355,50 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
break;
}
break;
- case Maxwell::VertexAttribute::Type::UnsignedNorm:
+ case Maxwell::VertexAttribute::Type::UnsignedScaled:
switch (size) {
case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_UNORM;
+ return VK_FORMAT_R8_USCALED;
case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_UNORM;
+ return VK_FORMAT_R8G8_USCALED;
case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_UNORM;
+ return VK_FORMAT_R8G8B8_USCALED;
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_UNORM;
+ return VK_FORMAT_R8G8B8A8_USCALED;
case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_UNORM;
+ return VK_FORMAT_R16_USCALED;
case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_UNORM;
+ return VK_FORMAT_R16G16_USCALED;
case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_UNORM;
+ return VK_FORMAT_R16G16B16_USCALED;
case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_UNORM;
+ return VK_FORMAT_R16G16B16A16_USCALED;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
- return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
default:
break;
}
break;
- case Maxwell::VertexAttribute::Type::SignedInt:
+ case Maxwell::VertexAttribute::Type::SignedScaled:
switch (size) {
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SINT;
case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_SINT;
+ return VK_FORMAT_R8_SSCALED;
case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_SINT;
+ return VK_FORMAT_R8G8_SSCALED;
case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_SINT;
+ return VK_FORMAT_R8G8B8_SSCALED;
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_SINT;
- case Maxwell::VertexAttribute::Size::Size_32:
- return VK_FORMAT_R32_SINT;
+ return VK_FORMAT_R8G8B8A8_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16:
+ return VK_FORMAT_R16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ return VK_FORMAT_R16G16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ return VK_FORMAT_R16G16B16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return VK_FORMAT_R16G16B16A16_SSCALED;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
default:
break;
}
@@ -387,56 +429,54 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32_UINT;
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return VK_FORMAT_R32G32B32A32_UINT;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_UINT_PACK32;
default:
break;
}
break;
- case Maxwell::VertexAttribute::Type::UnsignedScaled:
+ case Maxwell::VertexAttribute::Type::SignedInt:
switch (size) {
case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_USCALED;
+ return VK_FORMAT_R8_SINT;
case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_USCALED;
+ return VK_FORMAT_R8G8_SINT;
case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_USCALED;
+ return VK_FORMAT_R8G8B8_SINT;
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_USCALED;
+ return VK_FORMAT_R8G8B8A8_SINT;
case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_USCALED;
+ return VK_FORMAT_R16_SINT;
case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_USCALED;
+ return VK_FORMAT_R16G16_SINT;
case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_USCALED;
+ return VK_FORMAT_R16G16B16_SINT;
case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_USCALED;
+ return VK_FORMAT_R16G16B16A16_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return VK_FORMAT_R32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ return VK_FORMAT_R32G32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ return VK_FORMAT_R32G32B32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return VK_FORMAT_R32G32B32A32_SINT;
+ case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
+ return VK_FORMAT_A2B10G10R10_SINT_PACK32;
default:
break;
}
break;
- case Maxwell::VertexAttribute::Type::SignedScaled:
+ case Maxwell::VertexAttribute::Type::Float:
switch (size) {
- case Maxwell::VertexAttribute::Size::Size_8:
- return VK_FORMAT_R8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8:
- return VK_FORMAT_R8G8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8:
- return VK_FORMAT_R8G8B8_SSCALED;
- case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
- return VK_FORMAT_R8G8B8A8_SSCALED;
case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SSCALED;
+ return VK_FORMAT_R16_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SSCALED;
+ return VK_FORMAT_R16G16_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SSCALED;
+ return VK_FORMAT_R16G16B16_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SSCALED;
- default:
- break;
- }
- break;
- case Maxwell::VertexAttribute::Type::Float:
- switch (size) {
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_32:
return VK_FORMAT_R32_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_32_32:
@@ -445,14 +485,6 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return VK_FORMAT_R32G32B32A32_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16:
- return VK_FORMAT_R16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16:
- return VK_FORMAT_R16G16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16:
- return VK_FORMAT_R16G16B16_SFLOAT;
- case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
- return VK_FORMAT_R16G16B16A16_SFLOAT;
default:
break;
}
@@ -672,4 +704,27 @@ VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) {
return {};
}
+VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) {
+ switch (swizzle) {
+ case Maxwell::ViewportSwizzle::PositiveX:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV;
+ case Maxwell::ViewportSwizzle::NegativeX:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_X_NV;
+ case Maxwell::ViewportSwizzle::PositiveY:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Y_NV;
+ case Maxwell::ViewportSwizzle::NegativeY:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Y_NV;
+ case Maxwell::ViewportSwizzle::PositiveZ:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Z_NV;
+ case Maxwell::ViewportSwizzle::NegativeZ:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Z_NV;
+ case Maxwell::ViewportSwizzle::PositiveW:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_W_NV;
+ case Maxwell::ViewportSwizzle::NegativeW:
+ return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV;
+ }
+ UNREACHABLE_MSG("Invalid swizzle={}", static_cast<int>(swizzle));
+ return {};
+}
+
} // namespace Vulkan::MaxwellToVK
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h
index 81bce4c6c..7e213452f 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.h
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -59,4 +59,6 @@ VkCullModeFlags CullFace(Maxwell::CullFace cull_face);
VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle);
+VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle);
+
} // namespace Vulkan::MaxwellToVK
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
new file mode 100644
index 000000000..5b01020ec
--- /dev/null
+++ b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
@@ -0,0 +1,220 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef HAS_NSIGHT_AFTERMATH
+
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#define VK_NO_PROTOTYPES
+#include <vulkan/vulkan.h>
+
+#include <GFSDK_Aftermath.h>
+#include <GFSDK_Aftermath_Defines.h>
+#include <GFSDK_Aftermath_GpuCrashDump.h>
+#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
+
+#include "common/common_paths.h"
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+
+#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
+
+namespace Vulkan {
+
+static constexpr char AFTERMATH_LIB_NAME[] = "GFSDK_Aftermath_Lib.x64.dll";
+
+NsightAftermathTracker::NsightAftermathTracker() = default;
+
+NsightAftermathTracker::~NsightAftermathTracker() {
+ if (initialized) {
+ (void)GFSDK_Aftermath_DisableGpuCrashDumps();
+ }
+}
+
+bool NsightAftermathTracker::Initialize() {
+ if (!dl.Open(AFTERMATH_LIB_NAME)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath DLL");
+ return false;
+ }
+
+ if (!dl.GetSymbol("GFSDK_Aftermath_DisableGpuCrashDumps",
+ &GFSDK_Aftermath_DisableGpuCrashDumps) ||
+ !dl.GetSymbol("GFSDK_Aftermath_EnableGpuCrashDumps",
+ &GFSDK_Aftermath_EnableGpuCrashDumps) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GetShaderDebugInfoIdentifier",
+ &GFSDK_Aftermath_GetShaderDebugInfoIdentifier) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GetShaderHashSpirv", &GFSDK_Aftermath_GetShaderHashSpirv) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_CreateDecoder",
+ &GFSDK_Aftermath_GpuCrashDump_CreateDecoder) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_DestroyDecoder",
+ &GFSDK_Aftermath_GpuCrashDump_DestroyDecoder) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GenerateJSON",
+ &GFSDK_Aftermath_GpuCrashDump_GenerateJSON) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GetJSON",
+ &GFSDK_Aftermath_GpuCrashDump_GetJSON)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers");
+ return false;
+ }
+
+ dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash";
+
+ (void)Common::FS::DeleteDirRecursively(dump_dir);
+ if (!Common::FS::CreateDir(dump_dir)) {
+ LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
+ return false;
+ }
+
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps(
+ GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
+ GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback,
+ ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) {
+ LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
+ return false;
+ }
+
+ LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir);
+
+ initialized = true;
+ return true;
+}
+
+void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
+ if (!initialized) {
+ return;
+ }
+
+ std::vector<u32> spirv_copy = spirv;
+ GFSDK_Aftermath_SpirvCode shader;
+ shader.pData = spirv_copy.data();
+ shader.size = static_cast<u32>(spirv_copy.size() * 4);
+
+ std::scoped_lock lock{mutex};
+
+ GFSDK_Aftermath_ShaderHash hash;
+ if (!GFSDK_Aftermath_SUCCEED(
+ GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) {
+ LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module");
+ return;
+ }
+
+ Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
+ if (!file.IsOpen()) {
+ LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
+ return;
+ }
+ if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) {
+ LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash);
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
+ u32 gpu_crash_dump_size) {
+ std::scoped_lock lock{mutex};
+
+ LOG_CRITICAL(Render_Vulkan, "called");
+
+ GFSDK_Aftermath_GpuCrashDump_Decoder decoder;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_CreateDecoder(
+ GFSDK_Aftermath_Version_API, gpu_crash_dump, gpu_crash_dump_size, &decoder))) {
+ LOG_ERROR(Render_Vulkan, "Failed to create decoder");
+ return;
+ }
+ SCOPE_EXIT({ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder); });
+
+ u32 json_size = 0;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
+ decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
+ GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr,
+ this, &json_size))) {
+ LOG_ERROR(Render_Vulkan, "Failed to generate JSON");
+ return;
+ }
+ std::vector<char> json(json_size);
+ if (!GFSDK_Aftermath_SUCCEED(
+ GFSDK_Aftermath_GpuCrashDump_GetJSON(decoder, json_size, json.data()))) {
+ LOG_ERROR(Render_Vulkan, "Failed to query JSON");
+ return;
+ }
+
+ const std::string base_name = [this] {
+ const int id = dump_id++;
+ if (id == 0) {
+ return fmt::format("{}/crash.nv-gpudmp", dump_dir);
+ } else {
+ return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id);
+ }
+ }();
+
+ std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
+ if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
+ LOG_ERROR(Render_Vulkan, "Failed to write dump file");
+ return;
+ }
+ const std::string_view json_view(json.data(), json.size());
+ if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
+ LOG_ERROR(Render_Vulkan, "Failed to write JSON");
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_info,
+ u32 shader_debug_info_size) {
+ std::scoped_lock lock{mutex};
+
+ GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(
+ GFSDK_Aftermath_Version_API, shader_debug_info, shader_debug_info_size, &identifier))) {
+ LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_GetShaderDebugInfoIdentifier failed");
+ return;
+ }
+
+ const std::string path =
+ fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
+ Common::FS::IOFile file(path, "wb");
+ if (!file.IsOpen()) {
+ LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
+ return;
+ }
+ if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) !=
+ shader_debug_info_size) {
+ LOG_ERROR(Render_Vulkan, "Failed to write file {}", path);
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnCrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description) {
+ add_description(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, "yuzu");
+}
+
+void NsightAftermathTracker::GpuCrashDumpCallback(const void* gpu_crash_dump,
+ u32 gpu_crash_dump_size, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnGpuCrashDumpCallback(gpu_crash_dump,
+ gpu_crash_dump_size);
+}
+
+void NsightAftermathTracker::ShaderDebugInfoCallback(const void* shader_debug_info,
+ u32 shader_debug_info_size, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnShaderDebugInfoCallback(
+ shader_debug_info, shader_debug_info_size);
+}
+
+void NsightAftermathTracker::CrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnCrashDumpDescriptionCallback(
+ add_description);
+}
+
+} // namespace Vulkan
+
+#endif // HAS_NSIGHT_AFTERMATH
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h
new file mode 100644
index 000000000..afe7ae99e
--- /dev/null
+++ b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h
@@ -0,0 +1,87 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <mutex>
+#include <string>
+#include <vector>
+
+#define VK_NO_PROTOTYPES
+#include <vulkan/vulkan.h>
+
+#ifdef HAS_NSIGHT_AFTERMATH
+#include <GFSDK_Aftermath_Defines.h>
+#include <GFSDK_Aftermath_GpuCrashDump.h>
+#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
+#endif
+
+#include "common/common_types.h"
+#include "common/dynamic_library.h"
+
+namespace Vulkan {
+
+class NsightAftermathTracker {
+public:
+ NsightAftermathTracker();
+ ~NsightAftermathTracker();
+
+ NsightAftermathTracker(const NsightAftermathTracker&) = delete;
+ NsightAftermathTracker& operator=(const NsightAftermathTracker&) = delete;
+
+ // Delete move semantics because Aftermath initialization uses a pointer to this.
+ NsightAftermathTracker(NsightAftermathTracker&&) = delete;
+ NsightAftermathTracker& operator=(NsightAftermathTracker&&) = delete;
+
+ bool Initialize();
+
+ void SaveShader(const std::vector<u32>& spirv) const;
+
+private:
+#ifdef HAS_NSIGHT_AFTERMATH
+ static void GpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size,
+ void* user_data);
+
+ static void ShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size,
+ void* user_data);
+
+ static void CrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data);
+
+ void OnGpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size);
+
+ void OnShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size);
+
+ void OnCrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description);
+
+ mutable std::mutex mutex;
+
+ std::string dump_dir;
+ int dump_id = 0;
+
+ bool initialized = false;
+
+ Common::DynamicLibrary dl;
+ PFN_GFSDK_Aftermath_DisableGpuCrashDumps GFSDK_Aftermath_DisableGpuCrashDumps;
+ PFN_GFSDK_Aftermath_EnableGpuCrashDumps GFSDK_Aftermath_EnableGpuCrashDumps;
+ PFN_GFSDK_Aftermath_GetShaderDebugInfoIdentifier GFSDK_Aftermath_GetShaderDebugInfoIdentifier;
+ PFN_GFSDK_Aftermath_GetShaderHashSpirv GFSDK_Aftermath_GetShaderHashSpirv;
+ PFN_GFSDK_Aftermath_GpuCrashDump_CreateDecoder GFSDK_Aftermath_GpuCrashDump_CreateDecoder;
+ PFN_GFSDK_Aftermath_GpuCrashDump_DestroyDecoder GFSDK_Aftermath_GpuCrashDump_DestroyDecoder;
+ PFN_GFSDK_Aftermath_GpuCrashDump_GenerateJSON GFSDK_Aftermath_GpuCrashDump_GenerateJSON;
+ PFN_GFSDK_Aftermath_GpuCrashDump_GetJSON GFSDK_Aftermath_GpuCrashDump_GetJSON;
+#endif
+};
+
+#ifndef HAS_NSIGHT_AFTERMATH
+inline NsightAftermathTracker::NsightAftermathTracker() = default;
+inline NsightAftermathTracker::~NsightAftermathTracker() = default;
+inline bool NsightAftermathTracker::Initialize() {
+ return false;
+}
+inline void NsightAftermathTracker::SaveShader(const std::vector<u32>&) const {}
+#endif
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index dd590c38b..f2610868e 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -12,24 +12,22 @@
#include <fmt/format.h>
-#include "common/assert.h"
#include "common/dynamic_library.h"
+#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/telemetry.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
-#include "core/memory.h"
-#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
#include "video_core/gpu.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -42,7 +40,7 @@
#include <vulkan/vulkan_win32.h>
#endif
-#ifdef __linux__
+#if !defined(_WIN32) && !defined(__APPLE__)
#include <X11/Xlib.h>
#include <vulkan/vulkan_wayland.h>
#include <vulkan/vulkan_xlib.h>
@@ -58,7 +56,7 @@ VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* data,
[[maybe_unused]] void* user_data) {
- const char* message{data->pMessage};
+ const char* const message{data->pMessage};
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
LOG_CRITICAL(Render_Vulkan, "{}", message);
@@ -79,7 +77,8 @@ Common::DynamicLibrary OpenVulkanLibrary() {
char* libvulkan_env = getenv("LIBVULKAN_PATH");
if (!libvulkan_env || !library.Open(libvulkan_env)) {
// Use the libvulkan.dylib from the application bundle.
- std::string filename = File::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
+ const std::string filename =
+ Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
library.Open(filename.c_str());
}
#else
@@ -87,15 +86,15 @@ Common::DynamicLibrary OpenVulkanLibrary() {
if (!library.Open(filename.c_str())) {
// Android devices may not have libvulkan.so.1, only libvulkan.so.
filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
- library.Open(filename.c_str());
+ (void)library.Open(filename.c_str());
}
#endif
return library;
}
-vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
- WindowSystemType window_type = WindowSystemType::Headless,
- bool enable_layers = false) {
+std::pair<vk::Instance, u32> CreateInstance(
+ Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
+ WindowSystemType window_type = WindowSystemType::Headless, bool enable_layers = false) {
if (!library.IsOpen()) {
LOG_ERROR(Render_Vulkan, "Vulkan library not available");
return {};
@@ -119,7 +118,7 @@ vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatc
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
break;
#endif
-#ifdef __linux__
+#if !defined(_WIN32) && !defined(__APPLE__)
case Core::Frontend::WindowSystemType::X11:
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
break;
@@ -156,12 +155,35 @@ vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatc
}
}
- static constexpr std::array layers_data{"VK_LAYER_LUNARG_standard_validation"};
- vk::Span<const char*> layers = layers_data;
- if (!enable_layers) {
- layers = {};
+ std::vector<const char*> layers;
+ layers.reserve(1);
+ if (enable_layers) {
+ layers.push_back("VK_LAYER_KHRONOS_validation");
+ }
+
+ const std::optional layer_properties = vk::EnumerateInstanceLayerProperties(dld);
+ if (!layer_properties) {
+ LOG_ERROR(Render_Vulkan, "Failed to query layer properties, disabling layers");
+ layers.clear();
+ }
+
+ for (auto layer_it = layers.begin(); layer_it != layers.end();) {
+ const char* const layer = *layer_it;
+ const auto it = std::find_if(
+ layer_properties->begin(), layer_properties->end(),
+ [layer](const VkLayerProperties& prop) { return !std::strcmp(layer, prop.layerName); });
+ if (it == layer_properties->end()) {
+ LOG_ERROR(Render_Vulkan, "Layer {} not available, removing it", layer);
+ layer_it = layers.erase(layer_it);
+ } else {
+ ++layer_it;
+ }
}
- vk::Instance instance = vk::Instance::Create(layers, extensions, dld);
+
+ // Limit the maximum version of Vulkan to avoid using untested version.
+ const u32 version = std::min(vk::AvailableVersion(dld), static_cast<u32>(VK_API_VERSION_1_1));
+
+ vk::Instance instance = vk::Instance::Create(version, layers, extensions, dld);
if (!instance) {
LOG_ERROR(Render_Vulkan, "Failed to create Vulkan instance");
return {};
@@ -169,7 +191,7 @@ vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatc
if (!vk::Load(*instance, dld)) {
LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers");
}
- return instance;
+ return std::make_pair(std::move(instance), version);
}
std::string GetReadableVersion(u32 version) {
@@ -218,8 +240,12 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext
} // Anonymous namespace
-RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system)
- : RendererBase(window), system{system} {}
+RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context)
+ : RendererBase{emu_window, std::move(context)}, telemetry_session{telemetry_session_},
+ cpu_memory{cpu_memory_}, gpu{gpu_} {}
RendererVulkan::~RendererVulkan() {
ShutDown();
@@ -246,11 +272,11 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
scheduler->WaitWorker();
swapchain->AcquireNextImage();
- const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
+ const VkSemaphore render_semaphore = blit_screen->Draw(*framebuffer, use_accelerated);
- scheduler->Flush(false, render_semaphore);
+ scheduler->Flush(render_semaphore);
- if (swapchain->Present(render_semaphore, fence)) {
+ if (swapchain->Present(render_semaphore)) {
blit_screen->Recreate();
}
@@ -260,15 +286,10 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
render_window.PollEvents();
}
-bool RendererVulkan::TryPresent(int /*timeout_ms*/) {
- // TODO (bunnei): ImplementMe
- return true;
-}
-
bool RendererVulkan::Init() {
library = OpenVulkanLibrary();
- instance = CreateInstance(library, dld, render_window.GetWindowInfo().type,
- Settings::values.renderer_debug);
+ std::tie(instance, instance_version) = CreateInstance(
+ library, dld, render_window.GetWindowInfo().type, Settings::values.renderer_debug);
if (!instance || !CreateDebugCallback() || !CreateSurface() || !PickDevices()) {
return false;
}
@@ -277,23 +298,21 @@ bool RendererVulkan::Init() {
memory_manager = std::make_unique<VKMemoryManager>(*device);
- resource_manager = std::make_unique<VKResourceManager>(*device);
+ state_tracker = std::make_unique<StateTracker>(gpu);
+
+ scheduler = std::make_unique<VKScheduler>(*device, *state_tracker);
const auto& framebuffer = render_window.GetFramebufferLayout();
- swapchain = std::make_unique<VKSwapchain>(*surface, *device);
+ swapchain = std::make_unique<VKSwapchain>(*surface, *device, *scheduler);
swapchain->Create(framebuffer.width, framebuffer.height, false);
- state_tracker = std::make_unique<StateTracker>(system);
-
- scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker);
-
- rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
- *resource_manager, *memory_manager,
- *state_tracker, *scheduler);
+ rasterizer = std::make_unique<RasterizerVulkan>(render_window, gpu, gpu.MemoryManager(),
+ cpu_memory, screen_info, *device,
+ *memory_manager, *state_tracker, *scheduler);
- blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
- *resource_manager, *memory_manager, *swapchain,
- *scheduler, screen_info);
+ blit_screen =
+ std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device,
+ *memory_manager, *swapchain, *scheduler, screen_info);
return true;
}
@@ -311,7 +330,6 @@ void RendererVulkan::ShutDown() {
scheduler.reset();
swapchain.reset();
memory_manager.reset();
- resource_manager.reset();
device.reset();
}
@@ -345,7 +363,7 @@ bool RendererVulkan::CreateSurface() {
}
}
#endif
-#ifdef __linux__
+#if !defined(_WIN32) && !defined(__APPLE__)
if (window_info.type == Core::Frontend::WindowSystemType::X11) {
const VkXlibSurfaceCreateInfoKHR xlib_ci{
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0,
@@ -390,7 +408,7 @@ bool RendererVulkan::PickDevices() {
return false;
}
- const s32 device_index = Settings::values.vulkan_device;
+ const s32 device_index = Settings::values.vulkan_device.GetValue();
if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) {
LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
return false;
@@ -401,7 +419,8 @@ bool RendererVulkan::PickDevices() {
return false;
}
- device = std::make_unique<VKDevice>(*instance, physical_device, *surface, dld);
+ device =
+ std::make_unique<VKDevice>(*instance, instance_version, physical_device, *surface, dld);
return device->Create();
}
@@ -411,7 +430,7 @@ void RendererVulkan::Report() const {
const std::string driver_version = GetDriverVersion(*device);
const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
- const std::string api_version = GetReadableVersion(device->GetApiVersion());
+ const std::string api_version = GetReadableVersion(device->ApiVersion());
const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions());
@@ -419,8 +438,7 @@ void RendererVulkan::Report() const {
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
- auto& telemetry_session = system.TelemetrySession();
- constexpr auto field = Telemetry::FieldType::UserSystem;
+ static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
telemetry_session.AddField(field, "GPU_Model", model_name);
telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
@@ -431,7 +449,7 @@ void RendererVulkan::Report() const {
std::vector<std::string> RendererVulkan::EnumerateDevices() {
vk::InstanceDispatch dld;
Common::DynamicLibrary library = OpenVulkanLibrary();
- vk::Instance instance = CreateInstance(library, dld);
+ vk::Instance instance = CreateInstance(library, dld).first;
if (!instance) {
return {};
}
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 18270909b..1044ca124 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -5,7 +5,6 @@
#pragma once
#include <memory>
-#include <optional>
#include <string>
#include <vector>
@@ -15,7 +14,15 @@
#include "video_core/renderer_vulkan/wrapper.h"
namespace Core {
-class System;
+class TelemetrySession;
+}
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Tegra {
+class GPU;
}
namespace Vulkan {
@@ -23,9 +30,7 @@ namespace Vulkan {
class StateTracker;
class VKBlitScreen;
class VKDevice;
-class VKFence;
class VKMemoryManager;
-class VKResourceManager;
class VKSwapchain;
class VKScheduler;
class VKImage;
@@ -39,13 +44,15 @@ struct VKScreenInfo {
class RendererVulkan final : public VideoCore::RendererBase {
public:
- explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system);
+ explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
+ Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
+ Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
~RendererVulkan() override;
bool Init() override;
void ShutDown() override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- bool TryPresent(int timeout_ms) override;
static std::vector<std::string> EnumerateDevices();
@@ -58,23 +65,26 @@ private:
void Report() const;
- Core::System& system;
+ Core::TelemetrySession& telemetry_session;
+ Core::Memory::Memory& cpu_memory;
+ Tegra::GPU& gpu;
Common::DynamicLibrary library;
vk::InstanceDispatch dld;
vk::Instance instance;
+ u32 instance_version{};
+
vk::SurfaceKHR surface;
VKScreenInfo screen_info;
vk::DebugCallback debug_callback;
std::unique_ptr<VKDevice> device;
- std::unique_ptr<VKSwapchain> swapchain;
std::unique_ptr<VKMemoryManager> memory_manager;
- std::unique_ptr<VKResourceManager> resource_manager;
std::unique_ptr<StateTracker> state_tracker;
std::unique_ptr<VKScheduler> scheduler;
+ std::unique_ptr<VKSwapchain> swapchain;
std::unique_ptr<VKBlitScreen> blit_screen;
};
diff --git a/src/video_core/renderer_vulkan/shaders/quad_indexed.comp b/src/video_core/renderer_vulkan/shaders/quad_indexed.comp
new file mode 100644
index 000000000..5a472ba9b
--- /dev/null
+++ b/src/video_core/renderer_vulkan/shaders/quad_indexed.comp
@@ -0,0 +1,50 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+/*
+ * Build instructions:
+ * $ glslangValidator -V quad_indexed.comp -o output.spv
+ * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
+ * $ xxd -i optimized.spv
+ *
+ * Then copy that bytecode to the C++ file
+ */
+
+#version 460 core
+
+layout (local_size_x = 1024) in;
+
+layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
+ uint input_indexes[];
+};
+
+layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
+ uint output_indexes[];
+};
+
+layout (push_constant) uniform PushConstants {
+ uint base_vertex;
+ int index_shift; // 0: uint8, 1: uint16, 2: uint32
+};
+
+void main() {
+ int primitive = int(gl_GlobalInvocationID.x);
+ if (primitive * 6 >= output_indexes.length()) {
+ return;
+ }
+
+ int index_size = 8 << index_shift;
+ int flipped_shift = 2 - index_shift;
+ int mask = (1 << flipped_shift) - 1;
+
+ const int quad_swizzle[6] = int[](0, 1, 2, 0, 2, 3);
+ for (uint vertex = 0; vertex < 6; ++vertex) {
+ int offset = primitive * 4 + quad_swizzle[vertex];
+ int int_offset = offset >> flipped_shift;
+ int bit_offset = (offset & mask) * index_size;
+ uint packed_input = input_indexes[int_offset];
+ uint index = bitfieldExtract(packed_input, bit_offset, index_size);
+ output_indexes[primitive * 6 + vertex] = index + base_vertex;
+ }
+}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index fbd406f2b..b5b60309e 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -12,11 +12,9 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/math_util.h"
-
#include "core/core.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
-
#include "video_core/gpu.h"
#include "video_core/morton.h"
#include "video_core/rasterizer_interface.h"
@@ -24,8 +22,8 @@
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_image.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -141,24 +139,28 @@ struct ScreenRectVertex {
std::array<f32, 2> tex_coord;
static VkVertexInputBindingDescription GetDescription() {
- VkVertexInputBindingDescription description;
- description.binding = 0;
- description.stride = sizeof(ScreenRectVertex);
- description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
- return description;
+ return {
+ .binding = 0,
+ .stride = sizeof(ScreenRectVertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
+ };
}
static std::array<VkVertexInputAttributeDescription, 2> GetAttributes() {
- std::array<VkVertexInputAttributeDescription, 2> attributes;
- attributes[0].location = 0;
- attributes[0].binding = 0;
- attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
- attributes[0].offset = offsetof(ScreenRectVertex, position);
- attributes[1].location = 1;
- attributes[1].binding = 0;
- attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
- attributes[1].offset = offsetof(ScreenRectVertex, tex_coord);
- return attributes;
+ return {{
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = offsetof(ScreenRectVertex, position),
+ },
+ {
+ .location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = offsetof(ScreenRectVertex, tex_coord),
+ },
+ }};
}
};
@@ -183,9 +185,9 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) {
- case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
- case Tegra::FramebufferConfig::PixelFormat::RGB565:
+ case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
return VK_FORMAT_R5G6B5_UNORM_PACK16;
default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
@@ -206,17 +208,15 @@ struct VKBlitScreen::BufferData {
// Unaligned image data goes here
};
-VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
- VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKSwapchain& swapchain, VKScheduler& scheduler,
- const VKScreenInfo& screen_info)
- : system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain},
- scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} {
- watches.resize(image_count);
- std::generate(watches.begin(), watches.end(),
- []() { return std::make_unique<VKFenceWatch>(); });
+VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
+ Core::Frontend::EmuWindow& render_window_,
+ VideoCore::RasterizerInterface& rasterizer_, const VKDevice& device_,
+ VKMemoryManager& memory_manager_, VKSwapchain& swapchain_,
+ VKScheduler& scheduler_, const VKScreenInfo& screen_info_)
+ : cpu_memory{cpu_memory_}, render_window{render_window_}, rasterizer{rasterizer_},
+ device{device_}, memory_manager{memory_manager_}, swapchain{swapchain_},
+ scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
+ resource_ticks.resize(image_count);
CreateStaticResources();
CreateDynamicResources();
@@ -228,15 +228,16 @@ void VKBlitScreen::Recreate() {
CreateDynamicResources();
}
-std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated) {
+VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
const std::size_t image_index = swapchain.GetImageIndex();
- watches[image_index]->Watch(scheduler.GetFence());
+
+ scheduler.Wait(resource_ticks[image_index]);
+ resource_ticks[image_index] = scheduler.CurrentTick();
VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get();
@@ -255,7 +256,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
const auto pixel_format =
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
- const auto host_ptr = system.Memory().GetPointer(framebuffer_addr);
+ const auto host_ptr = cpu_memory.GetPointer(framebuffer_addr);
rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer));
// TODO(Rodrigo): Read this from HLE
@@ -267,20 +268,25 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
blit_image->Transition(0, 1, 0, 1, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
- VkBufferImageCopy copy;
- copy.bufferOffset = image_offset;
- copy.bufferRowLength = 0;
- copy.bufferImageHeight = 0;
- copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- copy.imageSubresource.mipLevel = 0;
- copy.imageSubresource.baseArrayLayer = 0;
- copy.imageSubresource.layerCount = 1;
- copy.imageOffset.x = 0;
- copy.imageOffset.y = 0;
- copy.imageOffset.z = 0;
- copy.imageExtent.width = framebuffer.width;
- copy.imageExtent.height = framebuffer.height;
- copy.imageExtent.depth = 1;
+ const VkBufferImageCopy copy{
+ .bufferOffset = image_offset,
+ .bufferRowLength = 0,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset = {.x = 0, .y = 0, .z = 0},
+ .imageExtent =
+ {
+ .width = framebuffer.width,
+ .height = framebuffer.height,
+ .depth = 1,
+ },
+ };
scheduler.Record(
[buffer = *buffer, image = *blit_image->GetHandle(), copy](vk::CommandBuffer cmdbuf) {
cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
@@ -295,11 +301,9 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
descriptor_set = descriptor_sets[image_index], buffer = *buffer,
size = swapchain.GetSize(), pipeline = *pipeline,
layout = *pipeline_layout](vk::CommandBuffer cmdbuf) {
- VkClearValue clear_color;
- clear_color.color.float32[0] = 0.0f;
- clear_color.color.float32[1] = 0.0f;
- clear_color.color.float32[2] = 0.0f;
- clear_color.color.float32[3] = 0.0f;
+ const VkClearValue clear_color{
+ .color = {.float32 = {0.0f, 0.0f, 0.0f, 0.0f}},
+ };
VkRenderPassBeginInfo renderpass_bi;
renderpass_bi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
@@ -336,7 +340,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
cmdbuf.EndRenderPass();
});
- return {scheduler.GetFence(), *semaphores[image_index]};
+ return *semaphores[image_index];
}
void VKBlitScreen::CreateStaticResources() {
@@ -379,93 +383,109 @@ void VKBlitScreen::CreateSemaphores() {
}
void VKBlitScreen::CreateDescriptorPool() {
- std::array<VkDescriptorPoolSize, 2> pool_sizes;
- pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- pool_sizes[0].descriptorCount = static_cast<u32>(image_count);
- pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
- pool_sizes[1].descriptorCount = static_cast<u32>(image_count);
-
- VkDescriptorPoolCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
- ci.maxSets = static_cast<u32>(image_count);
- ci.poolSizeCount = static_cast<u32>(pool_sizes.size());
- ci.pPoolSizes = pool_sizes.data();
+ const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
+ {
+ .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = static_cast<u32>(image_count),
+ },
+ {
+ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = static_cast<u32>(image_count),
+ },
+ }};
+
+ const VkDescriptorPoolCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .maxSets = static_cast<u32>(image_count),
+ .poolSizeCount = static_cast<u32>(pool_sizes.size()),
+ .pPoolSizes = pool_sizes.data(),
+ };
descriptor_pool = device.GetLogical().CreateDescriptorPool(ci);
}
void VKBlitScreen::CreateRenderPass() {
- VkAttachmentDescription color_attachment;
- color_attachment.flags = 0;
- color_attachment.format = swapchain.GetImageFormat();
- color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
- color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
- color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
- color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
- color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-
- VkAttachmentReference color_attachment_ref;
- color_attachment_ref.attachment = 0;
- color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
- VkSubpassDescription subpass_description;
- subpass_description.flags = 0;
- subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass_description.inputAttachmentCount = 0;
- subpass_description.pInputAttachments = nullptr;
- subpass_description.colorAttachmentCount = 1;
- subpass_description.pColorAttachments = &color_attachment_ref;
- subpass_description.pResolveAttachments = nullptr;
- subpass_description.pDepthStencilAttachment = nullptr;
- subpass_description.preserveAttachmentCount = 0;
- subpass_description.pPreserveAttachments = nullptr;
-
- VkSubpassDependency dependency;
- dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
- dependency.dstSubpass = 0;
- dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- dependency.srcAccessMask = 0;
- dependency.dstAccessMask =
- VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- dependency.dependencyFlags = 0;
-
- VkRenderPassCreateInfo renderpass_ci;
- renderpass_ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
- renderpass_ci.pNext = nullptr;
- renderpass_ci.flags = 0;
- renderpass_ci.attachmentCount = 1;
- renderpass_ci.pAttachments = &color_attachment;
- renderpass_ci.subpassCount = 1;
- renderpass_ci.pSubpasses = &subpass_description;
- renderpass_ci.dependencyCount = 1;
- renderpass_ci.pDependencies = &dependency;
+ const VkAttachmentDescription color_attachment{
+ .flags = 0,
+ .format = swapchain.GetImageFormat(),
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ };
+
+ const VkAttachmentReference color_attachment_ref{
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
+
+ const VkSubpassDescription subpass_description{
+ .flags = 0,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .inputAttachmentCount = 0,
+ .pInputAttachments = nullptr,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &color_attachment_ref,
+ .pResolveAttachments = nullptr,
+ .pDepthStencilAttachment = nullptr,
+ .preserveAttachmentCount = 0,
+ .pPreserveAttachments = nullptr,
+ };
+
+ const VkSubpassDependency dependency{
+ .srcSubpass = VK_SUBPASS_EXTERNAL,
+ .dstSubpass = 0,
+ .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .dependencyFlags = 0,
+ };
+
+ const VkRenderPassCreateInfo renderpass_ci{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .attachmentCount = 1,
+ .pAttachments = &color_attachment,
+ .subpassCount = 1,
+ .pSubpasses = &subpass_description,
+ .dependencyCount = 1,
+ .pDependencies = &dependency,
+ };
renderpass = device.GetLogical().CreateRenderPass(renderpass_ci);
}
void VKBlitScreen::CreateDescriptorSetLayout() {
- std::array<VkDescriptorSetLayoutBinding, 2> layout_bindings;
- layout_bindings[0].binding = 0;
- layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- layout_bindings[0].descriptorCount = 1;
- layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
- layout_bindings[0].pImmutableSamplers = nullptr;
- layout_bindings[1].binding = 1;
- layout_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
- layout_bindings[1].descriptorCount = 1;
- layout_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
- layout_bindings[1].pImmutableSamplers = nullptr;
-
- VkDescriptorSetLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.bindingCount = static_cast<u32>(layout_bindings.size());
- ci.pBindings = layout_bindings.data();
+ const std::array<VkDescriptorSetLayoutBinding, 2> layout_bindings{{
+ {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ {
+ .binding = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ }};
+
+ const VkDescriptorSetLayoutCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = static_cast<u32>(layout_bindings.size()),
+ .pBindings = layout_bindings.data(),
+ };
descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout(ci);
}
@@ -473,175 +493,192 @@ void VKBlitScreen::CreateDescriptorSetLayout() {
void VKBlitScreen::CreateDescriptorSets() {
const std::vector layouts(image_count, *descriptor_set_layout);
- VkDescriptorSetAllocateInfo ai;
- ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
- ai.pNext = nullptr;
- ai.descriptorPool = *descriptor_pool;
- ai.descriptorSetCount = static_cast<u32>(image_count);
- ai.pSetLayouts = layouts.data();
+ const VkDescriptorSetAllocateInfo ai{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .descriptorPool = *descriptor_pool,
+ .descriptorSetCount = static_cast<u32>(image_count),
+ .pSetLayouts = layouts.data(),
+ };
+
descriptor_sets = descriptor_pool.Allocate(ai);
}
void VKBlitScreen::CreatePipelineLayout() {
- VkPipelineLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.setLayoutCount = 1;
- ci.pSetLayouts = descriptor_set_layout.address();
- ci.pushConstantRangeCount = 0;
- ci.pPushConstantRanges = nullptr;
+ const VkPipelineLayoutCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .setLayoutCount = 1,
+ .pSetLayouts = descriptor_set_layout.address(),
+ .pushConstantRangeCount = 0,
+ .pPushConstantRanges = nullptr,
+ };
pipeline_layout = device.GetLogical().CreatePipelineLayout(ci);
}
void VKBlitScreen::CreateGraphicsPipeline() {
- std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
- shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
- shader_stages[0].pNext = nullptr;
- shader_stages[0].flags = 0;
- shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
- shader_stages[0].module = *vertex_shader;
- shader_stages[0].pName = "main";
- shader_stages[0].pSpecializationInfo = nullptr;
- shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
- shader_stages[1].pNext = nullptr;
- shader_stages[1].flags = 0;
- shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
- shader_stages[1].module = *fragment_shader;
- shader_stages[1].pName = "main";
- shader_stages[1].pSpecializationInfo = nullptr;
+ const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = *vertex_shader,
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ },
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = *fragment_shader,
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ },
+ }};
const auto vertex_binding_description = ScreenRectVertex::GetDescription();
const auto vertex_attrs_description = ScreenRectVertex::GetAttributes();
- VkPipelineVertexInputStateCreateInfo vertex_input_ci;
- vertex_input_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
- vertex_input_ci.pNext = nullptr;
- vertex_input_ci.flags = 0;
- vertex_input_ci.vertexBindingDescriptionCount = 1;
- vertex_input_ci.pVertexBindingDescriptions = &vertex_binding_description;
- vertex_input_ci.vertexAttributeDescriptionCount = u32{vertex_attrs_description.size()};
- vertex_input_ci.pVertexAttributeDescriptions = vertex_attrs_description.data();
-
- VkPipelineInputAssemblyStateCreateInfo input_assembly_ci;
- input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
- input_assembly_ci.pNext = nullptr;
- input_assembly_ci.flags = 0;
- input_assembly_ci.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
- input_assembly_ci.primitiveRestartEnable = VK_FALSE;
-
- VkPipelineViewportStateCreateInfo viewport_state_ci;
- viewport_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
- viewport_state_ci.pNext = nullptr;
- viewport_state_ci.flags = 0;
- viewport_state_ci.viewportCount = 1;
- viewport_state_ci.pViewports = nullptr;
- viewport_state_ci.scissorCount = 1;
- viewport_state_ci.pScissors = nullptr;
-
- VkPipelineRasterizationStateCreateInfo rasterization_ci;
- rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
- rasterization_ci.pNext = nullptr;
- rasterization_ci.flags = 0;
- rasterization_ci.depthClampEnable = VK_FALSE;
- rasterization_ci.rasterizerDiscardEnable = VK_FALSE;
- rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
- rasterization_ci.cullMode = VK_CULL_MODE_NONE;
- rasterization_ci.frontFace = VK_FRONT_FACE_CLOCKWISE;
- rasterization_ci.depthBiasEnable = VK_FALSE;
- rasterization_ci.depthBiasConstantFactor = 0.0f;
- rasterization_ci.depthBiasClamp = 0.0f;
- rasterization_ci.depthBiasSlopeFactor = 0.0f;
- rasterization_ci.lineWidth = 1.0f;
-
- VkPipelineMultisampleStateCreateInfo multisampling_ci;
- multisampling_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
- multisampling_ci.pNext = nullptr;
- multisampling_ci.flags = 0;
- multisampling_ci.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
- multisampling_ci.sampleShadingEnable = VK_FALSE;
- multisampling_ci.minSampleShading = 0.0f;
- multisampling_ci.pSampleMask = nullptr;
- multisampling_ci.alphaToCoverageEnable = VK_FALSE;
- multisampling_ci.alphaToOneEnable = VK_FALSE;
-
- VkPipelineColorBlendAttachmentState color_blend_attachment;
- color_blend_attachment.blendEnable = VK_FALSE;
- color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
- color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
- color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
- color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
- color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
- color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
- color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
- VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
-
- VkPipelineColorBlendStateCreateInfo color_blend_ci;
- color_blend_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
- color_blend_ci.flags = 0;
- color_blend_ci.pNext = nullptr;
- color_blend_ci.logicOpEnable = VK_FALSE;
- color_blend_ci.logicOp = VK_LOGIC_OP_COPY;
- color_blend_ci.attachmentCount = 1;
- color_blend_ci.pAttachments = &color_blend_attachment;
- color_blend_ci.blendConstants[0] = 0.0f;
- color_blend_ci.blendConstants[1] = 0.0f;
- color_blend_ci.blendConstants[2] = 0.0f;
- color_blend_ci.blendConstants[3] = 0.0f;
-
- static constexpr std::array dynamic_states = {VK_DYNAMIC_STATE_VIEWPORT,
- VK_DYNAMIC_STATE_SCISSOR};
- VkPipelineDynamicStateCreateInfo dynamic_state_ci;
- dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
- dynamic_state_ci.pNext = nullptr;
- dynamic_state_ci.flags = 0;
- dynamic_state_ci.dynamicStateCount = static_cast<u32>(dynamic_states.size());
- dynamic_state_ci.pDynamicStates = dynamic_states.data();
-
- VkGraphicsPipelineCreateInfo pipeline_ci;
- pipeline_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
- pipeline_ci.pNext = nullptr;
- pipeline_ci.flags = 0;
- pipeline_ci.stageCount = static_cast<u32>(shader_stages.size());
- pipeline_ci.pStages = shader_stages.data();
- pipeline_ci.pVertexInputState = &vertex_input_ci;
- pipeline_ci.pInputAssemblyState = &input_assembly_ci;
- pipeline_ci.pTessellationState = nullptr;
- pipeline_ci.pViewportState = &viewport_state_ci;
- pipeline_ci.pRasterizationState = &rasterization_ci;
- pipeline_ci.pMultisampleState = &multisampling_ci;
- pipeline_ci.pDepthStencilState = nullptr;
- pipeline_ci.pColorBlendState = &color_blend_ci;
- pipeline_ci.pDynamicState = &dynamic_state_ci;
- pipeline_ci.layout = *pipeline_layout;
- pipeline_ci.renderPass = *renderpass;
- pipeline_ci.subpass = 0;
- pipeline_ci.basePipelineHandle = 0;
- pipeline_ci.basePipelineIndex = 0;
+ const VkPipelineVertexInputStateCreateInfo vertex_input_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .vertexBindingDescriptionCount = 1,
+ .pVertexBindingDescriptions = &vertex_binding_description,
+ .vertexAttributeDescriptionCount = u32{vertex_attrs_description.size()},
+ .pVertexAttributeDescriptions = vertex_attrs_description.data(),
+ };
+
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ .primitiveRestartEnable = VK_FALSE,
+ };
+
+ const VkPipelineViewportStateCreateInfo viewport_state_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .viewportCount = 1,
+ .pViewports = nullptr,
+ .scissorCount = 1,
+ .pScissors = nullptr,
+ };
+
+ const VkPipelineRasterizationStateCreateInfo rasterization_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthClampEnable = VK_FALSE,
+ .rasterizerDiscardEnable = VK_FALSE,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_NONE,
+ .frontFace = VK_FRONT_FACE_CLOCKWISE,
+ .depthBiasEnable = VK_FALSE,
+ .depthBiasConstantFactor = 0.0f,
+ .depthBiasClamp = 0.0f,
+ .depthBiasSlopeFactor = 0.0f,
+ .lineWidth = 1.0f,
+ };
+
+ const VkPipelineMultisampleStateCreateInfo multisampling_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ .sampleShadingEnable = VK_FALSE,
+ .minSampleShading = 0.0f,
+ .pSampleMask = nullptr,
+ .alphaToCoverageEnable = VK_FALSE,
+ .alphaToOneEnable = VK_FALSE,
+ };
+
+ const VkPipelineColorBlendAttachmentState color_blend_attachment{
+ .blendEnable = VK_FALSE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+
+ const VkPipelineColorBlendStateCreateInfo color_blend_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_COPY,
+ .attachmentCount = 1,
+ .pAttachments = &color_blend_attachment,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
+ };
+
+ static constexpr std::array dynamic_states{
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ };
+ const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .dynamicStateCount = static_cast<u32>(dynamic_states.size()),
+ .pDynamicStates = dynamic_states.data(),
+ };
+
+ const VkGraphicsPipelineCreateInfo pipeline_ci{
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(shader_stages.size()),
+ .pStages = shader_stages.data(),
+ .pVertexInputState = &vertex_input_ci,
+ .pInputAssemblyState = &input_assembly_ci,
+ .pTessellationState = nullptr,
+ .pViewportState = &viewport_state_ci,
+ .pRasterizationState = &rasterization_ci,
+ .pMultisampleState = &multisampling_ci,
+ .pDepthStencilState = nullptr,
+ .pColorBlendState = &color_blend_ci,
+ .pDynamicState = &dynamic_state_ci,
+ .layout = *pipeline_layout,
+ .renderPass = *renderpass,
+ .subpass = 0,
+ .basePipelineHandle = 0,
+ .basePipelineIndex = 0,
+ };
pipeline = device.GetLogical().CreateGraphicsPipeline(pipeline_ci);
}
void VKBlitScreen::CreateSampler() {
- VkSamplerCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.magFilter = VK_FILTER_LINEAR;
- ci.minFilter = VK_FILTER_NEAREST;
- ci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
- ci.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
- ci.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
- ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
- ci.mipLodBias = 0.0f;
- ci.anisotropyEnable = VK_FALSE;
- ci.maxAnisotropy = 0.0f;
- ci.compareEnable = VK_FALSE;
- ci.compareOp = VK_COMPARE_OP_NEVER;
- ci.minLod = 0.0f;
- ci.maxLod = 0.0f;
- ci.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
- ci.unnormalizedCoordinates = VK_FALSE;
+ const VkSamplerCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .magFilter = VK_FILTER_LINEAR,
+ .minFilter = VK_FILTER_NEAREST,
+ .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .mipLodBias = 0.0f,
+ .anisotropyEnable = VK_FALSE,
+ .maxAnisotropy = 0.0f,
+ .compareEnable = VK_FALSE,
+ .compareOp = VK_COMPARE_OP_NEVER,
+ .minLod = 0.0f,
+ .maxLod = 0.0f,
+ .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK,
+ .unnormalizedCoordinates = VK_FALSE,
+ };
sampler = device.GetLogical().CreateSampler(ci);
}
@@ -650,15 +687,17 @@ void VKBlitScreen::CreateFramebuffers() {
const VkExtent2D size{swapchain.GetSize()};
framebuffers.resize(image_count);
- VkFramebufferCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.renderPass = *renderpass;
- ci.attachmentCount = 1;
- ci.width = size.width;
- ci.height = size.height;
- ci.layers = 1;
+ VkFramebufferCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .renderPass = *renderpass,
+ .attachmentCount = 1,
+ .pAttachments = nullptr,
+ .width = size.width,
+ .height = size.height,
+ .layers = 1,
+ };
for (std::size_t i = 0; i < image_count; ++i) {
const VkImageView image_view{swapchain.GetImageViewIndex(i)};
@@ -669,7 +708,7 @@ void VKBlitScreen::CreateFramebuffers() {
void VKBlitScreen::ReleaseRawImages() {
for (std::size_t i = 0; i < raw_images.size(); ++i) {
- watches[i]->Wait();
+ scheduler.Wait(resource_ticks.at(i));
}
raw_images.clear();
raw_buffer_commits.clear();
@@ -678,16 +717,17 @@ void VKBlitScreen::ReleaseRawImages() {
}
void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
- VkBufferCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.size = CalculateBufferSize(framebuffer);
- ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
- VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
+ const VkBufferCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = CalculateBufferSize(framebuffer),
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
buffer = device.GetLogical().CreateBuffer(ci);
buffer_commit = memory_manager.Commit(buffer, true);
@@ -697,24 +737,28 @@ void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer)
raw_images.resize(image_count);
raw_buffer_commits.resize(image_count);
- VkImageCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.imageType = VK_IMAGE_TYPE_2D;
- ci.format = GetFormat(framebuffer);
- ci.extent.width = framebuffer.width;
- ci.extent.height = framebuffer.height;
- ci.extent.depth = 1;
- ci.mipLevels = 1;
- ci.arrayLayers = 1;
- ci.samples = VK_SAMPLE_COUNT_1_BIT;
- ci.tiling = VK_IMAGE_TILING_LINEAR;
- ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
- ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ const VkImageCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = GetFormat(framebuffer),
+ .extent =
+ {
+ .width = framebuffer.width,
+ .height = framebuffer.height,
+ .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_LINEAR,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
for (std::size_t i = 0; i < image_count; ++i) {
raw_images[i] = std::make_unique<VKImage>(device, scheduler, ci, VK_IMAGE_ASPECT_COLOR_BIT);
@@ -723,39 +767,43 @@ void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer)
}
void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view) const {
- VkDescriptorBufferInfo buffer_info;
- buffer_info.buffer = *buffer;
- buffer_info.offset = offsetof(BufferData, uniform);
- buffer_info.range = sizeof(BufferData::uniform);
-
- VkWriteDescriptorSet ubo_write;
- ubo_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- ubo_write.pNext = nullptr;
- ubo_write.dstSet = descriptor_sets[image_index];
- ubo_write.dstBinding = 0;
- ubo_write.dstArrayElement = 0;
- ubo_write.descriptorCount = 1;
- ubo_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- ubo_write.pImageInfo = nullptr;
- ubo_write.pBufferInfo = &buffer_info;
- ubo_write.pTexelBufferView = nullptr;
-
- VkDescriptorImageInfo image_info;
- image_info.sampler = *sampler;
- image_info.imageView = image_view;
- image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
- VkWriteDescriptorSet sampler_write;
- sampler_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- sampler_write.pNext = nullptr;
- sampler_write.dstSet = descriptor_sets[image_index];
- sampler_write.dstBinding = 1;
- sampler_write.dstArrayElement = 0;
- sampler_write.descriptorCount = 1;
- sampler_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
- sampler_write.pImageInfo = &image_info;
- sampler_write.pBufferInfo = nullptr;
- sampler_write.pTexelBufferView = nullptr;
+ const VkDescriptorBufferInfo buffer_info{
+ .buffer = *buffer,
+ .offset = offsetof(BufferData, uniform),
+ .range = sizeof(BufferData::uniform),
+ };
+
+ const VkWriteDescriptorSet ubo_write{
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .pNext = nullptr,
+ .dstSet = descriptor_sets[image_index],
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .pImageInfo = nullptr,
+ .pBufferInfo = &buffer_info,
+ .pTexelBufferView = nullptr,
+ };
+
+ const VkDescriptorImageInfo image_info{
+ .sampler = *sampler,
+ .imageView = image_view,
+ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ };
+
+ const VkWriteDescriptorSet sampler_write{
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .pNext = nullptr,
+ .dstSet = descriptor_sets[image_index],
+ .dstBinding = 1,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &image_info,
+ .pBufferInfo = nullptr,
+ .pTexelBufferView = nullptr,
+ };
device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {});
}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 5eb544aea..8f2839214 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -4,18 +4,19 @@
#pragma once
-#include <array>
#include <memory>
-#include <tuple>
#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Core {
class System;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Core::Frontend {
class EmuWindow;
}
@@ -31,26 +32,26 @@ class RasterizerInterface;
namespace Vulkan {
struct ScreenInfo;
+
class RasterizerVulkan;
class VKDevice;
-class VKFence;
class VKImage;
class VKScheduler;
class VKSwapchain;
class VKBlitScreen final {
public:
- explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
+ explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
+ Core::Frontend::EmuWindow& render_window,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKSwapchain& swapchain, VKScheduler& scheduler,
- const VKScreenInfo& screen_info);
+ VKMemoryManager& memory_manager, VKSwapchain& swapchain,
+ VKScheduler& scheduler, const VKScreenInfo& screen_info);
~VKBlitScreen();
void Recreate();
- std::tuple<VKFence&, VkSemaphore> Draw(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated);
+ [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
+ bool use_accelerated);
private:
struct BufferData;
@@ -82,11 +83,10 @@ private:
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const;
- Core::System& system;
+ Core::Memory::Memory& cpu_memory;
Core::Frontend::EmuWindow& render_window;
VideoCore::RasterizerInterface& rasterizer;
const VKDevice& device;
- VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
VKSwapchain& swapchain;
VKScheduler& scheduler;
@@ -107,7 +107,7 @@ private:
vk::Buffer buffer;
VKMemoryCommit buffer_commit;
- std::vector<std::unique_ptr<VKFenceWatch>> watches;
+ std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> semaphores;
std::vector<std::unique_ptr<VKImage>> raw_images;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 0d167afbd..d9d3da9ea 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -5,12 +5,9 @@
#include <algorithm>
#include <cstring>
#include <memory>
-#include <optional>
-#include <tuple>
-#include "common/assert.h"
-#include "common/bit_util.h"
#include "core/core.h"
+#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -40,112 +37,88 @@ std::unique_ptr<VKStreamBuffer> CreateStreamBuffer(const VKDevice& device, VKSch
} // Anonymous namespace
-CachedBufferBlock::CachedBufferBlock(const VKDevice& device, VKMemoryManager& memory_manager,
- VAddr cpu_addr, std::size_t size)
- : VideoCommon::BufferBlock{cpu_addr, size} {
- VkBufferCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.size = static_cast<VkDeviceSize>(size);
- ci.usage = BUFFER_USAGE | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
+Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler_,
+ VKStagingBufferPool& staging_pool_, VAddr cpu_addr, std::size_t size)
+ : BufferBlock{cpu_addr, size}, scheduler{scheduler_}, staging_pool{staging_pool_} {
+ const VkBufferCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = static_cast<VkDeviceSize>(size),
+ .usage = BUFFER_USAGE | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
buffer.handle = device.GetLogical().CreateBuffer(ci);
buffer.commit = memory_manager.Commit(buffer.handle, false);
}
-CachedBufferBlock::~CachedBufferBlock() = default;
+Buffer::~Buffer() = default;
-VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool)
- : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, system,
- CreateStreamBuffer(device,
- scheduler)},
- device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{
- staging_pool} {}
-
-VKBufferCache::~VKBufferCache() = default;
-
-Buffer VKBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
- return std::make_shared<CachedBufferBlock>(device, memory_manager, cpu_addr, size);
-}
-
-const VkBuffer* VKBufferCache::ToHandle(const Buffer& buffer) {
- return buffer->GetHandle();
-}
-
-const VkBuffer* VKBufferCache::GetEmptyBuffer(std::size_t size) {
- size = std::max(size, std::size_t(4));
- const auto& empty = staging_pool.GetUnusedBuffer(size, false);
- scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf) {
- cmdbuf.FillBuffer(buffer, 0, size, 0);
- });
- return empty.handle.address();
-}
-
-void VKBufferCache::UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- const u8* data) {
+void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
const auto& staging = staging_pool.GetUnusedBuffer(size, true);
std::memcpy(staging.commit->Map(size), data, size);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([staging = *staging.handle, buffer = *buffer->GetHandle(), offset,
- size](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyBuffer(staging, buffer, VkBufferCopy{0, offset, size});
-
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- barrier.dstAccessMask = UPLOAD_ACCESS_BARRIERS;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = buffer;
- barrier.offset = offset;
- barrier.size = size;
+
+ const VkBuffer handle = Handle();
+ scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) {
+ cmdbuf.CopyBuffer(staging, handle, VkBufferCopy{0, offset, size});
+
+ const VkBufferMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = UPLOAD_ACCESS_BARRIERS,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = handle,
+ .offset = offset,
+ .size = size,
+ };
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, UPLOAD_PIPELINE_STAGE, 0, {},
barrier, {});
});
}
-void VKBufferCache::DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- u8* data) {
+void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
const auto& staging = staging_pool.GetUnusedBuffer(size, true);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([staging = *staging.handle, buffer = *buffer->GetHandle(), offset,
- size](vk::CommandBuffer cmdbuf) {
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = buffer;
- barrier.offset = offset;
- barrier.size = size;
+
+ const VkBuffer handle = Handle();
+ scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) {
+ const VkBufferMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = handle,
+ .offset = offset,
+ .size = size,
+ };
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, barrier, {});
- cmdbuf.CopyBuffer(buffer, staging, VkBufferCopy{offset, 0, size});
+ cmdbuf.CopyBuffer(handle, staging, VkBufferCopy{offset, 0, size});
});
scheduler.Finish();
std::memcpy(data, staging.commit->Map(size), size);
}
-void VKBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
- std::size_t dst_offset, std::size_t size) {
+void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
+ std::size_t size) {
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([src_buffer = *src->GetHandle(), dst_buffer = *dst->GetHandle(), src_offset,
- dst_offset, size](vk::CommandBuffer cmdbuf) {
+
+ const VkBuffer dst_buffer = Handle();
+ scheduler.Record([src_buffer = src.Handle(), dst_buffer, src_offset, dst_offset,
+ size](vk::CommandBuffer cmdbuf) {
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VkBufferCopy{src_offset, dst_offset, size});
std::array<VkBufferMemoryBarrier, 2> barriers;
@@ -172,4 +145,31 @@ void VKBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t
});
}
+VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
+ const VKDevice& device_, VKMemoryManager& memory_manager_,
+ VKScheduler& scheduler_, VKStagingBufferPool& staging_pool_)
+ : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, gpu_memory, cpu_memory,
+ CreateStreamBuffer(device_,
+ scheduler_)},
+ device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
+ staging_pool_} {}
+
+VKBufferCache::~VKBufferCache() = default;
+
+std::shared_ptr<Buffer> VKBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
+ return std::make_shared<Buffer>(device, memory_manager, scheduler, staging_pool, cpu_addr,
+ size);
+}
+
+VKBufferCache::BufferInfo VKBufferCache::GetEmptyBuffer(std::size_t size) {
+ size = std::max(size, std::size_t(4));
+ const auto& empty = staging_pool.GetUnusedBuffer(size, false);
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf) {
+ cmdbuf.FillBuffer(buffer, 0, size, 0);
+ });
+ return {*empty.handle, 0, 0};
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index d3c23da98..7fb5ceedf 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -5,68 +5,60 @@
#pragma once
#include <memory>
-#include <unordered_map>
-#include <vector>
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
#include "video_core/renderer_vulkan/wrapper.h"
-namespace Core {
-class System;
-}
-
namespace Vulkan {
class VKDevice;
class VKMemoryManager;
class VKScheduler;
-class CachedBufferBlock final : public VideoCommon::BufferBlock {
+class Buffer final : public VideoCommon::BufferBlock {
public:
- explicit CachedBufferBlock(const VKDevice& device, VKMemoryManager& memory_manager,
- VAddr cpu_addr, std::size_t size);
- ~CachedBufferBlock();
+ explicit Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler,
+ VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size);
+ ~Buffer();
+
+ void Upload(std::size_t offset, std::size_t size, const u8* data);
+
+ void Download(std::size_t offset, std::size_t size, u8* data);
+
+ void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
+ std::size_t size);
- const VkBuffer* GetHandle() const {
- return buffer.handle.address();
+ VkBuffer Handle() const {
+ return *buffer.handle;
+ }
+
+ u64 Address() const {
+ return 0;
}
private:
+ VKScheduler& scheduler;
+ VKStagingBufferPool& staging_pool;
+
VKBuffer buffer;
};
-using Buffer = std::shared_ptr<CachedBufferBlock>;
-
class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> {
public:
- explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
+ explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
const VKDevice& device, VKMemoryManager& memory_manager,
VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
~VKBufferCache();
- const VkBuffer* GetEmptyBuffer(std::size_t size) override;
+ BufferInfo GetEmptyBuffer(std::size_t size) override;
protected:
- void WriteBarrier() override {}
-
- Buffer CreateBlock(VAddr cpu_addr, std::size_t size) override;
-
- const VkBuffer* ToHandle(const Buffer& buffer) override;
-
- void UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- const u8* data) override;
-
- void DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size,
- u8* data) override;
-
- void CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset,
- std::size_t dst_offset, std::size_t size) override;
+ std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override;
private:
const VKDevice& device;
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp
new file mode 100644
index 000000000..6339f4fe0
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+
+#include "video_core/renderer_vulkan/vk_command_pool.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+constexpr size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
+
+struct CommandPool::Pool {
+ vk::CommandPool handle;
+ vk::CommandBuffers cmdbufs;
+};
+
+CommandPool::CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device)
+ : ResourcePool(master_semaphore, COMMAND_BUFFER_POOL_SIZE), device{device} {}
+
+CommandPool::~CommandPool() = default;
+
+void CommandPool::Allocate(size_t begin, size_t end) {
+ // Command buffers are going to be commited, recorded, executed every single usage cycle.
+ // They are also going to be reseted when commited.
+ Pool& pool = pools.emplace_back();
+ pool.handle = device.GetLogical().CreateCommandPool({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags =
+ VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = device.GetGraphicsFamily(),
+ });
+ pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
+}
+
+VkCommandBuffer CommandPool::Commit() {
+ const size_t index = CommitResource();
+ const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
+ const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
+ return pools[pool_index].cmdbufs[sub_index];
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h
new file mode 100644
index 000000000..b9cb3fb5d
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+class MasterSemaphore;
+class VKDevice;
+
+class CommandPool final : public ResourcePool {
+public:
+ explicit CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device);
+ ~CommandPool() override;
+
+ void Allocate(size_t begin, size_t end) override;
+
+ VkCommandBuffer Commit();
+
+private:
+ struct Pool;
+
+ const VKDevice& device;
+ std::vector<Pool> pools;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 9d92305f4..9637c6059 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -6,7 +6,7 @@
#include <memory>
#include <optional>
#include <utility>
-#include <vector>
+
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
@@ -112,35 +112,36 @@ constexpr u8 quad_array[] = {
0xf9, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x23, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4e, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00,
- 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
+ 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
VkDescriptorSetLayoutBinding BuildQuadArrayPassDescriptorSetLayoutBinding() {
- VkDescriptorSetLayoutBinding binding;
- binding.binding = 0;
- binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
- binding.descriptorCount = 1;
- binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
- binding.pImmutableSamplers = nullptr;
- return binding;
+ return {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ };
}
VkDescriptorUpdateTemplateEntryKHR BuildQuadArrayPassDescriptorUpdateTemplateEntry() {
- VkDescriptorUpdateTemplateEntryKHR entry;
- entry.dstBinding = 0;
- entry.dstArrayElement = 0;
- entry.descriptorCount = 1;
- entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
- entry.offset = 0;
- entry.stride = sizeof(DescriptorUpdateEntry);
- return entry;
+ return {
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .offset = 0,
+ .stride = sizeof(DescriptorUpdateEntry),
+ };
}
-VkPushConstantRange BuildQuadArrayPassPushConstantRange() {
- VkPushConstantRange range;
- range.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
- range.offset = 0;
- range.size = sizeof(u32);
- return range;
+VkPushConstantRange BuildComputePushConstantRange(std::size_t size) {
+ return {
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .offset = 0,
+ .size = static_cast<u32>(size),
+ };
}
// Uint8 SPIR-V module. Generated from the "shaders/" directory.
@@ -218,32 +219,161 @@ constexpr u8 uint8_pass[] = {
0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
-
-std::array<VkDescriptorSetLayoutBinding, 2> BuildUint8PassDescriptorSetBindings() {
- std::array<VkDescriptorSetLayoutBinding, 2> bindings;
- bindings[0].binding = 0;
- bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
- bindings[0].descriptorCount = 1;
- bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
- bindings[0].pImmutableSamplers = nullptr;
- bindings[1].binding = 1;
- bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
- bindings[1].descriptorCount = 1;
- bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
- bindings[1].pImmutableSamplers = nullptr;
- return bindings;
+ 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
+
+// Quad indexed SPIR-V module. Generated from the "shaders/" directory.
+constexpr u8 QUAD_INDEXED_SPV[] = {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x7c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x56, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x43, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x09, 0x00, 0x41, 0x00, 0x00, 0x00,
+ 0x44, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x46, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x57, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x46, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00,
+ 0xf8, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x73, 0x00, 0x00, 0x00,
+ 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00,
+ 0xf8, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x44, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
+ 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0xf5, 0x00, 0x07, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0xf6, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xfa, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
+ 0xf8, 0x00, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
+ 0x47, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
+ 0xc3, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
+ 0x4a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+ 0x5b, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
+ 0x4e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00,
+ 0x5c, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
+ 0x5d, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00,
+ 0x6a, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x62, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x5b, 0x00, 0x00, 0x00,
+ 0x6d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x03, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00,
+ 0xf9, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, 0x00,
+ 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00,
+ 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
+
+std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBindings() {
+ return {{
+ {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ {
+ .binding = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ }};
}
-VkDescriptorUpdateTemplateEntryKHR BuildUint8PassDescriptorUpdateTemplateEntry() {
- VkDescriptorUpdateTemplateEntryKHR entry;
- entry.dstBinding = 0;
- entry.dstArrayElement = 0;
- entry.descriptorCount = 2;
- entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
- entry.offset = 0;
- entry.stride = sizeof(DescriptorUpdateEntry);
- return entry;
+VkDescriptorUpdateTemplateEntryKHR BuildInputOutputDescriptorUpdateTemplate() {
+ return {
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 2,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .offset = 0,
+ .stride = sizeof(DescriptorUpdateEntry),
+ };
}
} // Anonymous namespace
@@ -253,37 +383,37 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates,
vk::Span<VkPushConstantRange> push_constants, std::size_t code_size,
const u8* code) {
- VkDescriptorSetLayoutCreateInfo descriptor_layout_ci;
- descriptor_layout_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- descriptor_layout_ci.pNext = nullptr;
- descriptor_layout_ci.flags = 0;
- descriptor_layout_ci.bindingCount = bindings.size();
- descriptor_layout_ci.pBindings = bindings.data();
- descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout(descriptor_layout_ci);
-
- VkPipelineLayoutCreateInfo pipeline_layout_ci;
- pipeline_layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
- pipeline_layout_ci.pNext = nullptr;
- pipeline_layout_ci.flags = 0;
- pipeline_layout_ci.setLayoutCount = 1;
- pipeline_layout_ci.pSetLayouts = descriptor_set_layout.address();
- pipeline_layout_ci.pushConstantRangeCount = push_constants.size();
- pipeline_layout_ci.pPushConstantRanges = push_constants.data();
- layout = device.GetLogical().CreatePipelineLayout(pipeline_layout_ci);
+ descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = bindings.size(),
+ .pBindings = bindings.data(),
+ });
+
+ layout = device.GetLogical().CreatePipelineLayout({
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .setLayoutCount = 1,
+ .pSetLayouts = descriptor_set_layout.address(),
+ .pushConstantRangeCount = push_constants.size(),
+ .pPushConstantRanges = push_constants.data(),
+ });
if (!templates.empty()) {
- VkDescriptorUpdateTemplateCreateInfoKHR template_ci;
- template_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR;
- template_ci.pNext = nullptr;
- template_ci.flags = 0;
- template_ci.descriptorUpdateEntryCount = templates.size();
- template_ci.pDescriptorUpdateEntries = templates.data();
- template_ci.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR;
- template_ci.descriptorSetLayout = *descriptor_set_layout;
- template_ci.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- template_ci.pipelineLayout = *layout;
- template_ci.set = 0;
- descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR(template_ci);
+ descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR({
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .descriptorUpdateEntryCount = templates.size(),
+ .pDescriptorUpdateEntries = templates.data(),
+ .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR,
+ .descriptorSetLayout = *descriptor_set_layout,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .pipelineLayout = *layout,
+ .set = 0,
+ });
descriptor_allocator.emplace(descriptor_pool, *descriptor_set_layout);
}
@@ -291,42 +421,42 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
auto code_copy = std::make_unique<u32[]>(code_size / sizeof(u32) + 1);
std::memcpy(code_copy.get(), code, code_size);
- VkShaderModuleCreateInfo module_ci;
- module_ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
- module_ci.pNext = nullptr;
- module_ci.flags = 0;
- module_ci.codeSize = code_size;
- module_ci.pCode = code_copy.get();
- module = device.GetLogical().CreateShaderModule(module_ci);
-
- VkComputePipelineCreateInfo pipeline_ci;
- pipeline_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
- pipeline_ci.pNext = nullptr;
- pipeline_ci.flags = 0;
- pipeline_ci.layout = *layout;
- pipeline_ci.basePipelineHandle = nullptr;
- pipeline_ci.basePipelineIndex = 0;
-
- VkPipelineShaderStageCreateInfo& stage_ci = pipeline_ci.stage;
- stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
- stage_ci.pNext = nullptr;
- stage_ci.flags = 0;
- stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT;
- stage_ci.module = *module;
- stage_ci.pName = "main";
- stage_ci.pSpecializationInfo = nullptr;
-
- pipeline = device.GetLogical().CreateComputePipeline(pipeline_ci);
+ module = device.GetLogical().CreateShaderModule({
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .codeSize = code_size,
+ .pCode = code_copy.get(),
+ });
+
+ pipeline = device.GetLogical().CreateComputePipeline({
+ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage =
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = VK_SHADER_STAGE_COMPUTE_BIT,
+ .module = *module,
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ },
+ .layout = *layout,
+ .basePipelineHandle = nullptr,
+ .basePipelineIndex = 0,
+ });
}
VKComputePass::~VKComputePass() = default;
-VkDescriptorSet VKComputePass::CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
- VKFence& fence) {
+VkDescriptorSet VKComputePass::CommitDescriptorSet(
+ VKUpdateDescriptorQueue& update_descriptor_queue) {
if (!descriptor_template) {
return nullptr;
}
- const auto set = descriptor_allocator->Commit(fence);
+ const VkDescriptorSet set = descriptor_allocator->Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
@@ -337,20 +467,20 @@ QuadArrayPass::QuadArrayPass(const VKDevice& device, VKScheduler& scheduler,
VKUpdateDescriptorQueue& update_descriptor_queue)
: VKComputePass(device, descriptor_pool, BuildQuadArrayPassDescriptorSetLayoutBinding(),
BuildQuadArrayPassDescriptorUpdateTemplateEntry(),
- BuildQuadArrayPassPushConstantRange(), std::size(quad_array), quad_array),
+ BuildComputePushConstantRange(sizeof(u32)), std::size(quad_array), quad_array),
scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
update_descriptor_queue{update_descriptor_queue} {}
QuadArrayPass::~QuadArrayPass() = default;
-std::pair<const VkBuffer*, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) {
- const u32 num_triangle_vertices = num_vertices * 6 / 4;
+std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) {
+ const u32 num_triangle_vertices = (num_vertices / 4) * 6;
const std::size_t staging_size = num_triangle_vertices * sizeof(u32);
auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(buffer.handle.address(), 0, staging_size);
- const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+ update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
+ const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
scheduler.RequestOutsideRenderPassOperationContext();
@@ -377,29 +507,29 @@ std::pair<const VkBuffer*, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertice
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, {barrier}, {});
});
- return {buffer.handle.address(), 0};
+ return {*buffer.handle, 0};
}
Uint8Pass::Uint8Pass(const VKDevice& device, VKScheduler& scheduler,
VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool,
VKUpdateDescriptorQueue& update_descriptor_queue)
- : VKComputePass(device, descriptor_pool, BuildUint8PassDescriptorSetBindings(),
- BuildUint8PassDescriptorUpdateTemplateEntry(), {}, std::size(uint8_pass),
+ : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(),
+ BuildInputOutputDescriptorUpdateTemplate(), {}, std::size(uint8_pass),
uint8_pass),
scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
update_descriptor_queue{update_descriptor_queue} {}
Uint8Pass::~Uint8Pass() = default;
-std::pair<const VkBuffer*, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
- u64 src_offset) {
- const auto staging_size = static_cast<u32>(num_vertices * sizeof(u16));
+std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
+ u64 src_offset) {
+ const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(&src_buffer, src_offset, num_vertices);
- update_descriptor_queue.AddBuffer(buffer.handle.address(), 0, staging_size);
- const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+ update_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
+ update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
+ const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
@@ -422,7 +552,73 @@ std::pair<const VkBuffer*, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer s
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
});
- return {buffer.handle.address(), 0};
+ return {*buffer.handle, 0};
+}
+
+QuadIndexedPass::QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler,
+ VKDescriptorPool& descriptor_pool,
+ VKStagingBufferPool& staging_buffer_pool,
+ VKUpdateDescriptorQueue& update_descriptor_queue)
+ : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(),
+ BuildInputOutputDescriptorUpdateTemplate(),
+ BuildComputePushConstantRange(sizeof(u32) * 2), std::size(QUAD_INDEXED_SPV),
+ QUAD_INDEXED_SPV),
+ scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
+ update_descriptor_queue{update_descriptor_queue} {}
+
+QuadIndexedPass::~QuadIndexedPass() = default;
+
+std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
+ Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices, u32 base_vertex,
+ VkBuffer src_buffer, u64 src_offset) {
+ const u32 index_shift = [index_format] {
+ switch (index_format) {
+ case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedByte:
+ return 0;
+ case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedShort:
+ return 1;
+ case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedInt:
+ return 2;
+ }
+ UNREACHABLE();
+ return 2;
+ }();
+ const u32 input_size = num_vertices << index_shift;
+ const u32 num_tri_vertices = (num_vertices / 4) * 6;
+
+ const std::size_t staging_size = num_tri_vertices * sizeof(u32);
+ auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+
+ update_descriptor_queue.Acquire();
+ update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
+ update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
+ const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
+ num_tri_vertices, base_vertex, index_shift](vk::CommandBuffer cmdbuf) {
+ static constexpr u32 dispatch_size = 1024;
+ const std::array push_constants = {base_vertex, index_shift};
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
+ cmdbuf.PushConstants(layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants),
+ &push_constants);
+ cmdbuf.Dispatch(Common::AlignUp(num_tri_vertices, dispatch_size) / dispatch_size, 1, 1);
+
+ VkBufferMemoryBarrier barrier;
+ barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ barrier.pNext = nullptr;
+ barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+ barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ barrier.buffer = buffer;
+ barrier.offset = 0;
+ barrier.size = static_cast<VkDeviceSize>(num_tri_vertices * sizeof(u32));
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
+ });
+ return {*buffer.handle, 0};
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index c62516bff..acc94f27e 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -6,15 +6,15 @@
#include <optional>
#include <utility>
-#include <vector>
+
#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
class VKDevice;
-class VKFence;
class VKScheduler;
class VKStagingBufferPool;
class VKUpdateDescriptorQueue;
@@ -29,8 +29,7 @@ public:
~VKComputePass();
protected:
- VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
- VKFence& fence);
+ VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue);
vk::DescriptorUpdateTemplateKHR descriptor_template;
vk::PipelineLayout layout;
@@ -50,7 +49,7 @@ public:
VKUpdateDescriptorQueue& update_descriptor_queue);
~QuadArrayPass();
- std::pair<const VkBuffer*, VkDeviceSize> Assemble(u32 num_vertices, u32 first);
+ std::pair<VkBuffer, VkDeviceSize> Assemble(u32 num_vertices, u32 first);
private:
VKScheduler& scheduler;
@@ -65,7 +64,25 @@ public:
VKUpdateDescriptorQueue& update_descriptor_queue);
~Uint8Pass();
- std::pair<const VkBuffer*, u64> Assemble(u32 num_vertices, VkBuffer src_buffer, u64 src_offset);
+ std::pair<VkBuffer, u64> Assemble(u32 num_vertices, VkBuffer src_buffer, u64 src_offset);
+
+private:
+ VKScheduler& scheduler;
+ VKStagingBufferPool& staging_buffer_pool;
+ VKUpdateDescriptorQueue& update_descriptor_queue;
+};
+
+class QuadIndexedPass final : public VKComputePass {
+public:
+ explicit QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler,
+ VKDescriptorPool& descriptor_pool,
+ VKStagingBufferPool& staging_buffer_pool,
+ VKUpdateDescriptorQueue& update_descriptor_queue);
+ ~QuadIndexedPass();
+
+ std::pair<VkBuffer, u64> Assemble(Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format,
+ u32 num_vertices, u32 base_vertex, VkBuffer src_buffer,
+ u64 src_offset);
private:
VKScheduler& scheduler;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 23beafa4f..9be72dc9b 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -2,14 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <memory>
#include <vector>
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
@@ -34,7 +32,7 @@ VkDescriptorSet VKComputePipeline::CommitDescriptorSet() {
if (!descriptor_template) {
return {};
}
- const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+ const VkDescriptorSet set = descriptor_allocator.Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
@@ -45,39 +43,41 @@ vk::DescriptorSetLayout VKComputePipeline::CreateDescriptorSetLayout() const {
const auto add_bindings = [&](VkDescriptorType descriptor_type, std::size_t num_entries) {
// TODO(Rodrigo): Maybe make individual bindings here?
for (u32 bindpoint = 0; bindpoint < static_cast<u32>(num_entries); ++bindpoint) {
- VkDescriptorSetLayoutBinding& entry = bindings.emplace_back();
- entry.binding = binding++;
- entry.descriptorType = descriptor_type;
- entry.descriptorCount = 1;
- entry.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
- entry.pImmutableSamplers = nullptr;
+ bindings.push_back({
+ .binding = binding++,
+ .descriptorType = descriptor_type,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ });
}
};
add_bindings(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, entries.const_buffers.size());
add_bindings(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, entries.global_buffers.size());
- add_bindings(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, entries.texel_buffers.size());
+ add_bindings(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, entries.uniform_texels.size());
add_bindings(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, entries.samplers.size());
+ add_bindings(VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, entries.storage_texels.size());
add_bindings(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, entries.images.size());
- VkDescriptorSetLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.bindingCount = static_cast<u32>(bindings.size());
- ci.pBindings = bindings.data();
- return device.GetLogical().CreateDescriptorSetLayout(ci);
+ return device.GetLogical().CreateDescriptorSetLayout({
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = static_cast<u32>(bindings.size()),
+ .pBindings = bindings.data(),
+ });
}
vk::PipelineLayout VKComputePipeline::CreatePipelineLayout() const {
- VkPipelineLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.setLayoutCount = 1;
- ci.pSetLayouts = descriptor_set_layout.address();
- ci.pushConstantRangeCount = 0;
- ci.pPushConstantRanges = nullptr;
- return device.GetLogical().CreatePipelineLayout(ci);
+ return device.GetLogical().CreatePipelineLayout({
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .setLayoutCount = 1,
+ .pSetLayouts = descriptor_set_layout.address(),
+ .pushConstantRangeCount = 0,
+ .pPushConstantRanges = nullptr,
+ });
}
vk::DescriptorUpdateTemplateKHR VKComputePipeline::CreateDescriptorUpdateTemplate() const {
@@ -90,57 +90,63 @@ vk::DescriptorUpdateTemplateKHR VKComputePipeline::CreateDescriptorUpdateTemplat
return {};
}
- VkDescriptorUpdateTemplateCreateInfoKHR ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.descriptorUpdateEntryCount = static_cast<u32>(template_entries.size());
- ci.pDescriptorUpdateEntries = template_entries.data();
- ci.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR;
- ci.descriptorSetLayout = *descriptor_set_layout;
- ci.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- ci.pipelineLayout = *layout;
- ci.set = DESCRIPTOR_SET;
- return device.GetLogical().CreateDescriptorUpdateTemplateKHR(ci);
+ return device.GetLogical().CreateDescriptorUpdateTemplateKHR({
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .descriptorUpdateEntryCount = static_cast<u32>(template_entries.size()),
+ .pDescriptorUpdateEntries = template_entries.data(),
+ .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR,
+ .descriptorSetLayout = *descriptor_set_layout,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .pipelineLayout = *layout,
+ .set = DESCRIPTOR_SET,
+ });
}
vk::ShaderModule VKComputePipeline::CreateShaderModule(const std::vector<u32>& code) const {
- VkShaderModuleCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.codeSize = code.size() * sizeof(u32);
- ci.pCode = code.data();
- return device.GetLogical().CreateShaderModule(ci);
+ device.SaveShader(code);
+
+ return device.GetLogical().CreateShaderModule({
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .codeSize = code.size() * sizeof(u32),
+ .pCode = code.data(),
+ });
}
vk::Pipeline VKComputePipeline::CreatePipeline() const {
- VkComputePipelineCreateInfo ci;
- VkPipelineShaderStageCreateInfo& stage_ci = ci.stage;
- stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
- stage_ci.pNext = nullptr;
- stage_ci.flags = 0;
- stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT;
- stage_ci.module = *shader_module;
- stage_ci.pName = "main";
- stage_ci.pSpecializationInfo = nullptr;
-
- VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci;
- subgroup_size_ci.sType =
- VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT;
- subgroup_size_ci.pNext = nullptr;
- subgroup_size_ci.requiredSubgroupSize = GuestWarpSize;
+
+ VkComputePipelineCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage =
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = VK_SHADER_STAGE_COMPUTE_BIT,
+ .module = *shader_module,
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ },
+ .layout = *layout,
+ .basePipelineHandle = nullptr,
+ .basePipelineIndex = 0,
+ };
+
+ const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .requiredSubgroupSize = GuestWarpSize,
+ };
if (entries.uses_warps && device.IsGuestWarpSizeSupported(VK_SHADER_STAGE_COMPUTE_BIT)) {
- stage_ci.pNext = &subgroup_size_ci;
+ ci.stage.pNext = &subgroup_size_ci;
}
- ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.layout = *layout;
- ci.basePipelineHandle = nullptr;
- ci.basePipelineIndex = 0;
return device.GetLogical().CreateComputePipeline(ci);
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
index 33b9af29e..6e2f22a4a 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
@@ -4,8 +4,6 @@
#pragma once
-#include <memory>
-
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index e9d528aa6..f38e089d5 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -2,13 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <memory>
#include <vector>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
@@ -16,14 +16,15 @@ namespace Vulkan {
// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines.
constexpr std::size_t SETS_GROW_RATE = 0x20;
-DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool,
- VkDescriptorSetLayout layout)
- : VKFencedPool{SETS_GROW_RATE}, descriptor_pool{descriptor_pool}, layout{layout} {}
+DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool_,
+ VkDescriptorSetLayout layout_)
+ : ResourcePool(descriptor_pool_.master_semaphore, SETS_GROW_RATE),
+ descriptor_pool{descriptor_pool_}, layout{layout_} {}
DescriptorAllocator::~DescriptorAllocator() = default;
-VkDescriptorSet DescriptorAllocator::Commit(VKFence& fence) {
- const std::size_t index = CommitResource(fence);
+VkDescriptorSet DescriptorAllocator::Commit() {
+ const std::size_t index = CommitResource();
return descriptors_allocations[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
}
@@ -31,8 +32,9 @@ void DescriptorAllocator::Allocate(std::size_t begin, std::size_t end) {
descriptors_allocations.push_back(descriptor_pool.AllocateDescriptors(layout, end - begin));
}
-VKDescriptorPool::VKDescriptorPool(const VKDevice& device)
- : device{device}, active_pool{AllocateNewPool()} {}
+VKDescriptorPool::VKDescriptorPool(const VKDevice& device_, VKScheduler& scheduler)
+ : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()}, active_pool{
+ AllocateNewPool()} {}
VKDescriptorPool::~VKDescriptorPool() = default;
@@ -43,27 +45,31 @@ vk::DescriptorPool* VKDescriptorPool::AllocateNewPool() {
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, num_sets * 60},
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, num_sets * 64},
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sets * 64},
- {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, num_sets * 40}};
-
- VkDescriptorPoolCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
- ci.maxSets = num_sets;
- ci.poolSizeCount = static_cast<u32>(std::size(pool_sizes));
- ci.pPoolSizes = std::data(pool_sizes);
+ {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, num_sets * 64},
+ {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, num_sets * 40},
+ };
+
+ const VkDescriptorPoolCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .maxSets = num_sets,
+ .poolSizeCount = static_cast<u32>(std::size(pool_sizes)),
+ .pPoolSizes = std::data(pool_sizes),
+ };
return &pools.emplace_back(device.GetLogical().CreateDescriptorPool(ci));
}
vk::DescriptorSets VKDescriptorPool::AllocateDescriptors(VkDescriptorSetLayout layout,
std::size_t count) {
const std::vector layout_copies(count, layout);
- VkDescriptorSetAllocateInfo ai;
- ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
- ai.pNext = nullptr;
- ai.descriptorPool = **active_pool;
- ai.descriptorSetCount = static_cast<u32>(count);
- ai.pSetLayouts = layout_copies.data();
+ VkDescriptorSetAllocateInfo ai{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .descriptorPool = **active_pool,
+ .descriptorSetCount = static_cast<u32>(count),
+ .pSetLayouts = layout_copies.data(),
+ };
vk::DescriptorSets sets = active_pool->Allocate(ai);
if (!sets.IsOutOfPoolMemory()) {
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
index ab40c70f0..544f32a20 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
@@ -4,25 +4,26 @@
#pragma once
-#include <memory>
#include <vector>
-#include "common/common_types.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
+class VKDevice;
class VKDescriptorPool;
+class VKScheduler;
-class DescriptorAllocator final : public VKFencedPool {
+class DescriptorAllocator final : public ResourcePool {
public:
explicit DescriptorAllocator(VKDescriptorPool& descriptor_pool, VkDescriptorSetLayout layout);
~DescriptorAllocator() override;
+ DescriptorAllocator& operator=(const DescriptorAllocator&) = delete;
DescriptorAllocator(const DescriptorAllocator&) = delete;
- VkDescriptorSet Commit(VKFence& fence);
+ VkDescriptorSet Commit();
protected:
void Allocate(std::size_t begin, std::size_t end) override;
@@ -38,15 +39,19 @@ class VKDescriptorPool final {
friend DescriptorAllocator;
public:
- explicit VKDescriptorPool(const VKDevice& device);
+ explicit VKDescriptorPool(const VKDevice& device, VKScheduler& scheduler);
~VKDescriptorPool();
+ VKDescriptorPool(const VKDescriptorPool&) = delete;
+ VKDescriptorPool& operator=(const VKDescriptorPool&) = delete;
+
private:
vk::DescriptorPool* AllocateNewPool();
vk::DescriptorSets AllocateDescriptors(VkDescriptorSetLayout layout, std::size_t count);
const VKDevice& device;
+ MasterSemaphore& master_semaphore;
std::vector<vk::DescriptorPool> pools;
vk::DescriptorPool* active_pool;
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 52d29e49d..f34ed6735 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -4,11 +4,11 @@
#include <bitset>
#include <chrono>
-#include <cstdlib>
#include <optional>
#include <string_view>
#include <thread>
#include <unordered_set>
+#include <utility>
#include <vector>
#include "common/assert.h"
@@ -22,19 +22,30 @@ namespace {
namespace Alternatives {
-constexpr std::array Depth24UnormS8_UINT = {VK_FORMAT_D32_SFLOAT_S8_UINT,
- VK_FORMAT_D16_UNORM_S8_UINT, VkFormat{}};
-constexpr std::array Depth16UnormS8_UINT = {VK_FORMAT_D24_UNORM_S8_UINT,
- VK_FORMAT_D32_SFLOAT_S8_UINT, VkFormat{}};
+constexpr std::array Depth24UnormS8_UINT{
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D16_UNORM_S8_UINT,
+ VkFormat{},
+};
+
+constexpr std::array Depth16UnormS8_UINT{
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VkFormat{},
+};
} // namespace Alternatives
-constexpr std::array REQUIRED_EXTENSIONS = {
+constexpr std::array REQUIRED_EXTENSIONS{
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE1_EXTENSION_NAME,
+ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME,
+ VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME,
VK_KHR_16BIT_STORAGE_EXTENSION_NAME,
VK_KHR_8BIT_STORAGE_EXTENSION_NAME,
VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME,
VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME,
+ VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME,
VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
@@ -71,76 +82,105 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType
}
}
+[[nodiscard]] bool IsRDNA(std::string_view device_name, VkDriverIdKHR driver_id) {
+ static constexpr std::array RDNA_DEVICES{
+ "5700",
+ "5600",
+ "5500",
+ "5300",
+ };
+ if (driver_id != VK_DRIVER_ID_AMD_PROPRIETARY_KHR) {
+ return false;
+ }
+ return std::any_of(RDNA_DEVICES.begin(), RDNA_DEVICES.end(), [device_name](const char* name) {
+ return device_name.find(name) != std::string_view::npos;
+ });
+}
+
std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(
vk::PhysicalDevice physical, const vk::InstanceDispatch& dld) {
- static constexpr std::array formats{VK_FORMAT_A8B8G8R8_UNORM_PACK32,
- VK_FORMAT_A8B8G8R8_UINT_PACK32,
- VK_FORMAT_A8B8G8R8_SNORM_PACK32,
- VK_FORMAT_A8B8G8R8_SRGB_PACK32,
- VK_FORMAT_B5G6R5_UNORM_PACK16,
- VK_FORMAT_A2B10G10R10_UNORM_PACK32,
- VK_FORMAT_A1R5G5B5_UNORM_PACK16,
- VK_FORMAT_R32G32B32A32_SFLOAT,
- VK_FORMAT_R32G32B32A32_UINT,
- VK_FORMAT_R32G32_SFLOAT,
- VK_FORMAT_R32G32_UINT,
- VK_FORMAT_R16G16B16A16_UINT,
- VK_FORMAT_R16G16B16A16_SNORM,
- VK_FORMAT_R16G16B16A16_UNORM,
- VK_FORMAT_R16G16_UNORM,
- VK_FORMAT_R16G16_SNORM,
- VK_FORMAT_R16G16_SFLOAT,
- VK_FORMAT_R16_UNORM,
- VK_FORMAT_R8G8B8A8_SRGB,
- VK_FORMAT_R8G8_UNORM,
- VK_FORMAT_R8G8_SNORM,
- VK_FORMAT_R8_UNORM,
- VK_FORMAT_R8_UINT,
- VK_FORMAT_B10G11R11_UFLOAT_PACK32,
- VK_FORMAT_R32_SFLOAT,
- VK_FORMAT_R32_UINT,
- VK_FORMAT_R32_SINT,
- VK_FORMAT_R16_SFLOAT,
- VK_FORMAT_R16G16B16A16_SFLOAT,
- VK_FORMAT_B8G8R8A8_UNORM,
- VK_FORMAT_R4G4B4A4_UNORM_PACK16,
- VK_FORMAT_D32_SFLOAT,
- VK_FORMAT_D16_UNORM,
- VK_FORMAT_D16_UNORM_S8_UINT,
- VK_FORMAT_D24_UNORM_S8_UINT,
- VK_FORMAT_D32_SFLOAT_S8_UINT,
- VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
- VK_FORMAT_BC2_UNORM_BLOCK,
- VK_FORMAT_BC3_UNORM_BLOCK,
- VK_FORMAT_BC4_UNORM_BLOCK,
- VK_FORMAT_BC5_UNORM_BLOCK,
- VK_FORMAT_BC5_SNORM_BLOCK,
- VK_FORMAT_BC7_UNORM_BLOCK,
- VK_FORMAT_BC6H_UFLOAT_BLOCK,
- VK_FORMAT_BC6H_SFLOAT_BLOCK,
- VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
- VK_FORMAT_BC2_SRGB_BLOCK,
- VK_FORMAT_BC3_SRGB_BLOCK,
- VK_FORMAT_BC7_SRGB_BLOCK,
- VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
- VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
- VK_FORMAT_E5B9G9R9_UFLOAT_PACK32};
+ static constexpr std::array formats{
+ VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ VK_FORMAT_A8B8G8R8_UINT_PACK32,
+ VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+ VK_FORMAT_A8B8G8R8_SINT_PACK32,
+ VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+ VK_FORMAT_B5G6R5_UNORM_PACK16,
+ VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+ VK_FORMAT_A2B10G10R10_UINT_PACK32,
+ VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_FORMAT_R32G32B32A32_SINT,
+ VK_FORMAT_R32G32B32A32_UINT,
+ VK_FORMAT_R32G32_SFLOAT,
+ VK_FORMAT_R32G32_SINT,
+ VK_FORMAT_R32G32_UINT,
+ VK_FORMAT_R16G16B16A16_SINT,
+ VK_FORMAT_R16G16B16A16_UINT,
+ VK_FORMAT_R16G16B16A16_SNORM,
+ VK_FORMAT_R16G16B16A16_UNORM,
+ VK_FORMAT_R16G16_UNORM,
+ VK_FORMAT_R16G16_SNORM,
+ VK_FORMAT_R16G16_SFLOAT,
+ VK_FORMAT_R16_UNORM,
+ VK_FORMAT_R16_UINT,
+ VK_FORMAT_R8G8B8A8_SRGB,
+ VK_FORMAT_R8G8_UNORM,
+ VK_FORMAT_R8G8_SNORM,
+ VK_FORMAT_R8G8_SINT,
+ VK_FORMAT_R8G8_UINT,
+ VK_FORMAT_R8_UNORM,
+ VK_FORMAT_R8_SNORM,
+ VK_FORMAT_R8_SINT,
+ VK_FORMAT_R8_UINT,
+ VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+ VK_FORMAT_R32_SFLOAT,
+ VK_FORMAT_R32_UINT,
+ VK_FORMAT_R32_SINT,
+ VK_FORMAT_R16_SFLOAT,
+ VK_FORMAT_R16G16B16A16_SFLOAT,
+ VK_FORMAT_B8G8R8A8_UNORM,
+ VK_FORMAT_B8G8R8A8_SRGB,
+ VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_D32_SFLOAT,
+ VK_FORMAT_D16_UNORM,
+ VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
+ VK_FORMAT_BC2_UNORM_BLOCK,
+ VK_FORMAT_BC3_UNORM_BLOCK,
+ VK_FORMAT_BC4_UNORM_BLOCK,
+ VK_FORMAT_BC4_SNORM_BLOCK,
+ VK_FORMAT_BC5_UNORM_BLOCK,
+ VK_FORMAT_BC5_SNORM_BLOCK,
+ VK_FORMAT_BC7_UNORM_BLOCK,
+ VK_FORMAT_BC6H_UFLOAT_BLOCK,
+ VK_FORMAT_BC6H_SFLOAT_BLOCK,
+ VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
+ VK_FORMAT_BC2_SRGB_BLOCK,
+ VK_FORMAT_BC3_SRGB_BLOCK,
+ VK_FORMAT_BC7_SRGB_BLOCK,
+ VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+ VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+ VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+ };
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
for (const auto format : formats) {
format_properties.emplace(format, physical.GetFormatProperties(format));
@@ -150,10 +190,10 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(
} // Anonymous namespace
-VKDevice::VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
- const vk::InstanceDispatch& dld)
- : dld{dld}, physical{physical}, properties{physical.GetProperties()},
- format_properties{GetFormatProperties(physical, dld)} {
+VKDevice::VKDevice(VkInstance instance_, u32 instance_version_, vk::PhysicalDevice physical_,
+ VkSurfaceKHR surface, const vk::InstanceDispatch& dld_)
+ : dld{dld_}, physical{physical_}, properties{physical.GetProperties()},
+ instance_version{instance_version_}, format_properties{GetFormatProperties(physical, dld)} {
SetupFamilies(surface);
SetupFeatures();
}
@@ -164,107 +204,127 @@ bool VKDevice::Create() {
const auto queue_cis = GetDeviceQueueCreateInfos();
const std::vector extensions = LoadExtensions();
- VkPhysicalDeviceFeatures2 features2;
- features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
- features2.pNext = nullptr;
+ VkPhysicalDeviceFeatures2 features2{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+ .pNext = nullptr,
+ };
+ const void* first_next = &features2;
void** next = &features2.pNext;
- auto& features = features2.features;
- features.robustBufferAccess = false;
- features.fullDrawIndexUint32 = false;
- features.imageCubeArray = false;
- features.independentBlend = true;
- features.geometryShader = true;
- features.tessellationShader = true;
- features.sampleRateShading = false;
- features.dualSrcBlend = false;
- features.logicOp = false;
- features.multiDrawIndirect = false;
- features.drawIndirectFirstInstance = false;
- features.depthClamp = true;
- features.depthBiasClamp = true;
- features.fillModeNonSolid = false;
- features.depthBounds = false;
- features.wideLines = false;
- features.largePoints = true;
- features.alphaToOne = false;
- features.multiViewport = true;
- features.samplerAnisotropy = true;
- features.textureCompressionETC2 = false;
- features.textureCompressionASTC_LDR = is_optimal_astc_supported;
- features.textureCompressionBC = false;
- features.occlusionQueryPrecise = true;
- features.pipelineStatisticsQuery = false;
- features.vertexPipelineStoresAndAtomics = true;
- features.fragmentStoresAndAtomics = true;
- features.shaderTessellationAndGeometryPointSize = false;
- features.shaderImageGatherExtended = true;
- features.shaderStorageImageExtendedFormats = false;
- features.shaderStorageImageMultisample = false;
- features.shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported;
- features.shaderStorageImageWriteWithoutFormat = true;
- features.shaderUniformBufferArrayDynamicIndexing = false;
- features.shaderSampledImageArrayDynamicIndexing = false;
- features.shaderStorageBufferArrayDynamicIndexing = false;
- features.shaderStorageImageArrayDynamicIndexing = false;
- features.shaderClipDistance = false;
- features.shaderCullDistance = false;
- features.shaderFloat64 = false;
- features.shaderInt64 = false;
- features.shaderInt16 = false;
- features.shaderResourceResidency = false;
- features.shaderResourceMinLod = false;
- features.sparseBinding = false;
- features.sparseResidencyBuffer = false;
- features.sparseResidencyImage2D = false;
- features.sparseResidencyImage3D = false;
- features.sparseResidency2Samples = false;
- features.sparseResidency4Samples = false;
- features.sparseResidency8Samples = false;
- features.sparseResidency16Samples = false;
- features.sparseResidencyAliased = false;
- features.variableMultisampleRate = false;
- features.inheritedQueries = false;
-
- VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage;
- bit16_storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR;
- bit16_storage.pNext = nullptr;
- bit16_storage.storageBuffer16BitAccess = false;
- bit16_storage.uniformAndStorageBuffer16BitAccess = true;
- bit16_storage.storagePushConstant16 = false;
- bit16_storage.storageInputOutput16 = false;
+ features2.features = {
+ .robustBufferAccess = false,
+ .fullDrawIndexUint32 = false,
+ .imageCubeArray = false,
+ .independentBlend = true,
+ .geometryShader = true,
+ .tessellationShader = true,
+ .sampleRateShading = false,
+ .dualSrcBlend = false,
+ .logicOp = false,
+ .multiDrawIndirect = false,
+ .drawIndirectFirstInstance = false,
+ .depthClamp = true,
+ .depthBiasClamp = true,
+ .fillModeNonSolid = false,
+ .depthBounds = false,
+ .wideLines = false,
+ .largePoints = true,
+ .alphaToOne = false,
+ .multiViewport = true,
+ .samplerAnisotropy = true,
+ .textureCompressionETC2 = false,
+ .textureCompressionASTC_LDR = is_optimal_astc_supported,
+ .textureCompressionBC = false,
+ .occlusionQueryPrecise = true,
+ .pipelineStatisticsQuery = false,
+ .vertexPipelineStoresAndAtomics = true,
+ .fragmentStoresAndAtomics = true,
+ .shaderTessellationAndGeometryPointSize = false,
+ .shaderImageGatherExtended = true,
+ .shaderStorageImageExtendedFormats = false,
+ .shaderStorageImageMultisample = false,
+ .shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported,
+ .shaderStorageImageWriteWithoutFormat = true,
+ .shaderUniformBufferArrayDynamicIndexing = false,
+ .shaderSampledImageArrayDynamicIndexing = false,
+ .shaderStorageBufferArrayDynamicIndexing = false,
+ .shaderStorageImageArrayDynamicIndexing = false,
+ .shaderClipDistance = false,
+ .shaderCullDistance = false,
+ .shaderFloat64 = false,
+ .shaderInt64 = false,
+ .shaderInt16 = false,
+ .shaderResourceResidency = false,
+ .shaderResourceMinLod = false,
+ .sparseBinding = false,
+ .sparseResidencyBuffer = false,
+ .sparseResidencyImage2D = false,
+ .sparseResidencyImage3D = false,
+ .sparseResidency2Samples = false,
+ .sparseResidency4Samples = false,
+ .sparseResidency8Samples = false,
+ .sparseResidency16Samples = false,
+ .sparseResidencyAliased = false,
+ .variableMultisampleRate = false,
+ .inheritedQueries = false,
+ };
+
+ VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline_semaphore{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR,
+ .pNext = nullptr,
+ .timelineSemaphore = true,
+ };
+ SetNext(next, timeline_semaphore);
+
+ VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
+ .pNext = nullptr,
+ .storageBuffer16BitAccess = false,
+ .uniformAndStorageBuffer16BitAccess = true,
+ .storagePushConstant16 = false,
+ .storageInputOutput16 = false,
+ };
SetNext(next, bit16_storage);
- VkPhysicalDevice8BitStorageFeaturesKHR bit8_storage;
- bit8_storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR;
- bit8_storage.pNext = nullptr;
- bit8_storage.storageBuffer8BitAccess = false;
- bit8_storage.uniformAndStorageBuffer8BitAccess = true;
- bit8_storage.storagePushConstant8 = false;
+ VkPhysicalDevice8BitStorageFeaturesKHR bit8_storage{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR,
+ .pNext = nullptr,
+ .storageBuffer8BitAccess = false,
+ .uniformAndStorageBuffer8BitAccess = true,
+ .storagePushConstant8 = false,
+ };
SetNext(next, bit8_storage);
- VkPhysicalDeviceHostQueryResetFeaturesEXT host_query_reset;
- host_query_reset.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT;
- host_query_reset.hostQueryReset = true;
+ VkPhysicalDeviceHostQueryResetFeaturesEXT host_query_reset{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT,
+ .hostQueryReset = true,
+ };
SetNext(next, host_query_reset);
VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
if (is_float16_supported) {
- float16_int8.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
- float16_int8.pNext = nullptr;
- float16_int8.shaderFloat16 = true;
- float16_int8.shaderInt8 = false;
+ float16_int8 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR,
+ .pNext = nullptr,
+ .shaderFloat16 = true,
+ .shaderInt8 = false,
+ };
SetNext(next, float16_int8);
} else {
LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively");
}
+ if (!nv_viewport_swizzle) {
+ LOG_INFO(Render_Vulkan, "Device doesn't support viewport swizzles");
+ }
+
VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout;
if (khr_uniform_buffer_standard_layout) {
- std430_layout.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR;
- std430_layout.pNext = nullptr;
- std430_layout.uniformBufferStandardLayout = true;
+ std430_layout = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR,
+ .pNext = nullptr,
+ .uniformBufferStandardLayout = true,
+ };
SetNext(next, std430_layout);
} else {
LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs");
@@ -272,9 +332,11 @@ bool VKDevice::Create() {
VkPhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8;
if (ext_index_type_uint8) {
- index_type_uint8.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT;
- index_type_uint8.pNext = nullptr;
- index_type_uint8.indexTypeUint8 = true;
+ index_type_uint8 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT,
+ .pNext = nullptr,
+ .indexTypeUint8 = true,
+ };
SetNext(next, index_type_uint8);
} else {
LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes");
@@ -282,21 +344,61 @@ bool VKDevice::Create() {
VkPhysicalDeviceTransformFeedbackFeaturesEXT transform_feedback;
if (ext_transform_feedback) {
- transform_feedback.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;
- transform_feedback.pNext = nullptr;
- transform_feedback.transformFeedback = true;
- transform_feedback.geometryStreams = true;
+ transform_feedback = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
+ .pNext = nullptr,
+ .transformFeedback = true,
+ .geometryStreams = true,
+ };
SetNext(next, transform_feedback);
} else {
LOG_INFO(Render_Vulkan, "Device doesn't support transform feedbacks");
}
+ VkPhysicalDeviceCustomBorderColorFeaturesEXT custom_border;
+ if (ext_custom_border_color) {
+ custom_border = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
+ .pNext = nullptr,
+ .customBorderColors = VK_TRUE,
+ .customBorderColorWithoutFormat = VK_TRUE,
+ };
+ SetNext(next, custom_border);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
+ }
+
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ if (ext_extended_dynamic_state) {
+ dynamic_state = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT,
+ .pNext = nullptr,
+ .extendedDynamicState = VK_TRUE,
+ };
+ SetNext(next, dynamic_state);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
+ }
+
if (!ext_depth_range_unrestricted) {
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
}
- logical = vk::Device::Create(physical, queue_cis, extensions, features2, dld);
+ VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv;
+ if (nv_device_diagnostics_config) {
+ nsight_aftermath_tracker.Initialize();
+
+ diagnostics_nv = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV,
+ .pNext = &features2,
+ .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV |
+ VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV |
+ VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV,
+ };
+ first_next = &diagnostics_nv;
+ }
+
+ logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
if (!logical) {
LOG_ERROR(Render_Vulkan, "Failed to create logical device");
return false;
@@ -304,8 +406,19 @@ bool VKDevice::Create() {
CollectTelemetryParameters();
+ if (ext_extended_dynamic_state && IsRDNA(properties.deviceName, driver_id)) {
+ // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but on RDNA devices it
+ // seems to cause stability issues
+ LOG_WARNING(
+ Render_Vulkan,
+ "Blacklisting AMD proprietary on RDNA devices from VK_EXT_extended_dynamic_state");
+ ext_extended_dynamic_state = false;
+ }
+
graphics_queue = logical.GetQueue(graphics_family);
present_queue = logical.GetQueue(present_family);
+
+ use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
return true;
}
@@ -344,17 +457,12 @@ VkFormat VKDevice::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFla
void VKDevice::ReportLoss() const {
LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
- // Wait some time to let the log flush
- std::this_thread::sleep_for(std::chrono::seconds{1});
-
- if (!nv_device_diagnostic_checkpoints) {
- return;
- }
+ // Wait for the log to flush and for Nsight Aftermath to dump the results
+ std::this_thread::sleep_for(std::chrono::seconds{3});
+}
- [[maybe_unused]] const std::vector data = graphics_queue.GetCheckpointDataNV(dld);
- // Catch here in debug builds (or with optimizations disabled) the last graphics pipeline to be
- // executed. It can be done on a debugger by evaluating the expression:
- // *(VKGraphicsPipeline*)data[0]
+void VKDevice::SaveShader(const std::vector<u32>& spirv) const {
+ nsight_aftermath_tracker.SaveShader(spirv);
}
bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const {
@@ -492,43 +600,44 @@ bool VKDevice::IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface) {
std::vector<const char*> VKDevice::LoadExtensions() {
std::vector<const char*> extensions;
- const auto Test = [&](const VkExtensionProperties& extension,
- std::optional<std::reference_wrapper<bool>> status, const char* name,
- bool push) {
- if (extension.extensionName != std::string_view(name)) {
- return;
- }
- if (push) {
- extensions.push_back(name);
- }
- if (status) {
- status->get() = true;
- }
- };
-
extensions.reserve(7 + REQUIRED_EXTENSIONS.size());
extensions.insert(extensions.begin(), REQUIRED_EXTENSIONS.begin(), REQUIRED_EXTENSIONS.end());
bool has_khr_shader_float16_int8{};
bool has_ext_subgroup_size_control{};
bool has_ext_transform_feedback{};
- for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
- Test(extension, khr_uniform_buffer_standard_layout,
+ bool has_ext_custom_border_color{};
+ bool has_ext_extended_dynamic_state{};
+ for (const VkExtensionProperties& extension : physical.EnumerateDeviceExtensionProperties()) {
+ const auto test = [&](std::optional<std::reference_wrapper<bool>> status, const char* name,
+ bool push) {
+ if (extension.extensionName != std::string_view(name)) {
+ return;
+ }
+ if (push) {
+ extensions.push_back(name);
+ }
+ if (status) {
+ status->get() = true;
+ }
+ };
+ test(nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
+ test(khr_uniform_buffer_standard_layout,
VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true);
- Test(extension, has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
- false);
- Test(extension, ext_depth_range_unrestricted,
- VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true);
- Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true);
- Test(extension, ext_shader_viewport_index_layer,
- VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
- Test(extension, has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
- false);
- Test(extension, has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME,
- false);
+ test(has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false);
+ test(ext_depth_range_unrestricted, VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true);
+ test(ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true);
+ test(ext_shader_viewport_index_layer, VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME,
+ true);
+ test(has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, false);
+ test(has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, false);
+ test(has_ext_extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
+ if (instance_version >= VK_API_VERSION_1_1) {
+ test(has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false);
+ }
if (Settings::values.renderer_debug) {
- Test(extension, nv_device_diagnostic_checkpoints,
- VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
+ test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME,
+ true);
}
}
@@ -598,6 +707,32 @@ std::vector<const char*> VKDevice::LoadExtensions() {
}
}
+ if (has_ext_custom_border_color) {
+ VkPhysicalDeviceCustomBorderColorFeaturesEXT border_features;
+ border_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT;
+ border_features.pNext = nullptr;
+ features.pNext = &border_features;
+ physical.GetFeatures2KHR(features);
+
+ if (border_features.customBorderColors && border_features.customBorderColorWithoutFormat) {
+ extensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
+ ext_custom_border_color = true;
+ }
+ }
+
+ if (has_ext_extended_dynamic_state) {
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
+ dynamic_state.pNext = nullptr;
+ features.pNext = &dynamic_state;
+ physical.GetFeatures2KHR(features);
+
+ if (dynamic_state.extendedDynamicState) {
+ extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ ext_extended_dynamic_state = true;
+ }
+ }
+
return extensions;
}
@@ -633,14 +768,21 @@ void VKDevice::SetupFeatures() {
}
void VKDevice::CollectTelemetryParameters() {
- VkPhysicalDeviceDriverPropertiesKHR driver;
- driver.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR;
- driver.pNext = nullptr;
+ VkPhysicalDeviceDriverPropertiesKHR driver{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
+ .pNext = nullptr,
+ .driverID = {},
+ .driverName = {},
+ .driverInfo = {},
+ .conformanceVersion = {},
+ };
- VkPhysicalDeviceProperties2KHR properties;
- properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
- properties.pNext = &driver;
- physical.GetProperties2KHR(properties);
+ VkPhysicalDeviceProperties2KHR device_properties{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+ .pNext = &driver,
+ .properties = {},
+ };
+ physical.GetProperties2KHR(device_properties);
driver_id = driver.driverID;
vendor_name = driver.driverName;
@@ -648,23 +790,26 @@ void VKDevice::CollectTelemetryParameters() {
const std::vector extensions = physical.EnumerateDeviceExtensionProperties();
reported_extensions.reserve(std::size(extensions));
for (const auto& extension : extensions) {
- reported_extensions.push_back(extension.extensionName);
+ reported_extensions.emplace_back(extension.extensionName);
}
}
std::vector<VkDeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const {
static constexpr float QUEUE_PRIORITY = 1.0f;
- std::unordered_set<u32> unique_queue_families = {graphics_family, present_family};
+ std::unordered_set<u32> unique_queue_families{graphics_family, present_family};
std::vector<VkDeviceQueueCreateInfo> queue_cis;
+ queue_cis.reserve(unique_queue_families.size());
for (const u32 queue_family : unique_queue_families) {
- VkDeviceQueueCreateInfo& ci = queue_cis.emplace_back();
- ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.queueFamilyIndex = queue_family;
- ci.queueCount = 1;
+ auto& ci = queue_cis.emplace_back(VkDeviceQueueCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .queueFamilyIndex = queue_family,
+ .queueCount = 1,
+ .pQueuePriorities = nullptr,
+ });
ci.pQueuePriorities = &QUEUE_PRIORITY;
}
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 60d64572a..4286673d9 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -10,6 +10,7 @@
#include <vector>
#include "common/common_types.h"
+#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
@@ -23,8 +24,8 @@ const u32 GuestWarpSize = 32;
/// Handles data specific to a physical device.
class VKDevice final {
public:
- explicit VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
- const vk::InstanceDispatch& dld);
+ explicit VKDevice(VkInstance instance, u32 instance_version, vk::PhysicalDevice physical,
+ VkSurfaceKHR surface, const vk::InstanceDispatch& dld);
~VKDevice();
/// Initializes the device. Returns true on success.
@@ -43,6 +44,9 @@ public:
/// Reports a device loss.
void ReportLoss() const;
+ /// Reports a shader to Nsight Aftermath.
+ void SaveShader(const std::vector<u32>& spirv) const;
+
/// Returns the dispatch loader with direct function pointers of the device.
const vk::DeviceDispatch& GetDispatchLoader() const {
return dld;
@@ -78,13 +82,13 @@ public:
return present_family;
}
- /// Returns true if the device is integrated with the host CPU.
- bool IsIntegrated() const {
- return properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
+ /// Returns the current instance Vulkan API version in Vulkan-formatted version numbers.
+ u32 InstanceApiVersion() const {
+ return instance_version;
}
/// Returns the current Vulkan API version provided in Vulkan-formatted version numbers.
- u32 GetApiVersion() const {
+ u32 ApiVersion() const {
return properties.apiVersion;
}
@@ -123,6 +127,11 @@ public:
return properties.limits.maxPushConstantsSize;
}
+ /// Returns the maximum size for shared memory.
+ u32 GetMaxComputeSharedMemorySize() const {
+ return properties.limits.maxComputeSharedMemorySize;
+ }
+
/// Returns true if ASTC is natively supported.
bool IsOptimalAstcSupported() const {
return is_optimal_astc_supported;
@@ -148,6 +157,11 @@ public:
return is_formatless_image_load_supported;
}
+ /// Returns true if the device supports VK_NV_viewport_swizzle.
+ bool IsNvViewportSwizzleSupported() const {
+ return nv_viewport_swizzle;
+ }
+
/// Returns true if the device supports VK_EXT_scalar_block_layout.
bool IsKhrUniformBufferStandardLayoutSupported() const {
return khr_uniform_buffer_standard_layout;
@@ -173,9 +187,14 @@ public:
return ext_transform_feedback;
}
- /// Returns true if the device supports VK_NV_device_diagnostic_checkpoints.
- bool IsNvDeviceDiagnosticCheckpoints() const {
- return nv_device_diagnostic_checkpoints;
+ /// Returns true if the device supports VK_EXT_custom_border_color.
+ bool IsExtCustomBorderColorSupported() const {
+ return ext_custom_border_color;
+ }
+
+ /// Returns true if the device supports VK_EXT_extended_dynamic_state.
+ bool IsExtExtendedDynamicStateSupported() const {
+ return ext_extended_dynamic_state;
}
/// Returns the vendor name reported from Vulkan.
@@ -188,6 +207,11 @@ public:
return reported_extensions;
}
+ /// Returns true if the setting for async shader compilation is enabled.
+ bool UseAsynchronousShaders() const {
+ return use_asynchronous_shaders;
+ }
+
/// Checks if the physical device is suitable.
static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface);
@@ -220,6 +244,7 @@ private:
vk::Device logical; ///< Logical device.
vk::Queue graphics_queue; ///< Main graphics queue.
vk::Queue present_queue; ///< Main present queue.
+ u32 instance_version{}; ///< Vulkan onstance version.
u32 graphics_family{}; ///< Main graphics queue family index.
u32 present_family{}; ///< Main present queue family index.
VkDriverIdKHR driver_id{}; ///< Driver ID.
@@ -228,12 +253,18 @@ private:
bool is_float16_supported{}; ///< Support for float16 arithmetics.
bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
bool is_formatless_image_load_supported{}; ///< Support for shader image read without format.
+ bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle.
bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs.
bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
- bool nv_device_diagnostic_checkpoints{}; ///< Support for VK_NV_device_diagnostic_checkpoints.
+ bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
+ bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
+ bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
+
+ // Asynchronous Graphics Pipeline setting
+ bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline
// Telemetry parameters
std::string vendor_name; ///< Device's driver name.
@@ -241,6 +272,9 @@ private:
/// Format properties dictionary.
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
+
+ /// Nsight Aftermath GPU crash tracker
+ NsightAftermathTracker nsight_aftermath_tracker;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
new file mode 100644
index 000000000..5babbdd0b
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -0,0 +1,101 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <thread>
+
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_fence_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload, bool is_stubbed)
+ : VideoCommon::FenceBase(payload, is_stubbed), device{device}, scheduler{scheduler} {}
+
+InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address,
+ u32 payload, bool is_stubbed)
+ : VideoCommon::FenceBase(address, payload, is_stubbed), device{device}, scheduler{scheduler} {}
+
+InnerFence::~InnerFence() = default;
+
+void InnerFence::Queue() {
+ if (is_stubbed) {
+ return;
+ }
+ ASSERT(!event);
+
+ event = device.GetLogical().CreateEvent();
+ ticks = scheduler.CurrentTick();
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([event = *event](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetEvent(event, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
+ });
+}
+
+bool InnerFence::IsSignaled() const {
+ if (is_stubbed) {
+ return true;
+ }
+ ASSERT(event);
+ return IsEventSignalled();
+}
+
+void InnerFence::Wait() {
+ if (is_stubbed) {
+ return;
+ }
+ ASSERT(event);
+
+ if (ticks >= scheduler.CurrentTick()) {
+ scheduler.Flush();
+ }
+ while (!IsEventSignalled()) {
+ std::this_thread::yield();
+ }
+}
+
+bool InnerFence::IsEventSignalled() const {
+ switch (const VkResult result = event.GetStatus()) {
+ case VK_EVENT_SET:
+ return true;
+ case VK_EVENT_RESET:
+ return false;
+ default:
+ throw vk::Exception(result);
+ }
+}
+
+VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
+ VKBufferCache& buffer_cache, VKQueryCache& query_cache,
+ const VKDevice& device_, VKScheduler& scheduler_)
+ : GenericFenceManager(rasterizer, gpu, texture_cache, buffer_cache, query_cache),
+ device{device_}, scheduler{scheduler_} {}
+
+Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) {
+ return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed);
+}
+
+Fence VKFenceManager::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) {
+ return std::make_shared<InnerFence>(device, scheduler, addr, value, is_stubbed);
+}
+
+void VKFenceManager::QueueFence(Fence& fence) {
+ fence->Queue();
+}
+
+bool VKFenceManager::IsFenceSignaled(Fence& fence) const {
+ return fence->IsSignaled();
+}
+
+void VKFenceManager::WaitFence(Fence& fence) {
+ fence->Wait();
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
new file mode 100644
index 000000000..1547d6d30
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -0,0 +1,75 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "video_core/fence_manager.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Core {
+class System;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace Vulkan {
+
+class VKBufferCache;
+class VKDevice;
+class VKQueryCache;
+class VKScheduler;
+class VKTextureCache;
+
+class InnerFence : public VideoCommon::FenceBase {
+public:
+ explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload,
+ bool is_stubbed);
+ explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address,
+ u32 payload, bool is_stubbed);
+ ~InnerFence();
+
+ void Queue();
+
+ bool IsSignaled() const;
+
+ void Wait();
+
+private:
+ bool IsEventSignalled() const;
+
+ const VKDevice& device;
+ VKScheduler& scheduler;
+ vk::Event event;
+ u64 ticks = 0;
+};
+using Fence = std::shared_ptr<InnerFence>;
+
+using GenericFenceManager =
+ VideoCommon::FenceManager<Fence, VKTextureCache, VKBufferCache, VKQueryCache>;
+
+class VKFenceManager final : public GenericFenceManager {
+public:
+ explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
+ VKBufferCache& buffer_cache, VKQueryCache& query_cache,
+ const VKDevice& device, VKScheduler& scheduler);
+
+protected:
+ Fence CreateFence(u32 value, bool is_stubbed) override;
+ Fence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) override;
+ void QueueFence(Fence& fence) override;
+ bool IsFenceSignaled(Fence& fence) const override;
+ void WaitFence(Fence& fence) override;
+
+private:
+ const VKDevice& device;
+ VKScheduler& scheduler;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index b540b838d..0e8f9c352 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -2,11 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cstring>
#include <vector>
-#include "common/assert.h"
#include "common/common_types.h"
#include "common/microprofile.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
@@ -26,16 +26,17 @@ MICROPROFILE_DECLARE(Vulkan_PipelineCache);
namespace {
-VkStencilOpState GetStencilFaceState(const FixedPipelineState::StencilFace& face) {
- VkStencilOpState state;
- state.failOp = MaxwellToVK::StencilOp(face.action_stencil_fail);
- state.passOp = MaxwellToVK::StencilOp(face.action_depth_pass);
- state.depthFailOp = MaxwellToVK::StencilOp(face.action_depth_fail);
- state.compareOp = MaxwellToVK::ComparisonOp(face.test_func);
- state.compareMask = 0;
- state.writeMask = 0;
- state.reference = 0;
- return state;
+template <class StencilFace>
+VkStencilOpState GetStencilFaceState(const StencilFace& face) {
+ return {
+ .failOp = MaxwellToVK::StencilOp(face.ActionStencilFail()),
+ .passOp = MaxwellToVK::StencilOp(face.ActionDepthPass()),
+ .depthFailOp = MaxwellToVK::StencilOp(face.ActionDepthFail()),
+ .compareOp = MaxwellToVK::ComparisonOp(face.TestFunc()),
+ .compareMask = 0,
+ .writeMask = 0,
+ .reference = 0,
+ };
}
bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) {
@@ -50,6 +51,24 @@ bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) {
topology) == std::end(unsupported_topologies);
}
+VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) {
+ union Swizzle {
+ u32 raw;
+ BitField<0, 3, Maxwell::ViewportSwizzle> x;
+ BitField<4, 3, Maxwell::ViewportSwizzle> y;
+ BitField<8, 3, Maxwell::ViewportSwizzle> z;
+ BitField<12, 3, Maxwell::ViewportSwizzle> w;
+ };
+ const Swizzle unpacked{swizzle};
+
+ return {
+ .x = MaxwellToVK::ViewportSwizzle(unpacked.x),
+ .y = MaxwellToVK::ViewportSwizzle(unpacked.y),
+ .z = MaxwellToVK::ViewportSwizzle(unpacked.z),
+ .w = MaxwellToVK::ViewportSwizzle(unpacked.w),
+ };
+}
+
} // Anonymous namespace
VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler,
@@ -59,15 +78,14 @@ VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& sche
const GraphicsPipelineCacheKey& key,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
const SPIRVProgram& program)
- : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()},
+ : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()},
descriptor_set_layout{CreateDescriptorSetLayout(bindings)},
descriptor_allocator{descriptor_pool, *descriptor_set_layout},
update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()},
descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules(
program)},
- renderpass{renderpass_cache.GetRenderPass(key.renderpass_params)}, pipeline{CreatePipeline(
- key.renderpass_params,
- program)} {}
+ renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)},
+ pipeline{CreatePipeline(cache_key.renderpass_params, program)} {}
VKGraphicsPipeline::~VKGraphicsPipeline() = default;
@@ -75,31 +93,33 @@ VkDescriptorSet VKGraphicsPipeline::CommitDescriptorSet() {
if (!descriptor_template) {
return {};
}
- const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+ const VkDescriptorSet set = descriptor_allocator.Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
vk::DescriptorSetLayout VKGraphicsPipeline::CreateDescriptorSetLayout(
vk::Span<VkDescriptorSetLayoutBinding> bindings) const {
- VkDescriptorSetLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.bindingCount = bindings.size();
- ci.pBindings = bindings.data();
+ const VkDescriptorSetLayoutCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = bindings.size(),
+ .pBindings = bindings.data(),
+ };
return device.GetLogical().CreateDescriptorSetLayout(ci);
}
vk::PipelineLayout VKGraphicsPipeline::CreatePipelineLayout() const {
- VkPipelineLayoutCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.setLayoutCount = 1;
- ci.pSetLayouts = descriptor_set_layout.address();
- ci.pushConstantRangeCount = 0;
- ci.pPushConstantRanges = nullptr;
+ const VkPipelineLayoutCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .setLayoutCount = 1,
+ .pSetLayouts = descriptor_set_layout.address(),
+ .pushConstantRangeCount = 0,
+ .pPushConstantRanges = nullptr,
+ };
return device.GetLogical().CreatePipelineLayout(ci);
}
@@ -118,26 +138,29 @@ vk::DescriptorUpdateTemplateKHR VKGraphicsPipeline::CreateDescriptorUpdateTempla
return {};
}
- VkDescriptorUpdateTemplateCreateInfoKHR ci;
- ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.descriptorUpdateEntryCount = static_cast<u32>(template_entries.size());
- ci.pDescriptorUpdateEntries = template_entries.data();
- ci.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR;
- ci.descriptorSetLayout = *descriptor_set_layout;
- ci.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- ci.pipelineLayout = *layout;
- ci.set = DESCRIPTOR_SET;
+ const VkDescriptorUpdateTemplateCreateInfoKHR ci{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .descriptorUpdateEntryCount = static_cast<u32>(template_entries.size()),
+ .pDescriptorUpdateEntries = template_entries.data(),
+ .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR,
+ .descriptorSetLayout = *descriptor_set_layout,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .pipelineLayout = *layout,
+ .set = DESCRIPTOR_SET,
+ };
return device.GetLogical().CreateDescriptorUpdateTemplateKHR(ci);
}
std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
const SPIRVProgram& program) const {
- VkShaderModuleCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
+ VkShaderModuleCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .codeSize = 0,
+ };
std::vector<vk::ShaderModule> modules;
modules.reserve(Maxwell::MaxShaderStage);
@@ -147,6 +170,8 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
continue;
}
+ device.SaveShader(stage->code);
+
ci.codeSize = stage->code.size() * sizeof(u32);
ci.pCode = stage->code.data();
modules.push_back(device.GetLogical().CreateShaderModule(ci));
@@ -156,186 +181,251 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
const SPIRVProgram& program) const {
- const auto& vi = fixed_state.vertex_input;
- const auto& ia = fixed_state.input_assembly;
- const auto& ds = fixed_state.depth_stencil;
- const auto& cd = fixed_state.color_blending;
- const auto& ts = fixed_state.tessellation;
- const auto& rs = fixed_state.rasterizer;
+ const auto& state = cache_key.fixed_state;
+ const auto& viewport_swizzles = state.viewport_swizzles;
+
+ FixedPipelineState::DynamicState dynamic;
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ // Insert dummy values, as long as they are valid they don't matter as extended dynamic
+ // state is ignored
+ dynamic.raw1 = 0;
+ dynamic.raw2 = 0;
+ for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) {
+ // Enable all vertex bindings
+ binding.raw = 0;
+ binding.enabled.Assign(1);
+ }
+ } else {
+ dynamic = state.dynamic_state;
+ }
std::vector<VkVertexInputBindingDescription> vertex_bindings;
std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
- for (std::size_t i = 0; i < vi.num_bindings; ++i) {
- const auto& binding = vi.bindings[i];
- const bool instanced = binding.divisor != 0;
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& binding = dynamic.vertex_bindings[index];
+ if (!binding.enabled) {
+ continue;
+ }
+ const bool instanced = state.binding_divisors[index] != 0;
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
- auto& vertex_binding = vertex_bindings.emplace_back();
- vertex_binding.binding = binding.index;
- vertex_binding.stride = binding.stride;
- vertex_binding.inputRate = rate;
+ vertex_bindings.push_back({
+ .binding = static_cast<u32>(index),
+ .stride = binding.stride,
+ .inputRate = rate,
+ });
if (instanced) {
- auto& binding_divisor = vertex_binding_divisors.emplace_back();
- binding_divisor.binding = binding.index;
- binding_divisor.divisor = binding.divisor;
+ vertex_binding_divisors.push_back({
+ .binding = static_cast<u32>(index),
+ .divisor = state.binding_divisors[index],
+ });
}
}
std::vector<VkVertexInputAttributeDescription> vertex_attributes;
const auto& input_attributes = program[0]->entries.attributes;
- for (std::size_t i = 0; i < vi.num_attributes; ++i) {
- const auto& attribute = vi.attributes[i];
- if (input_attributes.find(attribute.index) == input_attributes.end()) {
+ for (std::size_t index = 0; index < state.attributes.size(); ++index) {
+ const auto& attribute = state.attributes[index];
+ if (!attribute.enabled) {
+ continue;
+ }
+ if (input_attributes.find(static_cast<u32>(index)) == input_attributes.end()) {
// Skip attributes not used by the vertex shaders.
continue;
}
- auto& vertex_attribute = vertex_attributes.emplace_back();
- vertex_attribute.location = attribute.index;
- vertex_attribute.binding = attribute.buffer;
- vertex_attribute.format = MaxwellToVK::VertexFormat(attribute.type, attribute.size);
- vertex_attribute.offset = attribute.offset;
+ vertex_attributes.push_back({
+ .location = static_cast<u32>(index),
+ .binding = attribute.buffer,
+ .format = MaxwellToVK::VertexFormat(attribute.Type(), attribute.Size()),
+ .offset = attribute.offset,
+ });
}
- VkPipelineVertexInputStateCreateInfo vertex_input_ci;
- vertex_input_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
- vertex_input_ci.pNext = nullptr;
- vertex_input_ci.flags = 0;
- vertex_input_ci.vertexBindingDescriptionCount = static_cast<u32>(vertex_bindings.size());
- vertex_input_ci.pVertexBindingDescriptions = vertex_bindings.data();
- vertex_input_ci.vertexAttributeDescriptionCount = static_cast<u32>(vertex_attributes.size());
- vertex_input_ci.pVertexAttributeDescriptions = vertex_attributes.data();
-
- VkPipelineVertexInputDivisorStateCreateInfoEXT input_divisor_ci;
- input_divisor_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT;
- input_divisor_ci.pNext = nullptr;
- input_divisor_ci.vertexBindingDivisorCount = static_cast<u32>(vertex_binding_divisors.size());
- input_divisor_ci.pVertexBindingDivisors = vertex_binding_divisors.data();
+ VkPipelineVertexInputStateCreateInfo vertex_input_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .vertexBindingDescriptionCount = static_cast<u32>(vertex_bindings.size()),
+ .pVertexBindingDescriptions = vertex_bindings.data(),
+ .vertexAttributeDescriptionCount = static_cast<u32>(vertex_attributes.size()),
+ .pVertexAttributeDescriptions = vertex_attributes.data(),
+ };
+
+ const VkPipelineVertexInputDivisorStateCreateInfoEXT input_divisor_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .vertexBindingDivisorCount = static_cast<u32>(vertex_binding_divisors.size()),
+ .pVertexBindingDivisors = vertex_binding_divisors.data(),
+ };
if (!vertex_binding_divisors.empty()) {
vertex_input_ci.pNext = &input_divisor_ci;
}
- VkPipelineInputAssemblyStateCreateInfo input_assembly_ci;
- input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
- input_assembly_ci.pNext = nullptr;
- input_assembly_ci.flags = 0;
- input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, ia.topology);
- input_assembly_ci.primitiveRestartEnable =
- ia.primitive_restart_enable && SupportsPrimitiveRestart(input_assembly_ci.topology);
-
- VkPipelineTessellationStateCreateInfo tessellation_ci;
- tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
- tessellation_ci.pNext = nullptr;
- tessellation_ci.flags = 0;
- tessellation_ci.patchControlPoints = ts.patch_control_points;
-
- VkPipelineViewportStateCreateInfo viewport_ci;
- viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
- viewport_ci.pNext = nullptr;
- viewport_ci.flags = 0;
- viewport_ci.viewportCount = Maxwell::NumViewports;
- viewport_ci.pViewports = nullptr;
- viewport_ci.scissorCount = Maxwell::NumViewports;
- viewport_ci.pScissors = nullptr;
-
- VkPipelineRasterizationStateCreateInfo rasterization_ci;
- rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
- rasterization_ci.pNext = nullptr;
- rasterization_ci.flags = 0;
- rasterization_ci.depthClampEnable = rs.depth_clamp_enable;
- rasterization_ci.rasterizerDiscardEnable = VK_FALSE;
- rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
- rasterization_ci.cullMode =
- rs.cull_enable ? MaxwellToVK::CullFace(rs.cull_face) : VK_CULL_MODE_NONE;
- rasterization_ci.frontFace = MaxwellToVK::FrontFace(rs.front_face);
- rasterization_ci.depthBiasEnable = rs.depth_bias_enable;
- rasterization_ci.depthBiasConstantFactor = 0.0f;
- rasterization_ci.depthBiasClamp = 0.0f;
- rasterization_ci.depthBiasSlopeFactor = 0.0f;
- rasterization_ci.lineWidth = 1.0f;
-
- VkPipelineMultisampleStateCreateInfo multisample_ci;
- multisample_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
- multisample_ci.pNext = nullptr;
- multisample_ci.flags = 0;
- multisample_ci.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
- multisample_ci.sampleShadingEnable = VK_FALSE;
- multisample_ci.minSampleShading = 0.0f;
- multisample_ci.pSampleMask = nullptr;
- multisample_ci.alphaToCoverageEnable = VK_FALSE;
- multisample_ci.alphaToOneEnable = VK_FALSE;
-
- VkPipelineDepthStencilStateCreateInfo depth_stencil_ci;
- depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
- depth_stencil_ci.pNext = nullptr;
- depth_stencil_ci.flags = 0;
- depth_stencil_ci.depthTestEnable = ds.depth_test_enable;
- depth_stencil_ci.depthWriteEnable = ds.depth_write_enable;
- depth_stencil_ci.depthCompareOp = ds.depth_test_enable
- ? MaxwellToVK::ComparisonOp(ds.depth_test_function)
- : VK_COMPARE_OP_ALWAYS;
- depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable;
- depth_stencil_ci.stencilTestEnable = ds.stencil_enable;
- depth_stencil_ci.front = GetStencilFaceState(ds.front_stencil);
- depth_stencil_ci.back = GetStencilFaceState(ds.back_stencil);
- depth_stencil_ci.minDepthBounds = 0.0f;
- depth_stencil_ci.maxDepthBounds = 0.0f;
+ const auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, state.topology);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .topology = MaxwellToVK::PrimitiveTopology(device, state.topology),
+ .primitiveRestartEnable = state.primitive_restart_enable != 0 &&
+ SupportsPrimitiveRestart(input_assembly_topology),
+ };
+
+ const VkPipelineTessellationStateCreateInfo tessellation_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .patchControlPoints = state.patch_control_points_minus_one.Value() + 1,
+ };
+
+ VkPipelineViewportStateCreateInfo viewport_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .viewportCount = Maxwell::NumViewports,
+ .pViewports = nullptr,
+ .scissorCount = Maxwell::NumViewports,
+ .pScissors = nullptr,
+ };
+
+ std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles;
+ std::transform(viewport_swizzles.begin(), viewport_swizzles.end(), swizzles.begin(),
+ UnpackViewportSwizzle);
+ VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV,
+ .pNext = nullptr,
+ .flags = 0,
+ .viewportCount = Maxwell::NumViewports,
+ .pViewportSwizzles = swizzles.data(),
+ };
+ if (device.IsNvViewportSwizzleSupported()) {
+ viewport_ci.pNext = &swizzle_ci;
+ }
+
+ const VkPipelineRasterizationStateCreateInfo rasterization_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthClampEnable =
+ static_cast<VkBool32>(state.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE),
+ .rasterizerDiscardEnable =
+ static_cast<VkBool32>(state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE),
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode =
+ dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE,
+ .frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace()),
+ .depthBiasEnable = state.depth_bias_enable,
+ .depthBiasConstantFactor = 0.0f,
+ .depthBiasClamp = 0.0f,
+ .depthBiasSlopeFactor = 0.0f,
+ .lineWidth = 1.0f,
+ };
+
+ const VkPipelineMultisampleStateCreateInfo multisample_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ .sampleShadingEnable = VK_FALSE,
+ .minSampleShading = 0.0f,
+ .pSampleMask = nullptr,
+ .alphaToCoverageEnable = VK_FALSE,
+ .alphaToOneEnable = VK_FALSE,
+ };
+
+ const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthTestEnable = dynamic.depth_test_enable,
+ .depthWriteEnable = dynamic.depth_write_enable,
+ .depthCompareOp = dynamic.depth_test_enable
+ ? MaxwellToVK::ComparisonOp(dynamic.DepthTestFunc())
+ : VK_COMPARE_OP_ALWAYS,
+ .depthBoundsTestEnable = dynamic.depth_bounds_enable,
+ .stencilTestEnable = dynamic.stencil_enable,
+ .front = GetStencilFaceState(dynamic.front),
+ .back = GetStencilFaceState(dynamic.back),
+ .minDepthBounds = 0.0f,
+ .maxDepthBounds = 0.0f,
+ };
std::array<VkPipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments;
- const std::size_t num_attachments =
- std::min(cd.attachments_count, renderpass_params.color_attachments.size());
- for (std::size_t i = 0; i < num_attachments; ++i) {
- static constexpr std::array component_table = {
- VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT,
- VK_COLOR_COMPONENT_A_BIT};
- const auto& blend = cd.attachments[i];
+ const auto num_attachments = static_cast<std::size_t>(renderpass_params.num_color_attachments);
+ for (std::size_t index = 0; index < num_attachments; ++index) {
+ static constexpr std::array COMPONENT_TABLE{
+ VK_COLOR_COMPONENT_R_BIT,
+ VK_COLOR_COMPONENT_G_BIT,
+ VK_COLOR_COMPONENT_B_BIT,
+ VK_COLOR_COMPONENT_A_BIT,
+ };
+ const auto& blend = state.attachments[index];
VkColorComponentFlags color_components = 0;
- for (std::size_t j = 0; j < component_table.size(); ++j) {
- if (blend.components[j]) {
- color_components |= component_table[j];
+ for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) {
+ if (blend.Mask()[i]) {
+ color_components |= COMPONENT_TABLE[i];
}
}
- VkPipelineColorBlendAttachmentState& attachment = cb_attachments[i];
- attachment.blendEnable = blend.enable;
- attachment.srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.src_rgb_func);
- attachment.dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.dst_rgb_func);
- attachment.colorBlendOp = MaxwellToVK::BlendEquation(blend.rgb_equation);
- attachment.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.src_a_func);
- attachment.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.dst_a_func);
- attachment.alphaBlendOp = MaxwellToVK::BlendEquation(blend.a_equation);
- attachment.colorWriteMask = color_components;
+ cb_attachments[index] = {
+ .blendEnable = blend.enable != 0,
+ .srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()),
+ .dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()),
+ .colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()),
+ .srcAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.SourceAlphaFactor()),
+ .dstAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.DestAlphaFactor()),
+ .alphaBlendOp = MaxwellToVK::BlendEquation(blend.EquationAlpha()),
+ .colorWriteMask = color_components,
+ };
}
- VkPipelineColorBlendStateCreateInfo color_blend_ci;
- color_blend_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
- color_blend_ci.pNext = nullptr;
- color_blend_ci.flags = 0;
- color_blend_ci.logicOpEnable = VK_FALSE;
- color_blend_ci.logicOp = VK_LOGIC_OP_COPY;
- color_blend_ci.attachmentCount = static_cast<u32>(num_attachments);
- color_blend_ci.pAttachments = cb_attachments.data();
- std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants));
-
- static constexpr std::array dynamic_states = {
+ const VkPipelineColorBlendStateCreateInfo color_blend_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_COPY,
+ .attachmentCount = static_cast<u32>(num_attachments),
+ .pAttachments = cb_attachments.data(),
+ .blendConstants = {},
+ };
+
+ std::vector dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
- VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE};
+ VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
+ };
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ static constexpr std::array extended{
+ VK_DYNAMIC_STATE_CULL_MODE_EXT,
+ VK_DYNAMIC_STATE_FRONT_FACE_EXT,
+ VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
+ VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_STENCIL_OP_EXT,
+ };
+ dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
+ }
- VkPipelineDynamicStateCreateInfo dynamic_state_ci;
- dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
- dynamic_state_ci.pNext = nullptr;
- dynamic_state_ci.flags = 0;
- dynamic_state_ci.dynamicStateCount = static_cast<u32>(dynamic_states.size());
- dynamic_state_ci.pDynamicStates = dynamic_states.data();
+ const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .dynamicStateCount = static_cast<u32>(dynamic_states.size()),
+ .pDynamicStates = dynamic_states.data(),
+ };
- VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci;
- subgroup_size_ci.sType =
- VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT;
- subgroup_size_ci.pNext = nullptr;
- subgroup_size_ci.requiredSubgroupSize = GuestWarpSize;
+ const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .requiredSubgroupSize = GuestWarpSize,
+ };
std::vector<VkPipelineShaderStageCreateInfo> shader_stages;
std::size_t module_index = 0;
@@ -343,6 +433,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
if (!program[stage]) {
continue;
}
+
VkPipelineShaderStageCreateInfo& stage_ci = shader_stages.emplace_back();
stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stage_ci.pNext = nullptr;
@@ -357,26 +448,27 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
}
}
- VkGraphicsPipelineCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.stageCount = static_cast<u32>(shader_stages.size());
- ci.pStages = shader_stages.data();
- ci.pVertexInputState = &vertex_input_ci;
- ci.pInputAssemblyState = &input_assembly_ci;
- ci.pTessellationState = &tessellation_ci;
- ci.pViewportState = &viewport_ci;
- ci.pRasterizationState = &rasterization_ci;
- ci.pMultisampleState = &multisample_ci;
- ci.pDepthStencilState = &depth_stencil_ci;
- ci.pColorBlendState = &color_blend_ci;
- ci.pDynamicState = &dynamic_state_ci;
- ci.layout = *layout;
- ci.renderPass = renderpass;
- ci.subpass = 0;
- ci.basePipelineHandle = nullptr;
- ci.basePipelineIndex = 0;
+ const VkGraphicsPipelineCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(shader_stages.size()),
+ .pStages = shader_stages.data(),
+ .pVertexInputState = &vertex_input_ci,
+ .pInputAssemblyState = &input_assembly_ci,
+ .pTessellationState = &tessellation_ci,
+ .pViewportState = &viewport_ci,
+ .pRasterizationState = &rasterization_ci,
+ .pMultisampleState = &multisample_ci,
+ .pDepthStencilState = &depth_stencil_ci,
+ .pColorBlendState = &color_blend_ci,
+ .pDynamicState = &dynamic_state_ci,
+ .layout = *layout,
+ .renderPass = renderpass,
+ .subpass = 0,
+ .basePipelineHandle = nullptr,
+ .basePipelineIndex = 0,
+ };
return device.GetLogical().CreateGraphicsPipeline(ci);
}
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 7aba70960..58aa35efd 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -5,16 +5,13 @@
#pragma once
#include <array>
-#include <memory>
#include <optional>
-#include <unordered_map>
#include <vector>
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/wrapper.h"
@@ -22,7 +19,27 @@ namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-struct GraphicsPipelineCacheKey;
+struct GraphicsPipelineCacheKey {
+ RenderPassParams renderpass_params;
+ u32 padding;
+ std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
+ FixedPipelineState fixed_state;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
+
+ bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+
+ std::size_t Size() const noexcept {
+ return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
+ }
+};
+static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
+static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
+static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
class VKDescriptorPool;
class VKDevice;
@@ -57,6 +74,10 @@ public:
return renderpass;
}
+ GraphicsPipelineCacheKey GetCacheKey() const {
+ return cache_key;
+ }
+
private:
vk::DescriptorSetLayout CreateDescriptorSetLayout(
vk::Span<VkDescriptorSetLayoutBinding> bindings) const;
@@ -73,7 +94,7 @@ private:
const VKDevice& device;
VKScheduler& scheduler;
- const FixedPipelineState fixed_state;
+ const GraphicsPipelineCacheKey cache_key;
const u64 hash;
vk::DescriptorSetLayout descriptor_set_layout;
diff --git a/src/video_core/renderer_vulkan/vk_image.cpp b/src/video_core/renderer_vulkan/vk_image.cpp
index 9bceb3861..1c418ea17 100644
--- a/src/video_core/renderer_vulkan/vk_image.cpp
+++ b/src/video_core/renderer_vulkan/vk_image.cpp
@@ -102,21 +102,29 @@ bool VKImage::HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num
void VKImage::CreatePresentView() {
// Image type has to be 2D to be presented.
- VkImageViewCreateInfo image_view_ci;
- image_view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
- image_view_ci.pNext = nullptr;
- image_view_ci.flags = 0;
- image_view_ci.image = *image;
- image_view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
- image_view_ci.format = format;
- image_view_ci.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
- VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
- image_view_ci.subresourceRange.aspectMask = aspect_mask;
- image_view_ci.subresourceRange.baseMipLevel = 0;
- image_view_ci.subresourceRange.levelCount = 1;
- image_view_ci.subresourceRange.baseArrayLayer = 0;
- image_view_ci.subresourceRange.layerCount = 1;
- present_view = device.GetLogical().CreateImageView(image_view_ci);
+ present_view = device.GetLogical().CreateImageView({
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = *image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = format,
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ {
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ });
}
VKImage::SubrangeState& VKImage::GetSubrangeState(u32 layer, u32 level) noexcept {
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
new file mode 100644
index 000000000..ae26e558d
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <chrono>
+
+#include "core/settings.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+using namespace std::chrono_literals;
+
+MasterSemaphore::MasterSemaphore(const VKDevice& device) {
+ static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR,
+ .initialValue = 0,
+ };
+ static constexpr VkSemaphoreCreateInfo semaphore_ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = &semaphore_type_ci,
+ .flags = 0,
+ };
+ semaphore = device.GetLogical().CreateSemaphore(semaphore_ci);
+
+ if (!Settings::values.renderer_debug) {
+ return;
+ }
+ // Validation layers have a bug where they fail to track resource usage when using timeline
+ // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
+ // a separate thread waiting for each timeline semaphore value.
+ debug_thread = std::thread([this] {
+ u64 counter = 0;
+ while (!shutdown) {
+ if (semaphore.Wait(counter, 10'000'000)) {
+ ++counter;
+ }
+ }
+ });
+}
+
+MasterSemaphore::~MasterSemaphore() {
+ shutdown = true;
+
+ // This thread might not be started
+ if (debug_thread.joinable()) {
+ debug_thread.join();
+ }
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
new file mode 100644
index 000000000..0e93706d7
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -0,0 +1,70 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+class VKDevice;
+
+class MasterSemaphore {
+public:
+ explicit MasterSemaphore(const VKDevice& device);
+ ~MasterSemaphore();
+
+ /// Returns the current logical tick.
+ [[nodiscard]] u64 CurrentTick() const noexcept {
+ return current_tick;
+ }
+
+ /// Returns the timeline semaphore handle.
+ [[nodiscard]] VkSemaphore Handle() const noexcept {
+ return *semaphore;
+ }
+
+ /// Returns true when a tick has been hit by the GPU.
+ [[nodiscard]] bool IsFree(u64 tick) {
+ return gpu_tick >= tick;
+ }
+
+ /// Advance to the logical tick.
+ void NextTick() noexcept {
+ ++current_tick;
+ }
+
+ /// Refresh the known GPU tick
+ void Refresh() {
+ gpu_tick = semaphore.GetCounter();
+ }
+
+ /// Waits for a tick to be hit on the GPU
+ void Wait(u64 tick) {
+ // No need to wait if the GPU is ahead of the tick
+ if (IsFree(tick)) {
+ return;
+ }
+ // Update the GPU tick and try again
+ Refresh();
+ if (IsFree(tick)) {
+ return;
+ }
+ // If none of the above is hit, fallback to a regular wait
+ semaphore.Wait(tick);
+ }
+
+private:
+ vk::Semaphore semaphore; ///< Timeline semaphore.
+ std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
+ std::atomic<u64> current_tick{1}; ///< Current logical tick.
+ std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed.
+ std::thread debug_thread; ///< Debug thread to workaround validation layer bugs.
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.cpp b/src/video_core/renderer_vulkan/vk_memory_manager.cpp
index 6a9e658bf..24c8960ac 100644
--- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_memory_manager.cpp
@@ -118,8 +118,7 @@ private:
};
VKMemoryManager::VKMemoryManager(const VKDevice& device)
- : device{device}, properties{device.GetPhysical().GetMemoryProperties()},
- is_memory_unified{GetMemoryUnified(properties)} {}
+ : device{device}, properties{device.GetPhysical().GetMemoryProperties()} {}
VKMemoryManager::~VKMemoryManager() = default;
@@ -179,13 +178,12 @@ bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 t
}();
// Try to allocate found type.
- VkMemoryAllocateInfo memory_ai;
- memory_ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
- memory_ai.pNext = nullptr;
- memory_ai.allocationSize = size;
- memory_ai.memoryTypeIndex = type;
-
- vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory(memory_ai);
+ vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .allocationSize = size,
+ .memoryTypeIndex = type,
+ });
if (!memory) {
LOG_CRITICAL(Render_Vulkan, "Device allocation failed!");
return false;
@@ -209,16 +207,6 @@ VKMemoryCommit VKMemoryManager::TryAllocCommit(const VkMemoryRequirements& requi
return {};
}
-bool VKMemoryManager::GetMemoryUnified(const VkPhysicalDeviceMemoryProperties& properties) {
- for (u32 heap_index = 0; heap_index < properties.memoryHeapCount; ++heap_index) {
- if (!(properties.memoryHeaps[heap_index].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)) {
- // Memory is considered unified when heaps are device local only.
- return false;
- }
- }
- return true;
-}
-
VKMemoryCommitImpl::VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation,
const vk::DeviceMemory& memory, u64 begin, u64 end)
: device{device}, memory{memory}, interval{begin, end}, allocation{allocation} {}
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.h b/src/video_core/renderer_vulkan/vk_memory_manager.h
index 35ee54d30..1af88e3d4 100644
--- a/src/video_core/renderer_vulkan/vk_memory_manager.h
+++ b/src/video_core/renderer_vulkan/vk_memory_manager.h
@@ -32,7 +32,7 @@ public:
* memory. When passing false, it will try to allocate device local memory.
* @returns A memory commit.
*/
- VKMemoryCommit Commit(const VkMemoryRequirements& reqs, bool host_visible);
+ VKMemoryCommit Commit(const VkMemoryRequirements& requirements, bool host_visible);
/// Commits memory required by the buffer and binds it.
VKMemoryCommit Commit(const vk::Buffer& buffer, bool host_visible);
@@ -40,11 +40,6 @@ public:
/// Commits memory required by the image and binds it.
VKMemoryCommit Commit(const vk::Image& image, bool host_visible);
- /// Returns true if the memory allocations are done always in host visible and coherent memory.
- bool IsMemoryUnified() const {
- return is_memory_unified;
- }
-
private:
/// Allocates a chunk of memory.
bool AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, u64 size);
@@ -53,12 +48,8 @@ private:
VKMemoryCommit TryAllocCommit(const VkMemoryRequirements& requirements,
VkMemoryPropertyFlags wanted_properties);
- /// Returns true if the device uses an unified memory model.
- static bool GetMemoryUnified(const VkPhysicalDeviceMemoryProperties& properties);
-
- const VKDevice& device; ///< Device handler.
- const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
- const bool is_memory_unified; ///< True if memory model is unified.
+ const VKDevice& device; ///< Device handler.
+ const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
std::vector<std::unique_ptr<VKMemoryAllocation>> allocations; ///< Current allocations.
};
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 90e3a8edd..dedc9c466 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -22,17 +22,24 @@
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/shader/compiler_settings.h"
+#include "video_core/shader/memory_util.h"
+#include "video_core/shader_cache.h"
+#include "video_core/shader_notify.h"
namespace Vulkan {
MICROPROFILE_DECLARE(Vulkan_PipelineCache);
using Tegra::Engines::ShaderType;
+using VideoCommon::Shader::GetShaderAddress;
+using VideoCommon::Shader::GetShaderCode;
+using VideoCommon::Shader::KERNEL_MAIN_OFFSET;
+using VideoCommon::Shader::ProgramCode;
+using VideoCommon::Shader::STAGE_MAIN_OFFSET;
namespace {
@@ -40,65 +47,12 @@ constexpr VkDescriptorType UNIFORM_BUFFER = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
constexpr VkDescriptorType STORAGE_BUFFER = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
constexpr VkDescriptorType UNIFORM_TEXEL_BUFFER = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
constexpr VkDescriptorType COMBINED_IMAGE_SAMPLER = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+constexpr VkDescriptorType STORAGE_TEXEL_BUFFER = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
constexpr VkDescriptorType STORAGE_IMAGE = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
constexpr VideoCommon::Shader::CompilerSettings compiler_settings{
VideoCommon::Shader::CompileDepth::FullDecompile};
-/// Gets the address for the specified shader stage program
-GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program) {
- const auto& gpu{system.GPU().Maxwell3D()};
- const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
- return gpu.regs.code_address.CodeAddress() + shader_config.offset;
-}
-
-/// Gets if the current instruction offset is a scheduler instruction
-constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
- // Sched instructions appear once every 4 instructions.
- constexpr std::size_t SchedPeriod = 4;
- const std::size_t absolute_offset = offset - main_offset;
- return (absolute_offset % SchedPeriod) == 0;
-}
-
-/// Calculates the size of a program stream
-std::size_t CalculateProgramSize(const ProgramCode& program, bool is_compute) {
- const std::size_t start_offset = is_compute ? 0 : 10;
- // This is the encoded version of BRA that jumps to itself. All Nvidia
- // shaders end with one.
- constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL;
- constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL;
- std::size_t offset = start_offset;
- while (offset < program.size()) {
- const u64 instruction = program[offset];
- if (!IsSchedInstruction(offset, start_offset)) {
- if ((instruction & mask) == self_jumping_branch) {
- // End on Maxwell's "nop" instruction
- break;
- }
- if (instruction == 0) {
- break;
- }
- }
- ++offset;
- }
- // The last instruction is included in the program size
- return std::min(offset + 1, program.size());
-}
-
-/// Gets the shader program code from memory for the specified address
-ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr,
- const u8* host_ptr, bool is_compute) {
- ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
- ASSERT_OR_EXECUTE(host_ptr != nullptr, {
- std::fill(program_code.begin(), program_code.end(), 0);
- return program_code;
- });
- memory_manager.ReadBlockUnsafe(gpu_addr, program_code.data(),
- program_code.size() * sizeof(u64));
- program_code.resize(CalculateProgramSize(program_code, is_compute));
- return program_code;
-}
-
constexpr std::size_t GetStageFromProgram(std::size_t program) {
return program == 0 ? 0 : program - 1;
}
@@ -133,14 +87,15 @@ void AddBindings(std::vector<VkDescriptorSetLayoutBinding>& bindings, u32& bindi
u32 count = 1;
if constexpr (descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) {
// Combined image samplers can be arrayed.
- count = container[i].Size();
+ count = container[i].size;
}
- VkDescriptorSetLayoutBinding& entry = bindings.emplace_back();
- entry.binding = binding++;
- entry.descriptorType = descriptor_type;
- entry.descriptorCount = count;
- entry.stageFlags = stage_flags;
- entry.pImmutableSamplers = nullptr;
+ bindings.push_back({
+ .binding = binding++,
+ .descriptorType = descriptor_type,
+ .descriptorCount = count,
+ .stageFlags = stage_flags,
+ .pImmutableSamplers = nullptr,
+ });
}
}
@@ -153,96 +108,133 @@ u32 FillDescriptorLayout(const ShaderEntries& entries,
u32 binding = base_binding;
AddBindings<UNIFORM_BUFFER>(bindings, binding, flags, entries.const_buffers);
AddBindings<STORAGE_BUFFER>(bindings, binding, flags, entries.global_buffers);
- AddBindings<UNIFORM_TEXEL_BUFFER>(bindings, binding, flags, entries.texel_buffers);
+ AddBindings<UNIFORM_TEXEL_BUFFER>(bindings, binding, flags, entries.uniform_texels);
AddBindings<COMBINED_IMAGE_SAMPLER>(bindings, binding, flags, entries.samplers);
+ AddBindings<STORAGE_TEXEL_BUFFER>(bindings, binding, flags, entries.storage_texels);
AddBindings<STORAGE_IMAGE>(bindings, binding, flags, entries.images);
return binding;
}
} // Anonymous namespace
-CachedShader::CachedShader(Core::System& system, Tegra::Engines::ShaderType stage,
- GPUVAddr gpu_addr, VAddr cpu_addr, ProgramCode program_code,
- u32 main_offset)
- : RasterizerCacheObject{cpu_addr}, gpu_addr{gpu_addr}, program_code{std::move(program_code)},
- registry{stage, GetEngine(system, stage)}, shader_ir{this->program_code, main_offset,
- compiler_settings, registry},
- entries{GenerateShaderEntries(shader_ir)} {}
-
-CachedShader::~CachedShader() = default;
-
-Tegra::Engines::ConstBufferEngineInterface& CachedShader::GetEngine(
- Core::System& system, Tegra::Engines::ShaderType stage) {
- if (stage == Tegra::Engines::ShaderType::Compute) {
- return system.GPU().KeplerCompute();
- } else {
- return system.GPU().Maxwell3D();
- }
+std::size_t GraphicsPipelineCacheKey::Hash() const noexcept {
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
+ return static_cast<std::size_t>(hash);
+}
+
+bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
+ return std::memcmp(&rhs, this, Size()) == 0;
+}
+
+std::size_t ComputePipelineCacheKey::Hash() const noexcept {
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
+ return static_cast<std::size_t>(hash);
+}
+
+bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) const noexcept {
+ return std::memcmp(&rhs, this, sizeof *this) == 0;
}
-VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache)
- : RasterizerCache{rasterizer}, system{system}, device{device}, scheduler{scheduler},
- descriptor_pool{descriptor_pool}, update_descriptor_queue{update_descriptor_queue},
- renderpass_cache{renderpass_cache} {}
+Shader::Shader(Tegra::Engines::ConstBufferEngineInterface& engine, Tegra::Engines::ShaderType stage,
+ GPUVAddr gpu_addr_, VAddr cpu_addr, VideoCommon::Shader::ProgramCode program_code_,
+ u32 main_offset)
+ : gpu_addr(gpu_addr_), program_code(std::move(program_code_)), registry(stage, engine),
+ shader_ir(program_code, main_offset, compiler_settings, registry),
+ entries(GenerateShaderEntries(shader_ir)) {}
+
+Shader::~Shader() = default;
+
+VKPipelineCache::VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const VKDevice& device_,
+ VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ VKRenderPassCache& renderpass_cache_)
+ : VideoCommon::ShaderCache<Shader>{rasterizer}, gpu{gpu_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, device{device_},
+ scheduler{scheduler_}, descriptor_pool{descriptor_pool_},
+ update_descriptor_queue{update_descriptor_queue_}, renderpass_cache{renderpass_cache_} {}
VKPipelineCache::~VKPipelineCache() = default;
-std::array<Shader, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
- const auto& gpu = system.GPU().Maxwell3D();
+std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
+ std::array<Shader*, Maxwell::MaxShaderProgram> shaders{};
- std::array<Shader, Maxwell::MaxShaderProgram> shaders;
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr program_addr{GetShaderAddress(system, program)};
- const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const GPUVAddr gpu_addr{GetShaderAddress(maxwell3d, program)};
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
- auto shader = cpu_addr ? TryGet(*cpu_addr) : nullptr;
- if (!shader) {
- const auto host_ptr{memory_manager.GetPointer(program_addr)};
- // No shader found - create a new one
- constexpr u32 stage_offset = 10;
- const auto stage = static_cast<Tegra::Engines::ShaderType>(index == 0 ? 0 : index - 1);
- auto code = GetShaderCode(memory_manager, program_addr, host_ptr, false);
+ Shader* result = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
+ if (!result) {
+ const u8* const host_ptr{gpu_memory.GetPointer(gpu_addr)};
- shader = std::make_shared<CachedShader>(system, stage, program_addr, *cpu_addr,
- std::move(code), stage_offset);
- Register(shader);
+ // No shader found - create a new one
+ static constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
+ const auto stage = static_cast<ShaderType>(index == 0 ? 0 : index - 1);
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, false);
+ const std::size_t size_in_bytes = code.size() * sizeof(u64);
+
+ auto shader = std::make_unique<Shader>(maxwell3d, stage, gpu_addr, *cpu_addr,
+ std::move(code), stage_offset);
+ result = shader.get();
+
+ if (cpu_addr) {
+ Register(std::move(shader), *cpu_addr, size_in_bytes);
+ } else {
+ null_shader = std::move(shader);
+ }
}
- shaders[index] = std::move(shader);
+ shaders[index] = result;
}
return last_shaders = shaders;
}
-VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) {
+VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
+ const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) {
MICROPROFILE_SCOPE(Vulkan_PipelineCache);
if (last_graphics_pipeline && last_graphics_key == key) {
- return *last_graphics_pipeline;
+ return last_graphics_pipeline;
}
last_graphics_key = key;
+ if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(gpu)) {
+ std::unique_lock lock{pipeline_cache};
+ const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
+ if (is_cache_miss) {
+ gpu.ShaderNotify().MarkSharderBuilding();
+ LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
+ const auto [program, bindings] = DecompileShaders(key.fixed_state);
+ async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool,
+ update_descriptor_queue, renderpass_cache, bindings,
+ program, key);
+ }
+ last_graphics_pipeline = pair->second.get();
+ return last_graphics_pipeline;
+ }
+
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
auto& entry = pair->second;
if (is_cache_miss) {
+ gpu.ShaderNotify().MarkSharderBuilding();
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
- const auto [program, bindings] = DecompileShaders(key);
+ const auto [program, bindings] = DecompileShaders(key.fixed_state);
entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool,
update_descriptor_queue, renderpass_cache, key,
bindings, program);
+ gpu.ShaderNotify().MarkShaderComplete();
}
- return *(last_graphics_pipeline = entry.get());
+ last_graphics_pipeline = entry.get();
+ return last_graphics_pipeline;
}
VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) {
@@ -255,29 +247,39 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
}
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
- auto& memory_manager = system.GPU().MemoryManager();
- const auto program_addr = key.shader;
+ const GPUVAddr gpu_addr = key.shader;
- const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
- auto shader = cpu_addr ? TryGet(*cpu_addr) : nullptr;
+ Shader* shader = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get();
if (!shader) {
// No shader found - create a new one
- const auto host_ptr = memory_manager.GetPointer(program_addr);
-
- auto code = GetShaderCode(memory_manager, program_addr, host_ptr, true);
- constexpr u32 kernel_main_offset = 0;
- shader = std::make_shared<CachedShader>(system, Tegra::Engines::ShaderType::Compute,
- program_addr, *cpu_addr, std::move(code),
- kernel_main_offset);
- Register(shader);
- }
+ const auto host_ptr = gpu_memory.GetPointer(gpu_addr);
- Specialization specialization;
- specialization.workgroup_size = key.workgroup_size;
- specialization.shared_memory_size = key.shared_memory_size;
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, true);
+ const std::size_t size_in_bytes = code.size() * sizeof(u64);
+
+ auto shader_info = std::make_unique<Shader>(kepler_compute, ShaderType::Compute, gpu_addr,
+ *cpu_addr, std::move(code), KERNEL_MAIN_OFFSET);
+ shader = shader_info.get();
+ if (cpu_addr) {
+ Register(std::move(shader_info), *cpu_addr, size_in_bytes);
+ } else {
+ null_kernel = std::move(shader_info);
+ }
+ }
+
+ const Specialization specialization{
+ .base_binding = 0,
+ .workgroup_size = key.workgroup_size,
+ .shared_memory_size = key.shared_memory_size,
+ .point_size = std::nullopt,
+ .enabled_attributes = {},
+ .attribute_types = {},
+ .ndc_minus_one_to_one = false,
+ };
const SPIRVShader spirv_shader{Decompile(device, shader->GetIR(), ShaderType::Compute,
shader->GetRegistry(), specialization),
shader->GetEntries()};
@@ -286,7 +288,13 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
return *entry;
}
-void VKPipelineCache::Unregister(const Shader& shader) {
+void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) {
+ gpu.ShaderNotify().MarkShaderComplete();
+ std::unique_lock lock{pipeline_cache};
+ graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline);
+}
+
+void VKPipelineCache::OnShaderRemoval(Shader* shader) {
bool finished = false;
const auto Finish = [&] {
// TODO(Rodrigo): Instead of finishing here, wait for the fences that use this pipeline and
@@ -318,25 +326,23 @@ void VKPipelineCache::Unregister(const Shader& shader) {
Finish();
it = compute_cache.erase(it);
}
-
- RasterizerCache::Unregister(shader);
}
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>>
-VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
- const auto& fixed_state = key.fixed_state;
- auto& memory_manager = system.GPU().MemoryManager();
- const auto& gpu = system.GPU().Maxwell3D();
-
+VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
Specialization specialization;
- if (fixed_state.input_assembly.topology == Maxwell::PrimitiveTopology::Points) {
- ASSERT(fixed_state.input_assembly.point_size != 0.0f);
- specialization.point_size = fixed_state.input_assembly.point_size;
+ if (fixed_state.topology == Maxwell::PrimitiveTopology::Points) {
+ float point_size;
+ std::memcpy(&point_size, &fixed_state.point_size, sizeof(float));
+ specialization.point_size = point_size;
+ ASSERT(point_size != 0.0f);
}
for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
- specialization.attribute_types[i] = fixed_state.vertex_input.attributes[i].type;
+ const auto& attribute = fixed_state.attributes[i];
+ specialization.enabled_attributes[i] = attribute.enabled.Value() != 0;
+ specialization.attribute_types[i] = attribute.Type();
}
- specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one;
+ specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one;
SPIRVProgram program;
std::vector<VkDescriptorSetLayoutBinding> bindings;
@@ -345,18 +351,16 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
const auto program_enum = static_cast<Maxwell::ShaderProgram>(index);
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
- const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum);
- const auto cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr);
- const auto shader = TryGet(*cpu_addr);
- ASSERT(shader);
+ const GPUVAddr gpu_addr = GetShaderAddress(maxwell3d, program_enum);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ Shader* const shader = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5
- const auto program_type = GetShaderType(program_enum);
+ const ShaderType program_type = GetShaderType(program_enum);
const auto& entries = shader->GetEntries();
program[stage] = {
Decompile(device, shader->GetIR(), program_type, shader->GetRegistry(), specialization),
@@ -383,14 +387,15 @@ void AddEntry(std::vector<VkDescriptorUpdateTemplateEntry>& template_entries, u3
if constexpr (descriptor_type == COMBINED_IMAGE_SAMPLER) {
for (u32 i = 0; i < count; ++i) {
- const u32 num_samplers = container[i].Size();
- VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back();
- entry.dstBinding = binding;
- entry.dstArrayElement = 0;
- entry.descriptorCount = num_samplers;
- entry.descriptorType = descriptor_type;
- entry.offset = offset;
- entry.stride = entry_size;
+ const u32 num_samplers = container[i].size;
+ template_entries.push_back({
+ .dstBinding = binding,
+ .dstArrayElement = 0,
+ .descriptorCount = num_samplers,
+ .descriptorType = descriptor_type,
+ .offset = offset,
+ .stride = entry_size,
+ });
++binding;
offset += num_samplers * entry_size;
@@ -398,26 +403,29 @@ void AddEntry(std::vector<VkDescriptorUpdateTemplateEntry>& template_entries, u3
return;
}
- if constexpr (descriptor_type == UNIFORM_TEXEL_BUFFER) {
- // Nvidia has a bug where updating multiple uniform texels at once causes the driver to
- // crash.
+ if constexpr (descriptor_type == UNIFORM_TEXEL_BUFFER ||
+ descriptor_type == STORAGE_TEXEL_BUFFER) {
+ // Nvidia has a bug where updating multiple texels at once causes the driver to crash.
+ // Note: Fixed in driver Windows 443.24, Linux 440.66.15
for (u32 i = 0; i < count; ++i) {
- VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back();
- entry.dstBinding = binding + i;
- entry.dstArrayElement = 0;
- entry.descriptorCount = 1;
- entry.descriptorType = descriptor_type;
- entry.offset = offset + i * entry_size;
- entry.stride = entry_size;
+ template_entries.push_back({
+ .dstBinding = binding + i,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = descriptor_type,
+ .offset = static_cast<std::size_t>(offset + i * entry_size),
+ .stride = entry_size,
+ });
}
} else if (count > 0) {
- VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back();
- entry.dstBinding = binding;
- entry.dstArrayElement = 0;
- entry.descriptorCount = count;
- entry.descriptorType = descriptor_type;
- entry.offset = offset;
- entry.stride = entry_size;
+ template_entries.push_back({
+ .dstBinding = binding,
+ .dstArrayElement = 0,
+ .descriptorCount = count,
+ .descriptorType = descriptor_type,
+ .offset = offset,
+ .stride = entry_size,
+ });
}
offset += count * entry_size;
binding += count;
@@ -428,8 +436,9 @@ void FillDescriptorUpdateTemplateEntries(
std::vector<VkDescriptorUpdateTemplateEntryKHR>& template_entries) {
AddEntry<UNIFORM_BUFFER>(template_entries, offset, binding, entries.const_buffers);
AddEntry<STORAGE_BUFFER>(template_entries, offset, binding, entries.global_buffers);
- AddEntry<UNIFORM_TEXEL_BUFFER>(template_entries, offset, binding, entries.texel_buffers);
+ AddEntry<UNIFORM_TEXEL_BUFFER>(template_entries, offset, binding, entries.uniform_texels);
AddEntry<COMBINED_IMAGE_SAMPLER>(template_entries, offset, binding, entries.samplers);
+ AddEntry<STORAGE_TEXEL_BUFFER>(template_entries, offset, binding, entries.storage_texels);
AddEntry<STORAGE_IMAGE>(template_entries, offset, binding, entries.images);
}
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 7ccdb7083..e558e6658 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -7,7 +7,6 @@
#include <array>
#include <cstddef>
#include <memory>
-#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
@@ -18,16 +17,16 @@
#include "common/common_types.h"
#include "video_core/engines/const_buffer_engine_interface.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/shader/async_shaders.h"
+#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
-#include "video_core/surface.h"
+#include "video_core/shader_cache.h"
namespace Core {
class System;
@@ -39,54 +38,27 @@ class RasterizerVulkan;
class VKComputePipeline;
class VKDescriptorPool;
class VKDevice;
-class VKFence;
class VKScheduler;
class VKUpdateDescriptorQueue;
-class CachedShader;
-using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using ProgramCode = std::vector<u64>;
+struct ComputePipelineCacheKey {
+ GPUVAddr shader;
+ u32 shared_memory_size;
+ std::array<u32, 3> workgroup_size;
-struct GraphicsPipelineCacheKey {
- FixedPipelineState fixed_state;
- std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
- RenderPassParams renderpass_params;
+ std::size_t Hash() const noexcept;
- std::size_t Hash() const noexcept {
- std::size_t hash = fixed_state.Hash();
- for (const auto& shader : shaders) {
- boost::hash_combine(hash, shader);
- }
- boost::hash_combine(hash, renderpass_params.Hash());
- return hash;
- }
+ bool operator==(const ComputePipelineCacheKey& rhs) const noexcept;
- bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
- return std::tie(fixed_state, shaders, renderpass_params) ==
- std::tie(rhs.fixed_state, rhs.shaders, rhs.renderpass_params);
- }
-};
-
-struct ComputePipelineCacheKey {
- GPUVAddr shader{};
- u32 shared_memory_size{};
- std::array<u32, 3> workgroup_size{};
-
- std::size_t Hash() const noexcept {
- return static_cast<std::size_t>(shader) ^
- ((static_cast<std::size_t>(shared_memory_size) >> 7) << 40) ^
- static_cast<std::size_t>(workgroup_size[0]) ^
- (static_cast<std::size_t>(workgroup_size[1]) << 16) ^
- (static_cast<std::size_t>(workgroup_size[2]) << 24);
- }
-
- bool operator==(const ComputePipelineCacheKey& rhs) const noexcept {
- return std::tie(shader, shared_memory_size, workgroup_size) ==
- std::tie(rhs.shader, rhs.shared_memory_size, rhs.workgroup_size);
+ bool operator!=(const ComputePipelineCacheKey& rhs) const noexcept {
+ return !operator==(rhs);
}
};
+static_assert(std::has_unique_object_representations_v<ComputePipelineCacheKey>);
+static_assert(std::is_trivially_copyable_v<ComputePipelineCacheKey>);
+static_assert(std::is_trivially_constructible_v<ComputePipelineCacheKey>);
} // namespace Vulkan
@@ -110,21 +82,22 @@ struct hash<Vulkan::ComputePipelineCacheKey> {
namespace Vulkan {
-class CachedShader final : public RasterizerCacheObject {
+class Shader {
public:
- explicit CachedShader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
- VAddr cpu_addr, ProgramCode program_code, u32 main_offset);
- ~CachedShader();
+ explicit Shader(Tegra::Engines::ConstBufferEngineInterface& engine,
+ Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, VAddr cpu_addr,
+ VideoCommon::Shader::ProgramCode program_code, u32 main_offset);
+ ~Shader();
GPUVAddr GetGpuAddr() const {
return gpu_addr;
}
- std::size_t GetSizeInBytes() const override {
- return program_code.size() * sizeof(u64);
+ VideoCommon::Shader::ShaderIR& GetIR() {
+ return shader_ir;
}
- VideoCommon::Shader::ShaderIR& GetIR() {
+ const VideoCommon::Shader::ShaderIR& GetIR() const {
return shader_ir;
}
@@ -132,61 +105,65 @@ public:
return registry;
}
- const VideoCommon::Shader::ShaderIR& GetIR() const {
- return shader_ir;
- }
-
const ShaderEntries& GetEntries() const {
return entries;
}
private:
- static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system,
- Tegra::Engines::ShaderType stage);
-
GPUVAddr gpu_addr{};
- ProgramCode program_code;
+ VideoCommon::Shader::ProgramCode program_code;
VideoCommon::Shader::Registry registry;
VideoCommon::Shader::ShaderIR shader_ir;
ShaderEntries entries;
};
-class VKPipelineCache final : public RasterizerCache<Shader> {
+class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
+ explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::KeplerCompute& kepler_compute,
+ Tegra::MemoryManager& gpu_memory, const VKDevice& device,
+ VKScheduler& scheduler, VKDescriptorPool& descriptor_pool,
VKUpdateDescriptorQueue& update_descriptor_queue,
VKRenderPassCache& renderpass_cache);
- ~VKPipelineCache();
+ ~VKPipelineCache() override;
- std::array<Shader, Maxwell::MaxShaderProgram> GetShaders();
+ std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders();
- VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key);
+ VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key,
+ VideoCommon::Shader::AsyncShaders& async_shaders);
VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key);
-protected:
- void Unregister(const Shader& shader) override;
+ void EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline);
- void FlushObjectInner(const Shader& object) override {}
+protected:
+ void OnShaderRemoval(Shader* shader) final;
private:
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders(
- const GraphicsPipelineCacheKey& key);
+ const FixedPipelineState& fixed_state);
+
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
- Core::System& system;
const VKDevice& device;
VKScheduler& scheduler;
VKDescriptorPool& descriptor_pool;
VKUpdateDescriptorQueue& update_descriptor_queue;
VKRenderPassCache& renderpass_cache;
- std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
+ std::unique_ptr<Shader> null_shader;
+ std::unique_ptr<Shader> null_kernel;
+
+ std::array<Shader*, Maxwell::MaxShaderProgram> last_shaders{};
GraphicsPipelineCacheKey last_graphics_key;
VKGraphicsPipeline* last_graphics_pipeline = nullptr;
+ std::mutex pipeline_cache;
std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>>
graphics_cache;
std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 0966c7ff7..ee2d871e3 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -4,41 +4,38 @@
#include <algorithm>
#include <cstddef>
-#include <cstdint>
#include <utility>
#include <vector>
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
+using VideoCore::QueryType;
+
namespace {
constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION};
-constexpr VkQueryType GetTarget(VideoCore::QueryType type) {
+constexpr VkQueryType GetTarget(QueryType type) {
return QUERY_TARGETS[static_cast<std::size_t>(type)];
}
} // Anonymous namespace
-QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {}
+QueryPool::QueryPool(const VKDevice& device_, VKScheduler& scheduler, QueryType type_)
+ : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {}
QueryPool::~QueryPool() = default;
-void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) {
- device = &device_;
- type = type_;
-}
-
-std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
+std::pair<VkQueryPool, u32> QueryPool::Commit() {
std::size_t index;
do {
- index = CommitResource(fence);
+ index = CommitResource();
} while (usage[index]);
usage[index] = true;
@@ -48,14 +45,14 @@ std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
void QueryPool::Allocate(std::size_t begin, std::size_t end) {
usage.resize(end);
- VkQueryPoolCreateInfo query_pool_ci;
- query_pool_ci.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
- query_pool_ci.pNext = nullptr;
- query_pool_ci.flags = 0;
- query_pool_ci.queryType = GetTarget(type);
- query_pool_ci.queryCount = static_cast<u32>(end - begin);
- query_pool_ci.pipelineStatistics = 0;
- pools.push_back(device->GetLogical().CreateQueryPool(query_pool_ci));
+ pools.push_back(device.GetLogical().CreateQueryPool({
+ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .queryType = GetTarget(type),
+ .queryCount = static_cast<u32>(end - begin),
+ .pipelineStatistics = 0,
+ }));
}
void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
@@ -69,30 +66,39 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
}
-VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
const VKDevice& device, VKScheduler& scheduler)
- : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
- QueryPool>{system, rasterizer},
- device{device}, scheduler{scheduler} {
- for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
- query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
+ : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream,
+ HostCounter>{rasterizer, maxwell3d, gpu_memory},
+ device{device}, scheduler{scheduler}, query_pools{
+ QueryPool{device, scheduler,
+ QueryType::SamplesPassed},
+ } {}
+
+VKQueryCache::~VKQueryCache() {
+ // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class
+ // destructor is called. The query cache should be redesigned to have a proper ownership model
+ // instead of using shared pointers.
+ for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) {
+ auto& stream = Stream(static_cast<QueryType>(query_type));
+ stream.Update(false);
+ stream.Reset();
}
}
-VKQueryCache::~VKQueryCache() = default;
-
-std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(VideoCore::QueryType type) {
- return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence());
+std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(QueryType type) {
+ return query_pools[static_cast<std::size_t>(type)].Commit();
}
-void VKQueryCache::Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query) {
+void VKQueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
query_pools[static_cast<std::size_t>(type)].Reserve(query);
}
HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
- VideoCore::QueryType type)
+ QueryType type)
: VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache},
- type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} {
+ type{type}, query{cache.AllocateQuery(type)}, tick{cache.Scheduler().CurrentTick()} {
const vk::Device* logical = &cache.Device().GetLogical();
cache.Scheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
logical->ResetQueryPoolEXT(query.first, query.second, 1);
@@ -110,11 +116,22 @@ void HostCounter::EndQuery() {
}
u64 HostCounter::BlockingQuery() const {
- if (ticks >= cache.Scheduler().Ticks()) {
+ if (tick >= cache.Scheduler().CurrentTick()) {
cache.Scheduler().Flush();
}
- return cache.Device().GetLogical().GetQueryResult<u64>(
- query.first, query.second, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
+ u64 data;
+ const VkResult result = cache.Device().GetLogical().GetQueryResults(
+ query.first, query.second, 1, sizeof(data), &data, sizeof(data),
+ VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
+ switch (result) {
+ case VK_SUCCESS:
+ return data;
+ case VK_ERROR_DEVICE_LOST:
+ cache.Device().ReportLoss();
+ [[fallthrough]];
+ default:
+ throw vk::Exception(result);
+ }
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index b63784f4b..2e57fb75d 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -5,14 +5,13 @@
#pragma once
#include <cstddef>
-#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "video_core/query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace VideoCore {
@@ -29,14 +28,12 @@ class VKScheduler;
using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>;
-class QueryPool final : public VKFencedPool {
+class QueryPool final : public ResourcePool {
public:
- explicit QueryPool();
+ explicit QueryPool(const VKDevice& device, VKScheduler& scheduler, VideoCore::QueryType type);
~QueryPool() override;
- void Initialize(const VKDevice& device, VideoCore::QueryType type);
-
- std::pair<VkQueryPool, u32> Commit(VKFence& fence);
+ std::pair<VkQueryPool, u32> Commit();
void Reserve(std::pair<VkQueryPool, u32> query);
@@ -46,18 +43,18 @@ protected:
private:
static constexpr std::size_t GROW_STEP = 512;
- const VKDevice* device = nullptr;
- VideoCore::QueryType type = {};
+ const VKDevice& device;
+ const VideoCore::QueryType type;
std::vector<vk::QueryPool> pools;
std::vector<bool> usage;
};
class VKQueryCache final
- : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
- QueryPool> {
+ : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+ explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
const VKDevice& device, VKScheduler& scheduler);
~VKQueryCache();
@@ -76,6 +73,7 @@ public:
private:
const VKDevice& device;
VKScheduler& scheduler;
+ std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
};
class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> {
@@ -92,7 +90,7 @@ private:
VKQueryCache& cache;
const VideoCore::QueryType type;
const std::pair<VkQueryPool, u32> query;
- const u64 ticks;
+ const u64 tick;
};
class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 774ba1f26..e0fb8693f 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -9,14 +9,14 @@
#include <vector>
#include <boost/container/static_vector.hpp>
-#include <boost/functional/hash.hpp>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "core/core.h"
-#include "core/memory.h"
+#include "core/settings.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
@@ -31,7 +31,6 @@
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_sampler_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -39,6 +38,7 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/shader_cache.h"
namespace Vulkan {
@@ -64,20 +64,22 @@ VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::si
const auto& src = regs.viewport_transform[index];
const float width = src.scale_x * 2.0f;
const float height = src.scale_y * 2.0f;
+ const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
- VkViewport viewport;
- viewport.x = src.translate_x - src.scale_x;
- viewport.y = src.translate_y - src.scale_y;
- viewport.width = width != 0.0f ? width : 1.0f;
- viewport.height = height != 0.0f ? height : 1.0f;
+ VkViewport viewport{
+ .x = src.translate_x - src.scale_x,
+ .y = src.translate_y - src.scale_y,
+ .width = width != 0.0f ? width : 1.0f,
+ .height = height != 0.0f ? height : 1.0f,
+ .minDepth = src.translate_z - src.scale_z * reduce_z,
+ .maxDepth = src.translate_z + src.scale_z,
+ };
- const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
- viewport.minDepth = src.translate_z - src.scale_z * reduce_z;
- viewport.maxDepth = src.translate_z + src.scale_z;
if (!device.IsExtDepthRangeUnrestrictedSupported()) {
viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f);
viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f);
}
+
return viewport;
}
@@ -99,7 +101,7 @@ VkRect2D GetScissorState(const Maxwell& regs, std::size_t index) {
}
std::array<GPUVAddr, Maxwell::MaxShaderProgram> GetShaderAddresses(
- const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) {
+ const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) {
std::array<GPUVAddr, Maxwell::MaxShaderProgram> addresses;
for (std::size_t i = 0; i < std::size(addresses); ++i) {
addresses[i] = shaders[i] ? shaders[i]->GetGpuAddr() : 0;
@@ -118,14 +120,24 @@ template <typename Engine, typename Entry>
Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
std::size_t stage, std::size_t index = 0) {
const auto stage_type = static_cast<Tegra::Engines::ShaderType>(stage);
- if (entry.IsBindless()) {
- const Tegra::Texture::TextureHandle tex_handle =
- engine.AccessConstBuffer32(stage_type, entry.GetBuffer(), entry.GetOffset());
+ if constexpr (std::is_same_v<Entry, SamplerEntry>) {
+ if (entry.is_separated) {
+ const u32 buffer_1 = entry.buffer;
+ const u32 buffer_2 = entry.secondary_buffer;
+ const u32 offset_1 = entry.offset;
+ const u32 offset_2 = entry.secondary_offset;
+ const u32 handle_1 = engine.AccessConstBuffer32(stage_type, buffer_1, offset_1);
+ const u32 handle_2 = engine.AccessConstBuffer32(stage_type, buffer_2, offset_2);
+ return engine.GetTextureInfo(handle_1 | handle_2);
+ }
+ }
+ if (entry.is_bindless) {
+ const auto tex_handle = engine.AccessConstBuffer32(stage_type, entry.buffer, entry.offset);
return engine.GetTextureInfo(tex_handle);
}
const auto& gpu_profile = engine.AccessGuestDriverProfile();
const u32 entry_offset = static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
- const u32 offset = entry.GetOffset() + entry_offset;
+ const u32 offset = entry.offset + entry_offset;
if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
return engine.GetStageTexture(stage_type, offset);
} else {
@@ -133,92 +145,144 @@ Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry
}
}
+/// @brief Determine if an attachment to be updated has to preserve contents
+/// @param is_clear True when a clear is being executed
+/// @param regs 3D registers
+/// @return True when the contents have to be preserved
+bool HasToPreserveColorContents(bool is_clear, const Maxwell& regs) {
+ if (!is_clear) {
+ return true;
+ }
+ // First we have to make sure all clear masks are enabled.
+ if (!regs.clear_buffers.R || !regs.clear_buffers.G || !regs.clear_buffers.B ||
+ !regs.clear_buffers.A) {
+ return true;
+ }
+ // If scissors are disabled, the whole screen is cleared
+ if (!regs.clear_flags.scissor) {
+ return false;
+ }
+ // Then we have to confirm scissor testing clears the whole image
+ const std::size_t index = regs.clear_buffers.RT;
+ const auto& scissor = regs.scissor_test[0];
+ return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.rt[index].width ||
+ scissor.max_y < regs.rt[index].height;
+}
+
+/// @brief Determine if an attachment to be updated has to preserve contents
+/// @param is_clear True when a clear is being executed
+/// @param regs 3D registers
+/// @return True when the contents have to be preserved
+bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) {
+ // If we are not clearing, the contents have to be preserved
+ if (!is_clear) {
+ return true;
+ }
+ // For depth stencil clears we only have to confirm scissor test covers the whole image
+ if (!regs.clear_flags.scissor) {
+ return false;
+ }
+ // Make sure the clear cover the whole image
+ const auto& scissor = regs.scissor_test[0];
+ return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.zeta_width ||
+ scissor.max_y < regs.zeta_height;
+}
+
+template <std::size_t N>
+std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) {
+ std::array<VkDeviceSize, N> expanded;
+ std::copy(strides.begin(), strides.end(), expanded.begin());
+ return expanded;
+}
+
} // Anonymous namespace
class BufferBindings final {
public:
- void AddVertexBinding(const VkBuffer* buffer, VkDeviceSize offset) {
- vertex.buffer_ptrs[vertex.num_buffers] = buffer;
+ void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) {
+ vertex.buffers[vertex.num_buffers] = buffer;
vertex.offsets[vertex.num_buffers] = offset;
+ vertex.sizes[vertex.num_buffers] = size;
+ vertex.strides[vertex.num_buffers] = static_cast<u16>(stride);
++vertex.num_buffers;
}
- void SetIndexBinding(const VkBuffer* buffer, VkDeviceSize offset, VkIndexType type) {
+ void SetIndexBinding(VkBuffer buffer, VkDeviceSize offset, VkIndexType type) {
index.buffer = buffer;
index.offset = offset;
index.type = type;
}
- void Bind(VKScheduler& scheduler) const {
+ void Bind(const VKDevice& device, VKScheduler& scheduler) const {
// Use this large switch case to avoid dispatching more memory in the record lambda than
// what we need. It looks horrible, but it's the best we can do on standard C++.
switch (vertex.num_buffers) {
case 0:
- return BindStatic<0>(scheduler);
+ return BindStatic<0>(device, scheduler);
case 1:
- return BindStatic<1>(scheduler);
+ return BindStatic<1>(device, scheduler);
case 2:
- return BindStatic<2>(scheduler);
+ return BindStatic<2>(device, scheduler);
case 3:
- return BindStatic<3>(scheduler);
+ return BindStatic<3>(device, scheduler);
case 4:
- return BindStatic<4>(scheduler);
+ return BindStatic<4>(device, scheduler);
case 5:
- return BindStatic<5>(scheduler);
+ return BindStatic<5>(device, scheduler);
case 6:
- return BindStatic<6>(scheduler);
+ return BindStatic<6>(device, scheduler);
case 7:
- return BindStatic<7>(scheduler);
+ return BindStatic<7>(device, scheduler);
case 8:
- return BindStatic<8>(scheduler);
+ return BindStatic<8>(device, scheduler);
case 9:
- return BindStatic<9>(scheduler);
+ return BindStatic<9>(device, scheduler);
case 10:
- return BindStatic<10>(scheduler);
+ return BindStatic<10>(device, scheduler);
case 11:
- return BindStatic<11>(scheduler);
+ return BindStatic<11>(device, scheduler);
case 12:
- return BindStatic<12>(scheduler);
+ return BindStatic<12>(device, scheduler);
case 13:
- return BindStatic<13>(scheduler);
+ return BindStatic<13>(device, scheduler);
case 14:
- return BindStatic<14>(scheduler);
+ return BindStatic<14>(device, scheduler);
case 15:
- return BindStatic<15>(scheduler);
+ return BindStatic<15>(device, scheduler);
case 16:
- return BindStatic<16>(scheduler);
+ return BindStatic<16>(device, scheduler);
case 17:
- return BindStatic<17>(scheduler);
+ return BindStatic<17>(device, scheduler);
case 18:
- return BindStatic<18>(scheduler);
+ return BindStatic<18>(device, scheduler);
case 19:
- return BindStatic<19>(scheduler);
+ return BindStatic<19>(device, scheduler);
case 20:
- return BindStatic<20>(scheduler);
+ return BindStatic<20>(device, scheduler);
case 21:
- return BindStatic<21>(scheduler);
+ return BindStatic<21>(device, scheduler);
case 22:
- return BindStatic<22>(scheduler);
+ return BindStatic<22>(device, scheduler);
case 23:
- return BindStatic<23>(scheduler);
+ return BindStatic<23>(device, scheduler);
case 24:
- return BindStatic<24>(scheduler);
+ return BindStatic<24>(device, scheduler);
case 25:
- return BindStatic<25>(scheduler);
+ return BindStatic<25>(device, scheduler);
case 26:
- return BindStatic<26>(scheduler);
+ return BindStatic<26>(device, scheduler);
case 27:
- return BindStatic<27>(scheduler);
+ return BindStatic<27>(device, scheduler);
case 28:
- return BindStatic<28>(scheduler);
+ return BindStatic<28>(device, scheduler);
case 29:
- return BindStatic<29>(scheduler);
+ return BindStatic<29>(device, scheduler);
case 30:
- return BindStatic<30>(scheduler);
+ return BindStatic<30>(device, scheduler);
case 31:
- return BindStatic<31>(scheduler);
+ return BindStatic<31>(device, scheduler);
case 32:
- return BindStatic<32>(scheduler);
+ return BindStatic<32>(device, scheduler);
}
UNREACHABLE();
}
@@ -227,26 +291,36 @@ private:
// Some of these fields are intentionally left uninitialized to avoid initializing them twice.
struct {
std::size_t num_buffers = 0;
- std::array<const VkBuffer*, Maxwell::NumVertexArrays> buffer_ptrs;
+ std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
+ std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes;
+ std::array<u16, Maxwell::NumVertexArrays> strides;
} vertex;
struct {
- const VkBuffer* buffer = nullptr;
+ VkBuffer buffer = nullptr;
VkDeviceSize offset;
VkIndexType type;
} index;
template <std::size_t N>
- void BindStatic(VKScheduler& scheduler) const {
- if (index.buffer != nullptr) {
- BindStatic<N, true>(scheduler);
+ void BindStatic(const VKDevice& device, VKScheduler& scheduler) const {
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ if (index.buffer) {
+ BindStatic<N, true, true>(scheduler);
+ } else {
+ BindStatic<N, false, true>(scheduler);
+ }
} else {
- BindStatic<N, false>(scheduler);
+ if (index.buffer) {
+ BindStatic<N, true, false>(scheduler);
+ } else {
+ BindStatic<N, false, false>(scheduler);
+ }
}
}
- template <std::size_t N, bool is_indexed>
+ template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state>
void BindStatic(VKScheduler& scheduler) const {
static_assert(N <= Maxwell::NumVertexArrays);
if constexpr (N == 0) {
@@ -254,18 +328,39 @@ private:
}
std::array<VkBuffer, N> buffers;
- std::transform(vertex.buffer_ptrs.begin(), vertex.buffer_ptrs.begin() + N, buffers.begin(),
- [](const auto ptr) { return *ptr; });
-
std::array<VkDeviceSize, N> offsets;
+ std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
+ if constexpr (has_extended_dynamic_state) {
+ // With extended dynamic states we can specify the length and stride of a vertex buffer
+ std::array<VkDeviceSize, N> sizes;
+ std::array<u16, N> strides;
+ std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin());
+ std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin());
+
+ if constexpr (is_indexed) {
+ scheduler.Record(
+ [buffers, offsets, sizes, strides, index = index](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
+ cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
+ offsets.data(), sizes.data(),
+ ExpandStrides(strides).data());
+ });
+ } else {
+ scheduler.Record([buffers, offsets, sizes, strides](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
+ offsets.data(), sizes.data(),
+ ExpandStrides(strides).data());
+ });
+ }
+ return;
+ }
+
if constexpr (is_indexed) {
// Indexed draw
- scheduler.Record([buffers, offsets, index_buffer = *index.buffer,
- index_offset = index.offset,
- index_type = index.type](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindIndexBuffer(index_buffer, index_offset, index_type);
+ scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
cmdbuf.BindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data());
});
} else {
@@ -285,25 +380,32 @@ void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const {
}
}
-RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer,
- VKScreenInfo& screen_info, const VKDevice& device,
- VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, StateTracker& state_tracker,
- VKScheduler& scheduler)
- : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer},
- screen_info{screen_info}, device{device}, resource_manager{resource_manager},
- memory_manager{memory_manager}, state_tracker{state_tracker}, scheduler{scheduler},
- staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
- update_descriptor_queue(device, scheduler), renderpass_cache(device),
+RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
+ Tegra::MemoryManager& gpu_memory_,
+ Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info_,
+ const VKDevice& device_, VKMemoryManager& memory_manager_,
+ StateTracker& state_tracker_, VKScheduler& scheduler_)
+ : RasterizerAccelerated(cpu_memory), gpu(gpu_), gpu_memory(gpu_memory_),
+ maxwell3d(gpu.Maxwell3D()), kepler_compute(gpu.KeplerCompute()), screen_info(screen_info_),
+ device(device_), memory_manager(memory_manager_), state_tracker(state_tracker_),
+ scheduler(scheduler_), staging_pool(device, memory_manager, scheduler),
+ descriptor_pool(device, scheduler_), update_descriptor_queue(device, scheduler),
+ renderpass_cache(device),
quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
+ quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- texture_cache(system, *this, device, resource_manager, memory_manager, scheduler,
- staging_pool),
- pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue,
- renderpass_cache),
- buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
- sampler_cache(device), query_cache(system, *this, device, scheduler) {
+ texture_cache(*this, maxwell3d, gpu_memory, device, memory_manager, scheduler, staging_pool),
+ pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
+ descriptor_pool, update_descriptor_queue, renderpass_cache),
+ buffer_cache(*this, gpu_memory, cpu_memory, device, memory_manager, scheduler, staging_pool),
+ sampler_cache(device), query_cache(*this, maxwell3d, gpu_memory, device, scheduler),
+ fence_manager(*this, gpu, gpu_memory, texture_cache, buffer_cache, query_cache, device,
+ scheduler),
+ wfi_event(device.GetLogical().CreateEvent()), async_shaders(emu_window) {
scheduler.SetQueryCache(query_cache);
+ if (device.UseAsynchronousShaders()) {
+ async_shaders.AllocateWorkers();
+ }
}
RasterizerVulkan::~RasterizerVulkan() = default;
@@ -311,12 +413,13 @@ RasterizerVulkan::~RasterizerVulkan() = default;
void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(Vulkan_Drawing);
+ SCOPE_EXIT({ gpu.TickWork(); });
FlushWork();
query_cache.UpdateCounters();
- const auto& gpu = system.GPU().Maxwell3D();
- GraphicsPipelineCacheKey key{GetFixedPipelineState(gpu.regs)};
+ GraphicsPipelineCacheKey key;
+ key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported());
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
@@ -334,31 +437,32 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
buffer_cache.Unmap();
- const auto texceptions = UpdateAttachments();
+ const Texceptions texceptions = UpdateAttachments(false);
SetupImageTransitions(texceptions, color_attachments, zeta_attachment);
key.renderpass_params = GetRenderPassParams(texceptions);
+ key.padding = 0;
+
+ auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders);
+ if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {
+ // Async graphics pipeline was not ready.
+ return;
+ }
- auto& pipeline = pipeline_cache.GetGraphicsPipeline(key);
- scheduler.BindGraphicsPipeline(pipeline.GetHandle());
+ scheduler.BindGraphicsPipeline(pipeline->GetHandle());
- const auto renderpass = pipeline.GetRenderPass();
+ const auto renderpass = pipeline->GetRenderPass();
const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
scheduler.RequestRenderpass(renderpass, framebuffer, render_area);
UpdateDynamicStates();
- buffer_bindings.Bind(scheduler);
-
- if (device.IsNvDeviceDiagnosticCheckpoints()) {
- scheduler.Record(
- [&pipeline](vk::CommandBuffer cmdbuf) { cmdbuf.SetCheckpointNV(&pipeline); });
- }
+ buffer_bindings.Bind(device, scheduler);
BeginTransformFeedback();
- const auto pipeline_layout = pipeline.GetLayout();
- const auto descriptor_set = pipeline.CommitDescriptorSet();
+ const auto pipeline_layout = pipeline->GetLayout();
+ const auto descriptor_set = pipeline->CommitDescriptorSet();
scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) {
if (descriptor_set) {
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
@@ -373,8 +477,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
void RasterizerVulkan::Clear() {
MICROPROFILE_SCOPE(Vulkan_Clearing);
- const auto& gpu = system.GPU().Maxwell3D();
- if (!system.GPU().Maxwell3D().ShouldExecute()) {
+ if (!maxwell3d.ShouldExecute()) {
return;
}
@@ -383,7 +486,7 @@ void RasterizerVulkan::Clear() {
query_cache.UpdateCounters();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A;
const bool use_depth = regs.clear_buffers.Z;
@@ -392,7 +495,7 @@ void RasterizerVulkan::Clear() {
return;
}
- [[maybe_unused]] const auto texceptions = UpdateAttachments();
+ [[maybe_unused]] const auto texceptions = UpdateAttachments(true);
DEBUG_ASSERT(texceptions.none());
SetupImageTransitions(0, color_attachments, zeta_attachment);
@@ -413,10 +516,11 @@ void RasterizerVulkan::Clear() {
const u32 color_attachment = regs.clear_buffers.RT;
scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) {
- VkClearAttachment attachment;
- attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- attachment.colorAttachment = color_attachment;
- attachment.clearValue = clear_value;
+ const VkClearAttachment attachment{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .colorAttachment = color_attachment,
+ .clearValue = clear_value,
+ };
cmdbuf.ClearAttachments(attachment, clear_rect);
});
}
@@ -434,10 +538,6 @@ void RasterizerVulkan::Clear() {
scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
- VkClearValue clear_value;
- clear_value.depthStencil.depth = clear_depth;
- clear_value.depthStencil.stencil = clear_stencil;
-
VkClearAttachment attachment;
attachment.aspectMask = aspect_flags;
attachment.colorAttachment = 0;
@@ -455,12 +555,17 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
query_cache.UpdateCounters();
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
- const ComputePipelineCacheKey key{
- code_addr,
- launch_desc.shared_alloc,
- {launch_desc.block_dim_x, launch_desc.block_dim_y, launch_desc.block_dim_z}};
- auto& pipeline = pipeline_cache.GetComputePipeline(key);
+ const auto& launch_desc = kepler_compute.launch_description;
+ auto& pipeline = pipeline_cache.GetComputePipeline({
+ .shader = code_addr,
+ .shared_memory_size = launch_desc.shared_alloc,
+ .workgroup_size =
+ {
+ launch_desc.block_dim_x,
+ launch_desc.block_dim_y,
+ launch_desc.block_dim_z,
+ },
+ });
// Compute dispatches can't be executed inside a renderpass
scheduler.RequestOutsideRenderPassOperationContext();
@@ -470,8 +575,9 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
const auto& entries = pipeline.GetEntries();
SetupComputeConstBuffers(entries);
SetupComputeGlobalBuffers(entries);
- SetupComputeTexelBuffers(entries);
+ SetupComputeUniformTexels(entries);
SetupComputeTextures(entries);
+ SetupComputeStorageTexels(entries);
SetupComputeImages(entries);
buffer_cache.Unmap();
@@ -481,11 +587,6 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
TransitionImages(image_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
- if (device.IsNvDeviceDiagnosticCheckpoints()) {
- scheduler.Record(
- [&pipeline](vk::CommandBuffer cmdbuf) { cmdbuf.SetCheckpointNV(nullptr); });
- }
-
scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y,
grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(),
layout = pipeline.GetLayout(),
@@ -517,6 +618,13 @@ void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) {
query_cache.FlushRegion(addr, size);
}
+bool RasterizerVulkan::MustFlushRegion(VAddr addr, u64 size) {
+ if (!Settings::IsGPULevelHigh()) {
+ return buffer_cache.MustFlushRegion(addr, size);
+ }
+ return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size);
+}
+
void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
@@ -527,11 +635,71 @@ void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size) {
query_cache.InvalidateRegion(addr, size);
}
+void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
+ if (addr == 0 || size == 0) {
+ return;
+ }
+ texture_cache.OnCPUWrite(addr, size);
+ pipeline_cache.OnCPUWrite(addr, size);
+ buffer_cache.OnCPUWrite(addr, size);
+}
+
+void RasterizerVulkan::SyncGuestHost() {
+ texture_cache.SyncGuestHost();
+ buffer_cache.SyncGuestHost();
+ pipeline_cache.SyncGuestHost();
+}
+
+void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
+ if (!gpu.IsAsync()) {
+ gpu_memory.Write<u32>(addr, value);
+ return;
+ }
+ fence_manager.SignalSemaphore(addr, value);
+}
+
+void RasterizerVulkan::SignalSyncPoint(u32 value) {
+ if (!gpu.IsAsync()) {
+ gpu.IncrementSyncPoint(value);
+ return;
+ }
+ fence_manager.SignalSyncPoint(value);
+}
+
+void RasterizerVulkan::ReleaseFences() {
+ if (!gpu.IsAsync()) {
+ return;
+ }
+ fence_manager.WaitPendingFences();
+}
+
void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size) {
- FlushRegion(addr, size);
+ if (Settings::IsGPULevelExtreme()) {
+ FlushRegion(addr, size);
+ }
InvalidateRegion(addr, size);
}
+void RasterizerVulkan::WaitForIdle() {
+ // Everything but wait pixel operations. This intentionally includes FRAGMENT_SHADER_BIT because
+ // fragment shaders can still write storage buffers.
+ VkPipelineStageFlags flags =
+ VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
+ VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
+ VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT |
+ VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
+ if (device.IsExtTransformFeedbackSupported()) {
+ flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
+ }
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetEvent(event, flags);
+ cmdbuf.WaitEvents(event, flags, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, {}, {}, {});
+ });
+}
+
void RasterizerVulkan::FlushCommands() {
if (draw_counter > 0) {
draw_counter = 0;
@@ -576,10 +744,6 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
return true;
}
-void RasterizerVulkan::SetupDirtyFlags() {
- state_tracker.Initialize();
-}
-
void RasterizerVulkan::FlushWork() {
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
@@ -601,9 +765,11 @@ void RasterizerVulkan::FlushWork() {
draw_counter = 0;
}
-RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
+RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments(bool is_clear) {
MICROPROFILE_SCOPE(Vulkan_RenderTargets);
- auto& dirty = system.GPU().Maxwell3D().dirty.flags;
+
+ const auto& regs = maxwell3d.regs;
+ auto& dirty = maxwell3d.dirty.flags;
const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets];
dirty[VideoCommon::Dirty::RenderTargets] = false;
@@ -612,7 +778,8 @@ RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
Texceptions texceptions;
for (std::size_t rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
if (update_rendertargets) {
- color_attachments[rt] = texture_cache.GetColorBufferSurface(rt);
+ const bool preserve_contents = HasToPreserveColorContents(is_clear, regs);
+ color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, preserve_contents);
}
if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) {
texceptions[rt] = true;
@@ -620,7 +787,8 @@ RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
}
if (update_rendertargets) {
- zeta_attachment = texture_cache.GetDepthBufferSurface();
+ const bool preserve_contents = HasToPreserveDepthContents(is_clear, regs);
+ zeta_attachment = texture_cache.GetDepthBufferSurface(preserve_contents);
}
if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) {
texceptions[ZETA_TEXCEPTION_INDEX] = true;
@@ -645,21 +813,28 @@ bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachmen
std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
VkRenderPass renderpass) {
- FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(),
- std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()};
+ FramebufferCacheKey key{
+ .renderpass = renderpass,
+ .width = std::numeric_limits<u32>::max(),
+ .height = std::numeric_limits<u32>::max(),
+ .layers = std::numeric_limits<u32>::max(),
+ .views = {},
+ };
- const auto try_push = [&](const View& view) {
+ const auto try_push = [&key](const View& view) {
if (!view) {
return false;
}
- key.views.push_back(view->GetHandle());
+ key.views.push_back(view->GetAttachment());
key.width = std::min(key.width, view->GetWidth());
key.height = std::min(key.height, view->GetHeight());
key.layers = std::min(key.layers, view->GetNumLayers());
return true;
};
- for (std::size_t index = 0; index < std::size(color_attachments); ++index) {
+ const auto& regs = maxwell3d.regs;
+ const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
+ for (std::size_t index = 0; index < num_attachments; ++index) {
if (try_push(color_attachments[index])) {
texture_cache.MarkColorBufferInUse(index);
}
@@ -671,17 +846,17 @@ std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key);
auto& framebuffer = fbentry->second;
if (is_cache_miss) {
- VkFramebufferCreateInfo framebuffer_ci;
- framebuffer_ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
- framebuffer_ci.pNext = nullptr;
- framebuffer_ci.flags = 0;
- framebuffer_ci.renderPass = key.renderpass;
- framebuffer_ci.attachmentCount = static_cast<u32>(key.views.size());
- framebuffer_ci.pAttachments = key.views.data();
- framebuffer_ci.width = key.width;
- framebuffer_ci.height = key.height;
- framebuffer_ci.layers = key.layers;
- framebuffer = device.GetLogical().CreateFramebuffer(framebuffer_ci);
+ framebuffer = device.GetLogical().CreateFramebuffer({
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .renderPass = key.renderpass,
+ .attachmentCount = static_cast<u32>(key.views.size()),
+ .pAttachments = key.views.data(),
+ .width = key.width,
+ .height = key.height,
+ .layers = key.layers,
+ });
}
return {*framebuffer, VkExtent2D{key.width, key.height}};
@@ -693,13 +868,12 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
bool is_instanced) {
MICROPROFILE_SCOPE(Vulkan_Geometry);
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
- SetupVertexArrays(fixed_state.vertex_input, buffer_bindings);
+ SetupVertexArrays(buffer_bindings);
const u32 base_instance = regs.vb_base_instance;
- const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
+ const u32 num_instances = is_instanced ? maxwell3d.mme_draw.instance_count : 1;
const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first;
const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count;
@@ -710,20 +884,21 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
}
void RasterizerVulkan::SetupShaderDescriptors(
- const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) {
+ const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) {
texture_cache.GuardSamplers(true);
for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
// Skip VertexA stage
- const auto& shader = shaders[stage + 1];
+ Shader* const shader = shaders[stage + 1];
if (!shader) {
continue;
}
const auto& entries = shader->GetEntries();
SetupGraphicsConstBuffers(entries, stage);
SetupGraphicsGlobalBuffers(entries, stage);
- SetupGraphicsTexelBuffers(entries, stage);
+ SetupGraphicsUniformTexels(entries, stage);
SetupGraphicsTextures(entries, stage);
+ SetupGraphicsStorageTexels(entries, stage);
SetupGraphicsImages(entries, stage);
}
texture_cache.GuardSamplers(false);
@@ -759,20 +934,34 @@ void RasterizerVulkan::SetupImageTransitions(
}
void RasterizerVulkan::UpdateDynamicStates() {
- auto& regs = system.GPU().Maxwell3D().regs;
+ auto& regs = maxwell3d.regs;
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
UpdateBlendConstants(regs);
UpdateDepthBounds(regs);
UpdateStencilFaces(regs);
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ UpdateCullMode(regs);
+ UpdateDepthBoundsTestEnable(regs);
+ UpdateDepthTestEnable(regs);
+ UpdateDepthWriteEnable(regs);
+ UpdateDepthCompareOp(regs);
+ UpdateFrontFace(regs);
+ UpdateStencilOp(regs);
+ UpdateStencilTestEnable(regs);
+ }
}
void RasterizerVulkan::BeginTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
+ if (!device.IsExtTransformFeedbackSupported()) {
+ LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
+ return;
+ }
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) ||
@@ -787,90 +976,92 @@ void RasterizerVulkan::BeginTransformFeedback() {
UNIMPLEMENTED_IF(binding.buffer_offset != 0);
const GPUVAddr gpu_addr = binding.Address();
- const std::size_t size = binding.buffer_size;
- const auto [buffer, offset] = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
+ const VkDeviceSize size = static_cast<VkDeviceSize>(binding.buffer_size);
+ const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
- scheduler.Record([buffer = *buffer, offset = offset, size](vk::CommandBuffer cmdbuf) {
+ scheduler.Record([buffer = info.handle, offset = info.offset, size](vk::CommandBuffer cmdbuf) {
cmdbuf.BindTransformFeedbackBuffersEXT(0, 1, &buffer, &offset, &size);
cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
});
}
void RasterizerVulkan::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
+ if (!device.IsExtTransformFeedbackSupported()) {
+ return;
+ }
scheduler.Record(
[](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
-void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
- BufferBindings& buffer_bindings) {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexAttributes); ++index) {
- const auto& attrib = regs.vertex_attrib_format[index];
- if (!attrib.IsValid()) {
- continue;
- }
+void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
+ const auto& regs = maxwell3d.regs;
- const auto& buffer = regs.vertex_array[attrib.buffer];
- ASSERT(buffer.IsEnabled());
-
- vertex_input.attributes[vertex_input.num_attributes++] =
- FixedPipelineState::VertexAttribute(index, attrib.buffer, attrib.type, attrib.size,
- attrib.offset);
- }
-
- for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexArrays); ++index) {
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
const auto& vertex_array = regs.vertex_array[index];
if (!vertex_array.IsEnabled()) {
continue;
}
-
const GPUVAddr start{vertex_array.StartAddress()};
const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
- ASSERT(end > start);
- const std::size_t size{end - start + 1};
- const auto [buffer, offset] = buffer_cache.UploadMemory(start, size);
-
- vertex_input.bindings[vertex_input.num_bindings++] = FixedPipelineState::VertexBinding(
- index, vertex_array.stride,
- regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0);
- buffer_bindings.AddVertexBinding(buffer, offset);
+ ASSERT(end >= start);
+ const std::size_t size = end - start;
+ if (size == 0) {
+ buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0);
+ continue;
+ }
+ const auto info = buffer_cache.UploadMemory(start, size);
+ buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride);
}
}
void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params,
bool is_indexed) {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ if (params.num_vertices == 0) {
+ return;
+ }
+ const auto& regs = maxwell3d.regs;
switch (regs.draw.topology) {
- case Maxwell::PrimitiveTopology::Quads:
- if (params.is_indexed) {
- UNIMPLEMENTED();
- } else {
+ case Maxwell::PrimitiveTopology::Quads: {
+ if (!params.is_indexed) {
const auto [buffer, offset] =
quad_array_pass.Assemble(params.num_vertices, params.base_vertex);
buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32);
params.base_vertex = 0;
params.num_vertices = params.num_vertices * 6 / 4;
params.is_indexed = true;
+ break;
}
+ const GPUVAddr gpu_addr = regs.index_array.IndexStart();
+ const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
+ VkBuffer buffer = info.handle;
+ u64 offset = info.offset;
+ std::tie(buffer, offset) = quad_indexed_pass.Assemble(
+ regs.index_array.format, params.num_vertices, params.base_vertex, buffer, offset);
+
+ buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32);
+ params.num_vertices = (params.num_vertices / 4) * 6;
+ params.base_vertex = 0;
break;
+ }
default: {
if (!is_indexed) {
break;
}
const GPUVAddr gpu_addr = regs.index_array.IndexStart();
- auto [buffer, offset] = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
+ const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
+ VkBuffer buffer = info.handle;
+ u64 offset = info.offset;
auto format = regs.index_array.format;
const bool is_uint8 = format == Maxwell::IndexFormat::UnsignedByte;
if (is_uint8 && !device.IsExtIndexTypeUint8Supported()) {
- std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, *buffer, offset);
+ std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, buffer, offset);
format = Maxwell::IndexFormat::UnsignedShort;
}
@@ -882,8 +1073,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar
void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& shader_stage = gpu.state.shader_stages[stage];
+ const auto& shader_stage = maxwell3d.state.shader_stages[stage];
for (const auto& entry : entries.const_buffers) {
SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]);
}
@@ -891,8 +1081,7 @@ void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, s
void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- auto& gpu{system.GPU()};
- const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]};
+ const auto& cbufs{maxwell3d.state.shader_stages[stage]};
for (const auto& entry : entries.global_buffers) {
const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset();
@@ -900,38 +1089,43 @@ void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries,
}
}
-void RasterizerVulkan::SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage) {
+void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
- for (const auto& entry : entries.texel_buffers) {
- const auto image = GetTextureInfo(gpu, entry, stage).tic;
- SetupTexelBuffer(image, entry);
+ for (const auto& entry : entries.uniform_texels) {
+ const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
+ SetupUniformTexels(image, entry);
}
}
void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.samplers) {
- for (std::size_t i = 0; i < entry.Size(); ++i) {
- const auto texture = GetTextureInfo(gpu, entry, stage, i);
+ for (std::size_t i = 0; i < entry.size; ++i) {
+ const auto texture = GetTextureInfo(maxwell3d, entry, stage, i);
SetupTexture(texture, entry);
}
}
}
+void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ for (const auto& entry : entries.storage_texels) {
+ const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
+ SetupStorageTexel(image, entry);
+ }
+}
+
void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, stage).tic;
+ const auto tic = GetTextureInfo(maxwell3d, entry, stage).tic;
SetupImage(tic, entry);
}
}
void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
for (const auto& entry : entries.const_buffers) {
const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
@@ -945,38 +1139,43 @@ void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config};
+ const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
for (const auto& entry : entries.global_buffers) {
const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
SetupGlobalBuffer(entry, addr);
}
}
-void RasterizerVulkan::SetupComputeTexelBuffers(const ShaderEntries& entries) {
+void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
- for (const auto& entry : entries.texel_buffers) {
- const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
- SetupTexelBuffer(image, entry);
+ for (const auto& entry : entries.uniform_texels) {
+ const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
+ SetupUniformTexels(image, entry);
}
}
void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.samplers) {
- for (std::size_t i = 0; i < entry.Size(); ++i) {
- const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i);
+ for (std::size_t i = 0; i < entry.size; ++i) {
+ const auto texture = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex, i);
SetupTexture(texture, entry);
}
}
}
+void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ for (const auto& entry : entries.storage_texels) {
+ const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
+ SetupStorageTexel(image, entry);
+ }
+}
+
void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ const auto tic = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
SetupImage(tic, entry);
}
}
@@ -985,8 +1184,7 @@ void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
const Tegra::Engines::ConstBufferInfo& buffer) {
if (!buffer.enabled) {
// Set values to zero to unbind buffers
- update_descriptor_queue.AddBuffer(buffer_cache.GetEmptyBuffer(sizeof(float)), 0,
- sizeof(float));
+ update_descriptor_queue.AddBuffer(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE);
return;
}
@@ -995,33 +1193,33 @@ void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
Common::AlignUp(CalculateConstBufferSize(entry, buffer), 4 * sizeof(float));
ASSERT(size <= MaxConstbufferSize);
- const auto [buffer_handle, offset] =
+ const auto info =
buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment());
-
- update_descriptor_queue.AddBuffer(buffer_handle, offset, size);
+ update_descriptor_queue.AddBuffer(info.handle, info.offset, size);
}
void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto actual_addr = memory_manager.Read<u64>(address);
- const auto size = memory_manager.Read<u32>(address + 8);
+ const u64 actual_addr = gpu_memory.Read<u64>(address);
+ const u32 size = gpu_memory.Read<u32>(address + 8);
if (size == 0) {
- // Sometimes global memory pointers don't have a proper size. Upload a dummy entry because
- // Vulkan doesn't like empty buffers.
- constexpr std::size_t dummy_size = 4;
- const auto buffer = buffer_cache.GetEmptyBuffer(dummy_size);
- update_descriptor_queue.AddBuffer(buffer, 0, dummy_size);
+ // Sometimes global memory pointers don't have a proper size. Upload a dummy entry
+ // because Vulkan doesn't like empty buffers.
+ // Note: Do *not* use DefaultBuffer() here, storage buffers can be written breaking the
+ // default buffer.
+ static constexpr std::size_t dummy_size = 4;
+ const auto info = buffer_cache.GetEmptyBuffer(dummy_size);
+ update_descriptor_queue.AddBuffer(info.handle, info.offset, dummy_size);
return;
}
- const auto [buffer, offset] = buffer_cache.UploadMemory(
+ const auto info = buffer_cache.UploadMemory(
actual_addr, size, device.GetStorageBufferAlignment(), entry.IsWritten());
- update_descriptor_queue.AddBuffer(buffer, offset, size);
+ update_descriptor_queue.AddBuffer(info.handle, info.offset, size);
}
-void RasterizerVulkan::SetupTexelBuffer(const Tegra::Texture::TICEntry& tic,
- const TexelBufferEntry& entry) {
+void RasterizerVulkan::SetupUniformTexels(const Tegra::Texture::TICEntry& tic,
+ const UniformTexelEntry& entry) {
const auto view = texture_cache.GetTextureSurface(tic, entry);
ASSERT(view->IsBufferView());
@@ -1033,29 +1231,38 @@ void RasterizerVulkan::SetupTexture(const Tegra::Texture::FullTextureInfo& textu
auto view = texture_cache.GetTextureSurface(texture.tic, entry);
ASSERT(!view->IsBufferView());
- const auto image_view = view->GetHandle(texture.tic.x_source, texture.tic.y_source,
- texture.tic.z_source, texture.tic.w_source);
+ const VkImageView image_view = view->GetImageView(texture.tic.x_source, texture.tic.y_source,
+ texture.tic.z_source, texture.tic.w_source);
const auto sampler = sampler_cache.GetSampler(texture.tsc);
update_descriptor_queue.AddSampledImage(sampler, image_view);
- const auto image_layout = update_descriptor_queue.GetLastImageLayout();
+ VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout();
*image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
sampled_views.push_back(ImageView{std::move(view), image_layout});
}
+void RasterizerVulkan::SetupStorageTexel(const Tegra::Texture::TICEntry& tic,
+ const StorageTexelEntry& entry) {
+ const auto view = texture_cache.GetImageSurface(tic, entry);
+ ASSERT(view->IsBufferView());
+
+ update_descriptor_queue.AddTexelBuffer(view->GetBufferView());
+}
+
void RasterizerVulkan::SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry) {
auto view = texture_cache.GetImageSurface(tic, entry);
- if (entry.IsWritten()) {
+ if (entry.is_written) {
view->MarkAsModified(texture_cache.Tick());
}
UNIMPLEMENTED_IF(tic.IsBuffer());
- const auto image_view = view->GetHandle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
+ const VkImageView image_view =
+ view->GetImageView(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
update_descriptor_queue.AddImage(image_view);
- const auto image_layout = update_descriptor_queue.GetLastImageLayout();
+ VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout();
*image_layout = VK_IMAGE_LAYOUT_GENERAL;
image_views.push_back(ImageView{std::move(view), image_layout});
}
@@ -1150,6 +1357,107 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
}
}
+void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchCullMode()) {
+ return;
+ }
+ scheduler.Record(
+ [enabled = regs.cull_test_enabled, cull_face = regs.cull_face](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetCullModeEXT(enabled ? MaxwellToVK::CullFace(cull_face) : VK_CULL_MODE_NONE);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthBoundsTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_bounds_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthBoundsTestEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthTestEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthWriteEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthWriteEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthCompareOp()) {
+ return;
+ }
+ scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
+ });
+}
+
+void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchFrontFace()) {
+ return;
+ }
+
+ VkFrontFace front_face = MaxwellToVK::FrontFace(regs.front_face);
+ if (regs.screen_y_control.triangle_rast_flip != 0) {
+ front_face = front_face == VK_FRONT_FACE_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE
+ : VK_FRONT_FACE_CLOCKWISE;
+ }
+ scheduler.Record(
+ [front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); });
+}
+
+void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchStencilOp()) {
+ return;
+ }
+ const Maxwell::StencilOp fail = regs.stencil_front_op_fail;
+ const Maxwell::StencilOp zfail = regs.stencil_front_op_zfail;
+ const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass;
+ const Maxwell::ComparisonOp compare = regs.stencil_front_func_func;
+ if (regs.stencil_two_side_enable) {
+ scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
+ MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
+ MaxwellToVK::ComparisonOp(compare));
+ });
+ } else {
+ const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail;
+ const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail;
+ const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass;
+ const Maxwell::ComparisonOp back_compare = regs.stencil_back_func_func;
+ scheduler.Record([fail, zfail, zpass, compare, back_fail, back_zfail, back_zpass,
+ back_compare](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_BIT, MaxwellToVK::StencilOp(fail),
+ MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
+ MaxwellToVK::ComparisonOp(compare));
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_BACK_BIT, MaxwellToVK::StencilOp(back_fail),
+ MaxwellToVK::StencilOp(back_zpass),
+ MaxwellToVK::StencilOp(back_zfail),
+ MaxwellToVK::ComparisonOp(back_compare));
+ });
+ }
+}
+
+void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchStencilTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.stencil_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilTestEnableEXT(enable);
+ });
+}
+
std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
std::size_t size = CalculateVertexArraysSize();
if (is_indexed) {
@@ -1165,7 +1473,7 @@ std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const {
}
std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
std::size_t size = 0;
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
@@ -1174,15 +1482,14 @@ std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
DEBUG_ASSERT(end >= start);
- size += (end - start + 1) * regs.vertex_array[index].enable;
+ size += (end - start) * regs.vertex_array[index].enable;
}
return size;
}
std::size_t RasterizerVulkan::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
+ return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
+ static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
}
std::size_t RasterizerVulkan::CalculateConstBufferSize(
@@ -1197,28 +1504,54 @@ std::size_t RasterizerVulkan::CalculateConstBufferSize(
}
RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const {
- using namespace VideoCore::Surface;
+ const auto& regs = maxwell3d.regs;
+ const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
- const auto& regs = system.GPU().Maxwell3D().regs;
- RenderPassParams renderpass_params;
+ RenderPassParams params;
+ params.color_formats = {};
+ std::size_t color_texceptions = 0;
- for (std::size_t rt = 0; rt < static_cast<std::size_t>(regs.rt_control.count); ++rt) {
+ std::size_t index = 0;
+ for (std::size_t rt = 0; rt < num_attachments; ++rt) {
const auto& rendertarget = regs.rt[rt];
if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) {
continue;
}
- renderpass_params.color_attachments.push_back(RenderPassParams::ColorAttachment{
- static_cast<u32>(rt), PixelFormatFromRenderTargetFormat(rendertarget.format),
- texceptions[rt]});
+ params.color_formats[index] = static_cast<u8>(rendertarget.format);
+ color_texceptions |= (texceptions[rt] ? 1ULL : 0ULL) << index;
+ ++index;
}
+ params.num_color_attachments = static_cast<u8>(index);
+ params.texceptions = static_cast<u8>(color_texceptions);
- renderpass_params.has_zeta = regs.zeta_enable;
- if (renderpass_params.has_zeta) {
- renderpass_params.zeta_pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
- renderpass_params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX];
- }
+ params.zeta_format = regs.zeta_enable ? static_cast<u8>(regs.zeta.format) : 0;
+ params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX];
+ return params;
+}
+
+VkBuffer RasterizerVulkan::DefaultBuffer() {
+ if (default_buffer) {
+ return *default_buffer;
+ }
+
+ default_buffer = device.GetLogical().CreateBuffer({
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = DEFAULT_BUFFER_SIZE,
+ .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
+ default_buffer_commit = memory_manager.Commit(default_buffer, false);
- return renderpass_params;
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([buffer = *default_buffer](vk::CommandBuffer cmdbuf) {
+ cmdbuf.FillBuffer(buffer, 0, DEFAULT_BUFFER_SIZE, 0);
+ });
+ return *default_buffer;
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 46037860a..237e51fa4 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -14,24 +14,24 @@
#include <boost/functional/hash.hpp>
#include "common/common_types.h"
-#include "video_core/memory_manager.h"
#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/renderer_vulkan/vk_fence_manager.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_sampler_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/shader/async_shaders.h"
namespace Core {
class System;
@@ -105,10 +105,11 @@ struct ImageView {
class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
+ explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
VKScreenInfo& screen_info, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- StateTracker& state_tracker, VKScheduler& scheduler);
+ VKMemoryManager& memory_manager, StateTracker& state_tracker,
+ VKScheduler& scheduler);
~RasterizerVulkan() override;
void Draw(bool is_indexed, bool is_instanced) override;
@@ -118,8 +119,15 @@ public:
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
void FlushAll() override;
void FlushRegion(VAddr addr, u64 size) override;
+ bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
+ void OnCPUWrite(VAddr addr, u64 size) override;
+ void SyncGuestHost() override;
+ void SignalSemaphore(GPUVAddr addr, u32 value) override;
+ void SignalSyncPoint(u32 value) override;
+ void ReleaseFences() override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void WaitForIdle() override;
void FlushCommands() override;
void TickFrame() override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
@@ -127,7 +135,14 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void SetupDirtyFlags() override;
+
+ VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
+ return async_shaders;
+ }
+
+ const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const {
+ return async_shaders;
+ }
/// Maximum supported size that a constbuffer can have in bytes.
static constexpr std::size_t MaxConstbufferSize = 0x10000;
@@ -148,10 +163,14 @@ private:
using Texceptions = std::bitset<Maxwell::NumRenderTargets + 1>;
static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8;
+ static constexpr VkDeviceSize DEFAULT_BUFFER_SIZE = 4 * sizeof(float);
void FlushWork();
- Texceptions UpdateAttachments();
+ /// @brief Updates the currently bound attachments
+ /// @param is_clear True when the framebuffer is updated as a clear
+ /// @return Bitfield of attachments being used as sampled textures
+ Texceptions UpdateAttachments(bool is_clear);
std::tuple<VkFramebuffer, VkExtent2D> ConfigureFramebuffers(VkRenderPass renderpass);
@@ -160,7 +179,7 @@ private:
bool is_indexed, bool is_instanced);
/// Setup descriptors in the graphics pipeline.
- void SetupShaderDescriptors(const std::array<Shader, Maxwell::MaxShaderProgram>& shaders);
+ void SetupShaderDescriptors(const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders);
void SetupImageTransitions(Texceptions texceptions,
const std::array<View, Maxwell::NumRenderTargets>& color_attachments,
@@ -174,8 +193,7 @@ private:
bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
- void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
- BufferBindings& buffer_bindings);
+ void SetupVertexArrays(BufferBindings& buffer_bindings);
void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
@@ -185,12 +203,15 @@ private:
/// Setup global buffers in the graphics pipeline.
void SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage);
- /// Setup texel buffers in the graphics pipeline.
- void SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage);
+ /// Setup uniform texels in the graphics pipeline.
+ void SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage);
/// Setup textures in the graphics pipeline.
void SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage);
+ /// Setup storage texels in the graphics pipeline.
+ void SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage);
+
/// Setup images in the graphics pipeline.
void SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage);
@@ -201,11 +222,14 @@ private:
void SetupComputeGlobalBuffers(const ShaderEntries& entries);
/// Setup texel buffers in the compute pipeline.
- void SetupComputeTexelBuffers(const ShaderEntries& entries);
+ void SetupComputeUniformTexels(const ShaderEntries& entries);
/// Setup textures in the compute pipeline.
void SetupComputeTextures(const ShaderEntries& entries);
+ /// Setup storage texels in the compute pipeline.
+ void SetupComputeStorageTexels(const ShaderEntries& entries);
+
/// Setup images in the compute pipeline.
void SetupComputeImages(const ShaderEntries& entries);
@@ -214,10 +238,12 @@ private:
void SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address);
- void SetupTexelBuffer(const Tegra::Texture::TICEntry& image, const TexelBufferEntry& entry);
+ void SetupUniformTexels(const Tegra::Texture::TICEntry& image, const UniformTexelEntry& entry);
void SetupTexture(const Tegra::Texture::FullTextureInfo& texture, const SamplerEntry& entry);
+ void SetupStorageTexel(const Tegra::Texture::TICEntry& tic, const StorageTexelEntry& entry);
+
void SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry);
void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -227,6 +253,15 @@ private:
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+
std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
std::size_t CalculateComputeStreamBufferSize() const;
@@ -240,11 +275,15 @@ private:
RenderPassParams GetRenderPassParams(Texceptions texceptions) const;
- Core::System& system;
- Core::Frontend::EmuWindow& render_window;
+ VkBuffer DefaultBuffer();
+
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+
VKScreenInfo& screen_info;
const VKDevice& device;
- VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
StateTracker& state_tracker;
VKScheduler& scheduler;
@@ -254,6 +293,7 @@ private:
VKUpdateDescriptorQueue update_descriptor_queue;
VKRenderPassCache renderpass_cache;
QuadArrayPass quad_array_pass;
+ QuadIndexedPass quad_indexed_pass;
Uint8Pass uint8_pass;
VKTextureCache texture_cache;
@@ -261,6 +301,12 @@ private:
VKBufferCache buffer_cache;
VKSamplerCache sampler_cache;
VKQueryCache query_cache;
+ VKFenceManager fence_manager;
+
+ vk::Buffer default_buffer;
+ VKMemoryCommit default_buffer_commit;
+ vk::Event wfi_event;
+ VideoCommon::Shader::AsyncShaders async_shaders;
std::array<View, Maxwell::NumRenderTargets> color_attachments;
View zeta_attachment;
diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
index 4e5286a69..80284cf92 100644
--- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
@@ -2,9 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
#include <memory>
#include <vector>
+#include "common/cityhash.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_device.h"
@@ -13,6 +15,15 @@
namespace Vulkan {
+std::size_t RenderPassParams::Hash() const noexcept {
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
+ return static_cast<std::size_t>(hash);
+}
+
+bool RenderPassParams::operator==(const RenderPassParams& rhs) const noexcept {
+ return std::memcmp(&rhs, this, sizeof *this) == 0;
+}
+
VKRenderPassCache::VKRenderPassCache(const VKDevice& device) : device{device} {}
VKRenderPassCache::~VKRenderPassCache() = default;
@@ -27,72 +38,86 @@ VkRenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) {
}
vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& params) const {
+ using namespace VideoCore::Surface;
+ const std::size_t num_attachments = static_cast<std::size_t>(params.num_color_attachments);
+
std::vector<VkAttachmentDescription> descriptors;
+ descriptors.reserve(num_attachments);
+
std::vector<VkAttachmentReference> color_references;
+ color_references.reserve(num_attachments);
- for (std::size_t rt = 0; rt < params.color_attachments.size(); ++rt) {
- const auto attachment = params.color_attachments[rt];
- const auto format =
- MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, attachment.pixel_format);
+ for (std::size_t rt = 0; rt < num_attachments; ++rt) {
+ const auto guest_format = static_cast<Tegra::RenderTargetFormat>(params.color_formats[rt]);
+ const PixelFormat pixel_format = PixelFormatFromRenderTargetFormat(guest_format);
+ const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format);
ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}",
- static_cast<u32>(attachment.pixel_format));
-
- // TODO(Rodrigo): Add eMayAlias when it's needed.
- const auto color_layout = attachment.is_texception
- ? VK_IMAGE_LAYOUT_GENERAL
- : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- VkAttachmentDescription& descriptor = descriptors.emplace_back();
- descriptor.flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT;
- descriptor.format = format.format;
- descriptor.samples = VK_SAMPLE_COUNT_1_BIT;
- descriptor.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
- descriptor.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
- descriptor.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- descriptor.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- descriptor.initialLayout = color_layout;
- descriptor.finalLayout = color_layout;
-
- VkAttachmentReference& reference = color_references.emplace_back();
- reference.attachment = static_cast<u32>(rt);
- reference.layout = color_layout;
+ static_cast<int>(pixel_format));
+
+ // TODO(Rodrigo): Add MAY_ALIAS_BIT when it's needed.
+ const VkImageLayout color_layout = ((params.texceptions >> rt) & 1) != 0
+ ? VK_IMAGE_LAYOUT_GENERAL
+ : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ descriptors.push_back({
+ .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT,
+ .format = format.format,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = color_layout,
+ .finalLayout = color_layout,
+ });
+
+ color_references.push_back({
+ .attachment = static_cast<u32>(rt),
+ .layout = color_layout,
+ });
}
VkAttachmentReference zeta_attachment_ref;
- if (params.has_zeta) {
- const auto format =
- MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.zeta_pixel_format);
+ const bool has_zeta = params.zeta_format != 0;
+ if (has_zeta) {
+ const auto guest_format = static_cast<Tegra::DepthFormat>(params.zeta_format);
+ const PixelFormat pixel_format = PixelFormatFromDepthFormat(guest_format);
+ const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format);
ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}",
- static_cast<u32>(params.zeta_pixel_format));
-
- const auto zeta_layout = params.zeta_texception
- ? VK_IMAGE_LAYOUT_GENERAL
- : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- VkAttachmentDescription& descriptor = descriptors.emplace_back();
- descriptor.flags = 0;
- descriptor.format = format.format;
- descriptor.samples = VK_SAMPLE_COUNT_1_BIT;
- descriptor.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
- descriptor.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
- descriptor.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
- descriptor.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
- descriptor.initialLayout = zeta_layout;
- descriptor.finalLayout = zeta_layout;
-
- zeta_attachment_ref.attachment = static_cast<u32>(params.color_attachments.size());
- zeta_attachment_ref.layout = zeta_layout;
+ static_cast<int>(pixel_format));
+
+ const VkImageLayout zeta_layout = params.zeta_texception != 0
+ ? VK_IMAGE_LAYOUT_GENERAL
+ : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ descriptors.push_back({
+ .flags = 0,
+ .format = format.format,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .initialLayout = zeta_layout,
+ .finalLayout = zeta_layout,
+ });
+
+ zeta_attachment_ref = {
+ .attachment = static_cast<u32>(num_attachments),
+ .layout = zeta_layout,
+ };
}
- VkSubpassDescription subpass_description;
- subpass_description.flags = 0;
- subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass_description.inputAttachmentCount = 0;
- subpass_description.pInputAttachments = nullptr;
- subpass_description.colorAttachmentCount = static_cast<u32>(color_references.size());
- subpass_description.pColorAttachments = color_references.data();
- subpass_description.pResolveAttachments = nullptr;
- subpass_description.pDepthStencilAttachment = params.has_zeta ? &zeta_attachment_ref : nullptr;
- subpass_description.preserveAttachmentCount = 0;
- subpass_description.pPreserveAttachments = nullptr;
+ const VkSubpassDescription subpass_description{
+ .flags = 0,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .inputAttachmentCount = 0,
+ .pInputAttachments = nullptr,
+ .colorAttachmentCount = static_cast<u32>(color_references.size()),
+ .pColorAttachments = color_references.data(),
+ .pResolveAttachments = nullptr,
+ .pDepthStencilAttachment = has_zeta ? &zeta_attachment_ref : nullptr,
+ .preserveAttachmentCount = 0,
+ .pPreserveAttachments = nullptr,
+ };
VkAccessFlags access = 0;
VkPipelineStageFlags stage = 0;
@@ -101,32 +126,33 @@ vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& param
stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
}
- if (params.has_zeta) {
+ if (has_zeta) {
access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
stage |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
}
- VkSubpassDependency subpass_dependency;
- subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
- subpass_dependency.dstSubpass = 0;
- subpass_dependency.srcStageMask = stage;
- subpass_dependency.dstStageMask = stage;
- subpass_dependency.srcAccessMask = 0;
- subpass_dependency.dstAccessMask = access;
- subpass_dependency.dependencyFlags = 0;
-
- VkRenderPassCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.attachmentCount = static_cast<u32>(descriptors.size());
- ci.pAttachments = descriptors.data();
- ci.subpassCount = 1;
- ci.pSubpasses = &subpass_description;
- ci.dependencyCount = 1;
- ci.pDependencies = &subpass_dependency;
- return device.GetLogical().CreateRenderPass(ci);
+ const VkSubpassDependency subpass_dependency{
+ .srcSubpass = VK_SUBPASS_EXTERNAL,
+ .dstSubpass = 0,
+ .srcStageMask = stage,
+ .dstStageMask = stage,
+ .srcAccessMask = 0,
+ .dstAccessMask = access,
+ .dependencyFlags = 0,
+ };
+
+ return device.GetLogical().CreateRenderPass({
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .attachmentCount = static_cast<u32>(descriptors.size()),
+ .pAttachments = descriptors.data(),
+ .subpassCount = 1,
+ .pSubpasses = &subpass_description,
+ .dependencyCount = 1,
+ .pDependencies = &subpass_dependency,
+ });
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h
index 921b6efb5..8b0fec720 100644
--- a/src/video_core/renderer_vulkan/vk_renderpass_cache.h
+++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h
@@ -4,8 +4,7 @@
#pragma once
-#include <memory>
-#include <tuple>
+#include <type_traits>
#include <unordered_map>
#include <boost/container/static_vector.hpp>
@@ -19,51 +18,25 @@ namespace Vulkan {
class VKDevice;
-// TODO(Rodrigo): Optimize this structure for faster hashing
-
struct RenderPassParams {
- struct ColorAttachment {
- u32 index = 0;
- VideoCore::Surface::PixelFormat pixel_format = VideoCore::Surface::PixelFormat::Invalid;
- bool is_texception = false;
-
- std::size_t Hash() const noexcept {
- return static_cast<std::size_t>(pixel_format) |
- static_cast<std::size_t>(is_texception) << 6 |
- static_cast<std::size_t>(index) << 7;
- }
-
- bool operator==(const ColorAttachment& rhs) const noexcept {
- return std::tie(index, pixel_format, is_texception) ==
- std::tie(rhs.index, rhs.pixel_format, rhs.is_texception);
- }
- };
-
- boost::container::static_vector<ColorAttachment,
- Tegra::Engines::Maxwell3D::Regs::NumRenderTargets>
- color_attachments{};
- // TODO(Rodrigo): Unify has_zeta into zeta_pixel_format and zeta_component_type.
- VideoCore::Surface::PixelFormat zeta_pixel_format = VideoCore::Surface::PixelFormat::Invalid;
- bool has_zeta = false;
- bool zeta_texception = false;
-
- std::size_t Hash() const noexcept {
- std::size_t hash = 0;
- for (const auto& rt : color_attachments) {
- boost::hash_combine(hash, rt.Hash());
- }
- boost::hash_combine(hash, zeta_pixel_format);
- boost::hash_combine(hash, has_zeta);
- boost::hash_combine(hash, zeta_texception);
- return hash;
- }
+ std::array<u8, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> color_formats;
+ u8 num_color_attachments;
+ u8 texceptions;
+
+ u8 zeta_format;
+ u8 zeta_texception;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const RenderPassParams& rhs) const noexcept;
- bool operator==(const RenderPassParams& rhs) const {
- return std::tie(color_attachments, zeta_pixel_format, has_zeta, zeta_texception) ==
- std::tie(rhs.color_attachments, rhs.zeta_pixel_format, rhs.has_zeta,
- rhs.zeta_texception);
+ bool operator!=(const RenderPassParams& rhs) const noexcept {
+ return !operator==(rhs);
}
};
+static_assert(std::has_unique_object_representations_v<RenderPassParams>);
+static_assert(std::is_trivially_copyable_v<RenderPassParams>);
+static_assert(std::is_trivially_constructible_v<RenderPassParams>);
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
deleted file mode 100644
index dc06f545a..000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <optional>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-namespace {
-
-// TODO(Rodrigo): Fine tune these numbers.
-constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
-constexpr std::size_t FENCES_GROW_STEP = 0x40;
-
-VkFenceCreateInfo BuildFenceCreateInfo() {
- VkFenceCreateInfo fence_ci;
- fence_ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
- fence_ci.pNext = nullptr;
- fence_ci.flags = 0;
- return fence_ci;
-}
-
-} // Anonymous namespace
-
-class CommandBufferPool final : public VKFencedPool {
-public:
- CommandBufferPool(const VKDevice& device)
- : VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
-
- void Allocate(std::size_t begin, std::size_t end) override {
- // Command buffers are going to be commited, recorded, executed every single usage cycle.
- // They are also going to be reseted when commited.
- VkCommandPoolCreateInfo command_pool_ci;
- command_pool_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
- command_pool_ci.pNext = nullptr;
- command_pool_ci.flags =
- VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
- command_pool_ci.queueFamilyIndex = device.GetGraphicsFamily();
-
- Pool& pool = pools.emplace_back();
- pool.handle = device.GetLogical().CreateCommandPool(command_pool_ci);
- pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
- }
-
- VkCommandBuffer Commit(VKFence& fence) {
- const std::size_t index = CommitResource(fence);
- const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
- const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
- return pools[pool_index].cmdbufs[sub_index];
- }
-
-private:
- struct Pool {
- vk::CommandPool handle;
- vk::CommandBuffers cmdbufs;
- };
-
- const VKDevice& device;
- std::vector<Pool> pools;
-};
-
-VKResource::VKResource() = default;
-
-VKResource::~VKResource() = default;
-
-VKFence::VKFence(const VKDevice& device)
- : device{device}, handle{device.GetLogical().CreateFence(BuildFenceCreateInfo())} {}
-
-VKFence::~VKFence() = default;
-
-void VKFence::Wait() {
- switch (const VkResult result = handle.Wait()) {
- case VK_SUCCESS:
- return;
- case VK_ERROR_DEVICE_LOST:
- device.ReportLoss();
- [[fallthrough]];
- default:
- throw vk::Exception(result);
- }
-}
-
-void VKFence::Release() {
- ASSERT(is_owned);
- is_owned = false;
-}
-
-void VKFence::Commit() {
- is_owned = true;
- is_used = true;
-}
-
-bool VKFence::Tick(bool gpu_wait, bool owner_wait) {
- if (!is_used) {
- // If a fence is not used it's always free.
- return true;
- }
- if (is_owned && !owner_wait) {
- // The fence is still being owned (Release has not been called) and ownership wait has
- // not been asked.
- return false;
- }
-
- if (gpu_wait) {
- // Wait for the fence if it has been requested.
- (void)handle.Wait();
- } else {
- if (handle.GetStatus() != VK_SUCCESS) {
- // Vulkan fence is not ready, not much it can do here
- return false;
- }
- }
-
- // Broadcast resources their free state.
- for (auto* resource : protected_resources) {
- resource->OnFenceRemoval(this);
- }
- protected_resources.clear();
-
- // Prepare fence for reusage.
- handle.Reset();
- is_used = false;
- return true;
-}
-
-void VKFence::Protect(VKResource* resource) {
- protected_resources.push_back(resource);
-}
-
-void VKFence::Unprotect(VKResource* resource) {
- const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource);
- ASSERT(it != protected_resources.end());
-
- resource->OnFenceRemoval(this);
- protected_resources.erase(it);
-}
-
-void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept {
- std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource,
- new_resource);
-}
-
-VKFenceWatch::VKFenceWatch() = default;
-
-VKFenceWatch::VKFenceWatch(VKFence& initial_fence) {
- Watch(initial_fence);
-}
-
-VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept {
- fence = std::exchange(rhs.fence, nullptr);
- if (fence) {
- fence->RedirectProtection(&rhs, this);
- }
-}
-
-VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept {
- fence = std::exchange(rhs.fence, nullptr);
- if (fence) {
- fence->RedirectProtection(&rhs, this);
- }
- return *this;
-}
-
-VKFenceWatch::~VKFenceWatch() {
- if (fence) {
- fence->Unprotect(this);
- }
-}
-
-void VKFenceWatch::Wait() {
- if (fence == nullptr) {
- return;
- }
- fence->Wait();
- fence->Unprotect(this);
-}
-
-void VKFenceWatch::Watch(VKFence& new_fence) {
- Wait();
- fence = &new_fence;
- fence->Protect(this);
-}
-
-bool VKFenceWatch::TryWatch(VKFence& new_fence) {
- if (fence) {
- return false;
- }
- fence = &new_fence;
- fence->Protect(this);
- return true;
-}
-
-void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) {
- ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence");
- fence = nullptr;
-}
-
-VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {}
-
-VKFencedPool::~VKFencedPool() = default;
-
-std::size_t VKFencedPool::CommitResource(VKFence& fence) {
- const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> {
- for (std::size_t iterator = begin; iterator < end; ++iterator) {
- if (watches[iterator]->TryWatch(fence)) {
- // The resource is now being watched, a free resource was successfully found.
- return iterator;
- }
- }
- return {};
- };
- // Try to find a free resource from the hinted position to the end.
- auto found = Search(free_iterator, watches.size());
- if (!found) {
- // Search from beginning to the hinted position.
- found = Search(0, free_iterator);
- if (!found) {
- // Both searches failed, the pool is full; handle it.
- const std::size_t free_resource = ManageOverflow();
-
- // Watch will wait for the resource to be free.
- watches[free_resource]->Watch(fence);
- found = free_resource;
- }
- }
- // Free iterator is hinted to the resource after the one that's been commited.
- free_iterator = (*found + 1) % watches.size();
- return *found;
-}
-
-std::size_t VKFencedPool::ManageOverflow() {
- const std::size_t old_capacity = watches.size();
- Grow();
-
- // The last entry is guaranted to be free, since it's the first element of the freshly
- // allocated resources.
- return old_capacity;
-}
-
-void VKFencedPool::Grow() {
- const std::size_t old_capacity = watches.size();
- watches.resize(old_capacity + grow_step);
- std::generate(watches.begin() + old_capacity, watches.end(),
- []() { return std::make_unique<VKFenceWatch>(); });
- Allocate(old_capacity, old_capacity + grow_step);
-}
-
-VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} {
- GrowFences(FENCES_GROW_STEP);
- command_buffer_pool = std::make_unique<CommandBufferPool>(device);
-}
-
-VKResourceManager::~VKResourceManager() = default;
-
-VKFence& VKResourceManager::CommitFence() {
- const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* {
- const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); };
- const auto hinted = fences.begin() + fences_iterator;
-
- auto it = std::find_if(hinted, fences.end(), Tick);
- if (it == fences.end()) {
- it = std::find_if(fences.begin(), hinted, Tick);
- if (it == hinted) {
- return nullptr;
- }
- }
- fences_iterator = std::distance(fences.begin(), it) + 1;
- if (fences_iterator >= fences.size())
- fences_iterator = 0;
-
- auto& fence = *it;
- fence->Commit();
- return fence.get();
- };
-
- VKFence* found_fence = StepFences(false, false);
- if (!found_fence) {
- // Try again, this time waiting.
- found_fence = StepFences(true, false);
-
- if (!found_fence) {
- // Allocate new fences and try again.
- LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(),
- fences.size() + FENCES_GROW_STEP);
-
- GrowFences(FENCES_GROW_STEP);
- found_fence = StepFences(true, false);
- ASSERT(found_fence != nullptr);
- }
- }
- return *found_fence;
-}
-
-VkCommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) {
- return command_buffer_pool->Commit(fence);
-}
-
-void VKResourceManager::GrowFences(std::size_t new_fences_count) {
- const std::size_t previous_size = fences.size();
- fences.resize(previous_size + new_fences_count);
-
- std::generate(fences.begin() + previous_size, fences.end(),
- [this] { return std::make_unique<VKFence>(device); });
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
deleted file mode 100644
index f683d2276..000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.h
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <memory>
-#include <vector>
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-class VKDevice;
-class VKFence;
-class VKResourceManager;
-
-class CommandBufferPool;
-
-/// Interface for a Vulkan resource
-class VKResource {
-public:
- explicit VKResource();
- virtual ~VKResource();
-
- /**
- * Signals the object that an owning fence has been signaled.
- * @param signaling_fence Fence that signals its usage end.
- */
- virtual void OnFenceRemoval(VKFence* signaling_fence) = 0;
-};
-
-/**
- * Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access.
- * They must be commited from the resource manager. Their usage flow is: commit the fence from the
- * resource manager, protect resources with it and use them, send the fence to an execution queue
- * and Wait for it if needed and then call Release. Used resources will automatically be signaled
- * when they are free to be reused.
- * @brief Protects resources for concurrent usage and signals its release.
- */
-class VKFence {
- friend class VKResourceManager;
-
-public:
- explicit VKFence(const VKDevice& device);
- ~VKFence();
-
- /**
- * Waits for the fence to be signaled.
- * @warning You must have ownership of the fence and it has to be previously sent to a queue to
- * call this function.
- */
- void Wait();
-
- /**
- * Releases ownership of the fence. Pass after it has been sent to an execution queue.
- * Unmanaged usage of the fence after the call will result in undefined behavior because it may
- * be being used for something else.
- */
- void Release();
-
- /// Protects a resource with this fence.
- void Protect(VKResource* resource);
-
- /// Removes protection for a resource.
- void Unprotect(VKResource* resource);
-
- /// Redirects one protected resource to a new address.
- void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept;
-
- /// Retreives the fence.
- operator VkFence() const {
- return *handle;
- }
-
-private:
- /// Take ownership of the fence.
- void Commit();
-
- /**
- * Updates the fence status.
- * @warning Waiting for the owner might soft lock the execution.
- * @param gpu_wait Wait for the fence to be signaled by the driver.
- * @param owner_wait Wait for the owner to signal its freedom.
- * @returns True if the fence is free. Waiting for gpu and owner will always return true.
- */
- bool Tick(bool gpu_wait, bool owner_wait);
-
- const VKDevice& device; ///< Device handler
- vk::Fence handle; ///< Vulkan fence
- std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence
- bool is_owned = false; ///< The fence has been commited but not released yet.
- bool is_used = false; ///< The fence has been commited but it has not been checked to be free.
-};
-
-/**
- * A fence watch is used to keep track of the usage of a fence and protect a resource or set of
- * resources without having to inherit VKResource from their handlers.
- */
-class VKFenceWatch final : public VKResource {
-public:
- explicit VKFenceWatch();
- VKFenceWatch(VKFence& initial_fence);
- VKFenceWatch(VKFenceWatch&&) noexcept;
- VKFenceWatch(const VKFenceWatch&) = delete;
- ~VKFenceWatch() override;
-
- VKFenceWatch& operator=(VKFenceWatch&&) noexcept;
-
- /// Waits for the fence to be released.
- void Wait();
-
- /**
- * Waits for a previous fence and watches a new one.
- * @param new_fence New fence to wait to.
- */
- void Watch(VKFence& new_fence);
-
- /**
- * Checks if it's currently being watched and starts watching it if it's available.
- * @returns True if a watch has started, false if it's being watched.
- */
- bool TryWatch(VKFence& new_fence);
-
- void OnFenceRemoval(VKFence* signaling_fence) override;
-
- /**
- * Do not use it paired with Watch. Use TryWatch instead.
- * Returns true when the watch is free.
- */
- bool IsUsed() const {
- return fence != nullptr;
- }
-
-private:
- VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free.
-};
-
-/**
- * Handles a pool of resources protected by fences. Manages resource overflow allocating more
- * resources.
- */
-class VKFencedPool {
-public:
- explicit VKFencedPool(std::size_t grow_step);
- virtual ~VKFencedPool();
-
-protected:
- /**
- * Commits a free resource and protects it with a fence. It may allocate new resources.
- * @param fence Fence that protects the commited resource.
- * @returns Index of the resource commited.
- */
- std::size_t CommitResource(VKFence& fence);
-
- /// Called when a chunk of resources have to be allocated.
- virtual void Allocate(std::size_t begin, std::size_t end) = 0;
-
-private:
- /// Manages pool overflow allocating new resources.
- std::size_t ManageOverflow();
-
- /// Allocates a new page of resources.
- void Grow();
-
- std::size_t grow_step = 0; ///< Number of new resources created after an overflow
- std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
- std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources
-};
-
-/**
- * The resource manager handles all resources that can be protected with a fence avoiding
- * driver-side or GPU-side concurrent usage. Usage is documented in VKFence.
- */
-class VKResourceManager final {
-public:
- explicit VKResourceManager(const VKDevice& device);
- ~VKResourceManager();
-
- /// Commits a fence. It has to be sent to a queue and released.
- VKFence& CommitFence();
-
- /// Commits an unused command buffer and protects it with a fence.
- VkCommandBuffer CommitCommandBuffer(VKFence& fence);
-
-private:
- /// Allocates new fences.
- void GrowFences(std::size_t new_fences_count);
-
- const VKDevice& device; ///< Device handler.
- std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found.
- std::vector<std::unique_ptr<VKFence>> fences; ///< Pool of fences.
- std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers.
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
new file mode 100644
index 000000000..ee274ac59
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
@@ -0,0 +1,63 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <optional>
+
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+
+namespace Vulkan {
+
+ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_)
+ : master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
+
+ResourcePool::~ResourcePool() = default;
+
+size_t ResourcePool::CommitResource() {
+ // Refresh semaphore to query updated results
+ master_semaphore.Refresh();
+
+ const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> {
+ for (size_t iterator = begin; iterator < end; ++iterator) {
+ if (master_semaphore.IsFree(ticks[iterator])) {
+ ticks[iterator] = master_semaphore.CurrentTick();
+ return iterator;
+ }
+ }
+ return {};
+ };
+ // Try to find a free resource from the hinted position to the end.
+ auto found = search(free_iterator, ticks.size());
+ if (!found) {
+ // Search from beginning to the hinted position.
+ found = search(0, free_iterator);
+ if (!found) {
+ // Both searches failed, the pool is full; handle it.
+ const size_t free_resource = ManageOverflow();
+
+ ticks[free_resource] = master_semaphore.CurrentTick();
+ found = free_resource;
+ }
+ }
+ // Free iterator is hinted to the resource after the one that's been commited.
+ free_iterator = (*found + 1) % ticks.size();
+ return *found;
+}
+
+size_t ResourcePool::ManageOverflow() {
+ const size_t old_capacity = ticks.size();
+ Grow();
+
+ // The last entry is guaranted to be free, since it's the first element of the freshly
+ // allocated resources.
+ return old_capacity;
+}
+
+void ResourcePool::Grow() {
+ const size_t old_capacity = ticks.size();
+ ticks.resize(old_capacity + grow_step);
+ Allocate(old_capacity, old_capacity + grow_step);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h
new file mode 100644
index 000000000..a018c7ec2
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Vulkan {
+
+class MasterSemaphore;
+
+/**
+ * Handles a pool of resources protected by fences. Manages resource overflow allocating more
+ * resources.
+ */
+class ResourcePool {
+public:
+ explicit ResourcePool(MasterSemaphore& master_semaphore, size_t grow_step);
+ virtual ~ResourcePool();
+
+protected:
+ size_t CommitResource();
+
+ /// Called when a chunk of resources have to be allocated.
+ virtual void Allocate(size_t begin, size_t end) = 0;
+
+private:
+ /// Manages pool overflow allocating new resources.
+ size_t ManageOverflow();
+
+ /// Allocates a new page of resources.
+ void Grow();
+
+ MasterSemaphore& master_semaphore;
+ size_t grow_step = 0; ///< Number of new resources created after an overflow
+ size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
+ std::vector<u64> ticks; ///< Ticks for each resource
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
index 07bbcf520..b068888f9 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -2,16 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cstring>
-#include <optional>
#include <unordered_map>
-#include "common/assert.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_sampler_cache.h"
#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/textures/texture.h"
+using Tegra::Texture::TextureMipmapFilter;
+
namespace Vulkan {
namespace {
@@ -42,26 +41,39 @@ VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
VKSamplerCache::~VKSamplerCache() = default;
vk::Sampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
- VkSamplerCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter);
- ci.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter);
- ci.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter);
- ci.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter);
- ci.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter);
- ci.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter);
- ci.mipLodBias = tsc.GetLodBias();
- ci.anisotropyEnable = tsc.GetMaxAnisotropy() > 1.0f ? VK_TRUE : VK_FALSE;
- ci.maxAnisotropy = tsc.GetMaxAnisotropy();
- ci.compareEnable = tsc.depth_compare_enabled;
- ci.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func);
- ci.minLod = tsc.GetMinLod();
- ci.maxLod = tsc.GetMaxLod();
- ci.borderColor = ConvertBorderColor(tsc.GetBorderColor());
- ci.unnormalizedCoordinates = VK_FALSE;
- return device.GetLogical().CreateSampler(ci);
+ const bool arbitrary_borders = device.IsExtCustomBorderColorSupported();
+ const std::array color = tsc.GetBorderColor();
+
+ VkSamplerCustomBorderColorCreateInfoEXT border{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .customBorderColor = {},
+ .format = VK_FORMAT_UNDEFINED,
+ };
+ std::memcpy(&border.customBorderColor, color.data(), sizeof(color));
+
+ return device.GetLogical().CreateSampler({
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = arbitrary_borders ? &border : nullptr,
+ .flags = 0,
+ .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
+ .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
+ .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
+ .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
+ .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
+ .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
+ .mipLodBias = tsc.GetLodBias(),
+ .anisotropyEnable =
+ static_cast<VkBool32>(tsc.GetMaxAnisotropy() > 1.0f ? VK_TRUE : VK_FALSE),
+ .maxAnisotropy = tsc.GetMaxAnisotropy(),
+ .compareEnable = tsc.depth_compare_enabled,
+ .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
+ .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.GetMinLod(),
+ .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.GetMaxLod(),
+ .borderColor =
+ arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color),
+ .unnormalizedCoordinates = VK_FALSE,
+ });
}
VkSampler VKSamplerCache::ToSamplerType(const vk::Sampler& sampler) const {
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 900f551b3..1a483dc71 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -8,11 +8,12 @@
#include <thread>
#include <utility>
-#include "common/assert.h"
#include "common/microprofile.h"
+#include "common/thread.h"
+#include "video_core/renderer_vulkan/vk_command_pool.h"
#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/wrapper.h"
@@ -35,10 +36,10 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
last = nullptr;
}
-VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
- StateTracker& state_tracker)
- : device{device}, resource_manager{resource_manager}, state_tracker{state_tracker},
- next_fence{&resource_manager.CommitFence()} {
+VKScheduler::VKScheduler(const VKDevice& device_, StateTracker& state_tracker_)
+ : device{device_}, state_tracker{state_tracker_},
+ master_semaphore{std::make_unique<MasterSemaphore>(device)},
+ command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
AcquireNewChunk();
AllocateNewContext();
worker_thread = std::thread(&VKScheduler::WorkerThread, this);
@@ -50,20 +51,27 @@ VKScheduler::~VKScheduler() {
worker_thread.join();
}
-void VKScheduler::Flush(bool release_fence, VkSemaphore semaphore) {
+u64 VKScheduler::CurrentTick() const noexcept {
+ return master_semaphore->CurrentTick();
+}
+
+bool VKScheduler::IsFree(u64 tick) const noexcept {
+ return master_semaphore->IsFree(tick);
+}
+
+void VKScheduler::Wait(u64 tick) {
+ master_semaphore->Wait(tick);
+}
+
+void VKScheduler::Flush(VkSemaphore semaphore) {
SubmitExecution(semaphore);
- if (release_fence) {
- current_fence->Release();
- }
AllocateNewContext();
}
-void VKScheduler::Finish(bool release_fence, VkSemaphore semaphore) {
+void VKScheduler::Finish(VkSemaphore semaphore) {
+ const u64 presubmit_tick = CurrentTick();
SubmitExecution(semaphore);
- current_fence->Wait();
- if (release_fence) {
- current_fence->Release();
- }
+ Wait(presubmit_tick);
AllocateNewContext();
}
@@ -100,16 +108,19 @@ void VKScheduler::RequestRenderpass(VkRenderPass renderpass, VkFramebuffer frame
state.framebuffer = framebuffer;
state.render_area = render_area;
- VkRenderPassBeginInfo renderpass_bi;
- renderpass_bi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
- renderpass_bi.pNext = nullptr;
- renderpass_bi.renderPass = renderpass;
- renderpass_bi.framebuffer = framebuffer;
- renderpass_bi.renderArea.offset.x = 0;
- renderpass_bi.renderArea.offset.y = 0;
- renderpass_bi.renderArea.extent = render_area;
- renderpass_bi.clearValueCount = 0;
- renderpass_bi.pClearValues = nullptr;
+ const VkRenderPassBeginInfo renderpass_bi{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .pNext = nullptr,
+ .renderPass = renderpass,
+ .framebuffer = framebuffer,
+ .renderArea =
+ {
+ .offset = {.x = 0, .y = 0},
+ .extent = render_area,
+ },
+ .clearValueCount = 0,
+ .pClearValues = nullptr,
+ };
Record([renderpass_bi, end_renderpass](vk::CommandBuffer cmdbuf) {
if (end_renderpass) {
@@ -134,6 +145,7 @@ void VKScheduler::BindGraphicsPipeline(VkPipeline pipeline) {
}
void VKScheduler::WorkerThread() {
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
std::unique_lock lock{mutex};
do {
cv.wait(lock, [this] { return !chunk_queue.Empty() || quit; });
@@ -156,35 +168,58 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
current_cmdbuf.End();
- VkSubmitInfo submit_info;
- submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
- submit_info.pNext = nullptr;
- submit_info.waitSemaphoreCount = 0;
- submit_info.pWaitSemaphores = nullptr;
- submit_info.pWaitDstStageMask = nullptr;
- submit_info.commandBufferCount = 1;
- submit_info.pCommandBuffers = current_cmdbuf.address();
- submit_info.signalSemaphoreCount = semaphore ? 1 : 0;
- submit_info.pSignalSemaphores = &semaphore;
- device.GetGraphicsQueue().Submit(submit_info, *current_fence);
+ const VkSemaphore timeline_semaphore = master_semaphore->Handle();
+ const u32 num_signal_semaphores = semaphore ? 2U : 1U;
+
+ const u64 signal_value = master_semaphore->CurrentTick();
+ const u64 wait_value = signal_value - 1;
+ const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+
+ master_semaphore->NextTick();
+
+ const std::array signal_values{signal_value, u64(0)};
+ const std::array signal_semaphores{timeline_semaphore, semaphore};
+
+ const VkTimelineSemaphoreSubmitInfoKHR timeline_si{
+ .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR,
+ .pNext = nullptr,
+ .waitSemaphoreValueCount = 1,
+ .pWaitSemaphoreValues = &wait_value,
+ .signalSemaphoreValueCount = num_signal_semaphores,
+ .pSignalSemaphoreValues = signal_values.data(),
+ };
+ const VkSubmitInfo submit_info{
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .pNext = &timeline_si,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &timeline_semaphore,
+ .pWaitDstStageMask = &wait_stage_mask,
+ .commandBufferCount = 1,
+ .pCommandBuffers = current_cmdbuf.address(),
+ .signalSemaphoreCount = num_signal_semaphores,
+ .pSignalSemaphores = signal_semaphores.data(),
+ };
+ switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) {
+ case VK_SUCCESS:
+ break;
+ case VK_ERROR_DEVICE_LOST:
+ device.ReportLoss();
+ [[fallthrough]];
+ default:
+ vk::Check(result);
+ }
}
void VKScheduler::AllocateNewContext() {
- ++ticks;
-
- VkCommandBufferBeginInfo cmdbuf_bi;
- cmdbuf_bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
- cmdbuf_bi.pNext = nullptr;
- cmdbuf_bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
- cmdbuf_bi.pInheritanceInfo = nullptr;
-
std::unique_lock lock{mutex};
- current_fence = next_fence;
- next_fence = &resource_manager.CommitFence();
- current_cmdbuf = vk::CommandBuffer(resource_manager.CommitCommandBuffer(*current_fence),
- device.GetDispatchLoader());
- current_cmdbuf.Begin(cmdbuf_bi);
+ current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
+ current_cmdbuf.Begin({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = nullptr,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ .pInheritanceInfo = nullptr,
+ });
// Enable counters once again. These are disabled when a command buffer is finished.
if (query_cache) {
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 82a8adc69..7be8a19f0 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -7,7 +7,6 @@
#include <atomic>
#include <condition_variable>
#include <memory>
-#include <optional>
#include <stack>
#include <thread>
#include <utility>
@@ -17,42 +16,33 @@
namespace Vulkan {
+class CommandPool;
+class MasterSemaphore;
class StateTracker;
class VKDevice;
-class VKFence;
class VKQueryCache;
-class VKResourceManager;
-
-class VKFenceView {
-public:
- VKFenceView() = default;
- VKFenceView(VKFence* const& fence) : fence{fence} {}
-
- VKFence* operator->() const noexcept {
- return fence;
- }
-
- operator VKFence&() const noexcept {
- return *fence;
- }
-
-private:
- VKFence* const& fence;
-};
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
/// OpenGL-like operations on Vulkan command buffers.
class VKScheduler {
public:
- explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
- StateTracker& state_tracker);
+ explicit VKScheduler(const VKDevice& device, StateTracker& state_tracker);
~VKScheduler();
+ /// Returns the current command buffer tick.
+ [[nodiscard]] u64 CurrentTick() const noexcept;
+
+ /// Returns true when a tick has been triggered by the GPU.
+ [[nodiscard]] bool IsFree(u64 tick) const noexcept;
+
+ /// Waits for the given tick to trigger on the GPU.
+ void Wait(u64 tick);
+
/// Sends the current execution context to the GPU.
- void Flush(bool release_fence = true, VkSemaphore semaphore = nullptr);
+ void Flush(VkSemaphore semaphore = nullptr);
/// Sends the current execution context to the GPU and waits for it to complete.
- void Finish(bool release_fence = true, VkSemaphore semaphore = nullptr);
+ void Finish(VkSemaphore semaphore = nullptr);
/// Waits for the worker thread to finish executing everything. After this function returns it's
/// safe to touch worker resources.
@@ -87,14 +77,9 @@ public:
(void)chunk->Record(command);
}
- /// Gets a reference to the current fence.
- VKFenceView GetFence() const {
- return current_fence;
- }
-
- /// Returns the current command buffer tick.
- u64 Ticks() const {
- return ticks;
+ /// Returns the master timeline semaphore.
+ [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept {
+ return *master_semaphore;
}
private:
@@ -172,6 +157,13 @@ private:
std::array<u8, 0x8000> data{};
};
+ struct State {
+ VkRenderPass renderpass = nullptr;
+ VkFramebuffer framebuffer = nullptr;
+ VkExtent2D render_area = {0, 0};
+ VkPipeline graphics_pipeline = nullptr;
+ };
+
void WorkerThread();
void SubmitExecution(VkSemaphore semaphore);
@@ -187,30 +179,23 @@ private:
void AcquireNewChunk();
const VKDevice& device;
- VKResourceManager& resource_manager;
StateTracker& state_tracker;
+ std::unique_ptr<MasterSemaphore> master_semaphore;
+ std::unique_ptr<CommandPool> command_pool;
+
VKQueryCache* query_cache = nullptr;
vk::CommandBuffer current_cmdbuf;
- VKFence* current_fence = nullptr;
- VKFence* next_fence = nullptr;
-
- struct State {
- VkRenderPass renderpass = nullptr;
- VkFramebuffer framebuffer = nullptr;
- VkExtent2D render_area = {0, 0};
- VkPipeline graphics_pipeline = nullptr;
- } state;
std::unique_ptr<CommandChunk> chunk;
std::thread worker_thread;
+ State state;
Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
std::mutex mutex;
std::condition_variable cv;
- std::atomic<u64> ticks = 0;
bool quit = false;
};
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index aaa138f52..a20452b87 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -103,8 +103,8 @@ struct GenericVaryingDescription {
};
spv::Dim GetSamplerDim(const Sampler& sampler) {
- ASSERT(!sampler.IsBuffer());
- switch (sampler.GetType()) {
+ ASSERT(!sampler.is_buffer);
+ switch (sampler.type) {
case Tegra::Shader::TextureType::Texture1D:
return spv::Dim::Dim1D;
case Tegra::Shader::TextureType::Texture2D:
@@ -114,13 +114,13 @@ spv::Dim GetSamplerDim(const Sampler& sampler) {
case Tegra::Shader::TextureType::TextureCube:
return spv::Dim::Cube;
default:
- UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<u32>(sampler.GetType()));
+ UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<int>(sampler.type));
return spv::Dim::Dim2D;
}
}
std::pair<spv::Dim, bool> GetImageDim(const Image& image) {
- switch (image.GetType()) {
+ switch (image.type) {
case Tegra::Shader::ImageType::Texture1D:
return {spv::Dim::Dim1D, false};
case Tegra::Shader::ImageType::TextureBuffer:
@@ -134,7 +134,7 @@ std::pair<spv::Dim, bool> GetImageDim(const Image& image) {
case Tegra::Shader::ImageType::Texture3D:
return {spv::Dim::Dim3D, false};
default:
- UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<u32>(image.GetType()));
+ UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<int>(image.type));
return {spv::Dim::Dim2D, false};
}
}
@@ -272,12 +272,19 @@ bool IsPrecise(Operation operand) {
return false;
}
+u32 ShaderVersion(const VKDevice& device) {
+ if (device.InstanceApiVersion() < VK_API_VERSION_1_1) {
+ return 0x00010000;
+ }
+ return 0x00010300;
+}
+
class SPIRVDecompiler final : public Sirit::Module {
public:
explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderType stage,
const Registry& registry, const Specialization& specialization)
- : Module(0x00010300), device{device}, ir{ir}, stage{stage}, header{ir.GetHeader()},
- registry{registry}, specialization{specialization} {
+ : Module(ShaderVersion(device)), device{device}, ir{ir}, stage{stage},
+ header{ir.GetHeader()}, registry{registry}, specialization{specialization} {
if (stage != ShaderType::Compute) {
transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo());
}
@@ -293,6 +300,7 @@ public:
AddCapability(spv::Capability::DrawParameters);
AddCapability(spv::Capability::SubgroupBallotKHR);
AddCapability(spv::Capability::SubgroupVoteKHR);
+ AddExtension("SPV_KHR_16bit_storage");
AddExtension("SPV_KHR_shader_ballot");
AddExtension("SPV_KHR_subgroup_vote");
AddExtension("SPV_KHR_storage_buffer_storage_class");
@@ -400,8 +408,9 @@ private:
u32 binding = specialization.base_binding;
binding = DeclareConstantBuffers(binding);
binding = DeclareGlobalBuffers(binding);
- binding = DeclareTexelBuffers(binding);
+ binding = DeclareUniformTexels(binding);
binding = DeclareSamplers(binding);
+ binding = DeclareStorageTexels(binding);
binding = DeclareImages(binding);
const Id main = OpFunction(t_void, {}, TypeFunction(t_void));
@@ -515,6 +524,16 @@ private:
void DeclareCommon() {
thread_id =
DeclareInputBuiltIn(spv::BuiltIn::SubgroupLocalInvocationId, t_in_uint, "thread_id");
+ thread_masks[0] =
+ DeclareInputBuiltIn(spv::BuiltIn::SubgroupEqMask, t_in_uint4, "thread_eq_mask");
+ thread_masks[1] =
+ DeclareInputBuiltIn(spv::BuiltIn::SubgroupGeMask, t_in_uint4, "thread_ge_mask");
+ thread_masks[2] =
+ DeclareInputBuiltIn(spv::BuiltIn::SubgroupGtMask, t_in_uint4, "thread_gt_mask");
+ thread_masks[3] =
+ DeclareInputBuiltIn(spv::BuiltIn::SubgroupLeMask, t_in_uint4, "thread_le_mask");
+ thread_masks[4] =
+ DeclareInputBuiltIn(spv::BuiltIn::SubgroupLtMask, t_in_uint4, "thread_lt_mask");
}
void DeclareVertex() {
@@ -674,13 +693,19 @@ private:
}
t_smem_uint = TypePointer(spv::StorageClass::Workgroup, t_uint);
- const u32 smem_size = specialization.shared_memory_size;
+ u32 smem_size = specialization.shared_memory_size * 4;
if (smem_size == 0) {
// Avoid declaring an empty array.
return;
}
- const auto element_count = static_cast<u32>(Common::AlignUp(smem_size, 4) / 4);
- const Id type_array = TypeArray(t_uint, Constant(t_uint, element_count));
+ const u32 limit = device.GetMaxComputeSharedMemorySize();
+ if (smem_size > limit) {
+ LOG_ERROR(Render_Vulkan, "Shared memory size {} is clamped to host's limit {}",
+ smem_size, limit);
+ smem_size = limit;
+ }
+
+ const Id type_array = TypeArray(t_uint, Constant(t_uint, smem_size / 4));
const Id type_pointer = TypePointer(spv::StorageClass::Workgroup, type_array);
Name(type_pointer, "SharedMemory");
@@ -689,9 +714,9 @@ private:
}
void DeclareInternalFlags() {
- constexpr std::array names = {"zero", "sign", "carry", "overflow"};
+ static constexpr std::array names{"zero", "sign", "carry", "overflow"};
+
for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) {
- const auto flag_code = static_cast<InternalFlag>(flag);
const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
internal_flags[flag] = AddGlobalVariable(Name(id, names[flag]));
}
@@ -731,8 +756,10 @@ private:
if (!IsGenericAttribute(index)) {
continue;
}
-
const u32 location = GetGenericAttributeLocation(index);
+ if (!IsAttributeEnabled(location)) {
+ continue;
+ }
const auto type_descriptor = GetAttributeType(location);
Id type;
if (IsInputAttributeArray()) {
@@ -877,13 +904,13 @@ private:
return binding;
}
- u32 DeclareTexelBuffers(u32 binding) {
+ u32 DeclareUniformTexels(u32 binding) {
for (const auto& sampler : ir.GetSamplers()) {
- if (!sampler.IsBuffer()) {
+ if (!sampler.is_buffer) {
continue;
}
- ASSERT(!sampler.IsArray());
- ASSERT(!sampler.IsShadow());
+ ASSERT(!sampler.is_array);
+ ASSERT(!sampler.is_shadow);
constexpr auto dim = spv::Dim::Buffer;
constexpr int depth = 0;
@@ -894,23 +921,23 @@ private:
const Id image_type = TypeImage(t_float, dim, depth, arrayed, ms, sampled, format);
const Id pointer_type = TypePointer(spv::StorageClass::UniformConstant, image_type);
const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
- AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.GetIndex())));
+ AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.index)));
Decorate(id, spv::Decoration::Binding, binding++);
Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
- texel_buffers.emplace(sampler.GetIndex(), TexelBuffer{image_type, id});
+ uniform_texels.emplace(sampler.index, TexelBuffer{image_type, id});
}
return binding;
}
u32 DeclareSamplers(u32 binding) {
for (const auto& sampler : ir.GetSamplers()) {
- if (sampler.IsBuffer()) {
+ if (sampler.is_buffer) {
continue;
}
const auto dim = GetSamplerDim(sampler);
- const int depth = sampler.IsShadow() ? 1 : 0;
- const int arrayed = sampler.IsArray() ? 1 : 0;
+ const int depth = sampler.is_shadow ? 1 : 0;
+ const int arrayed = sampler.is_array ? 1 : 0;
constexpr bool ms = false;
constexpr int sampled = 1;
constexpr auto format = spv::ImageFormat::Unknown;
@@ -918,46 +945,63 @@ private:
const Id sampler_type = TypeSampledImage(image_type);
const Id sampler_pointer_type =
TypePointer(spv::StorageClass::UniformConstant, sampler_type);
- const Id type = sampler.IsIndexed()
- ? TypeArray(sampler_type, Constant(t_uint, sampler.Size()))
+ const Id type = sampler.is_indexed
+ ? TypeArray(sampler_type, Constant(t_uint, sampler.size))
: sampler_type;
const Id pointer_type = TypePointer(spv::StorageClass::UniformConstant, type);
const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
- AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.GetIndex())));
+ AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.index)));
Decorate(id, spv::Decoration::Binding, binding++);
Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
- sampled_images.emplace(sampler.GetIndex(), SampledImage{image_type, sampler_type,
- sampler_pointer_type, id});
+ sampled_images.emplace(
+ sampler.index, SampledImage{image_type, sampler_type, sampler_pointer_type, id});
}
return binding;
}
- u32 DeclareImages(u32 binding) {
+ u32 DeclareStorageTexels(u32 binding) {
for (const auto& image : ir.GetImages()) {
- const auto [dim, arrayed] = GetImageDim(image);
- constexpr int depth = 0;
- constexpr bool ms = false;
- constexpr int sampled = 2; // This won't be accessed with a sampler
- constexpr auto format = spv::ImageFormat::Unknown;
- const Id image_type = TypeImage(t_uint, dim, depth, arrayed, ms, sampled, format, {});
- const Id pointer_type = TypePointer(spv::StorageClass::UniformConstant, image_type);
- const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
- AddGlobalVariable(Name(id, fmt::format("image_{}", image.GetIndex())));
-
- Decorate(id, spv::Decoration::Binding, binding++);
- Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
- if (image.IsRead() && !image.IsWritten()) {
- Decorate(id, spv::Decoration::NonWritable);
- } else if (image.IsWritten() && !image.IsRead()) {
- Decorate(id, spv::Decoration::NonReadable);
+ if (image.type != Tegra::Shader::ImageType::TextureBuffer) {
+ continue;
}
+ DeclareImage(image, binding);
+ }
+ return binding;
+ }
- images.emplace(static_cast<u32>(image.GetIndex()), StorageImage{image_type, id});
+ u32 DeclareImages(u32 binding) {
+ for (const auto& image : ir.GetImages()) {
+ if (image.type == Tegra::Shader::ImageType::TextureBuffer) {
+ continue;
+ }
+ DeclareImage(image, binding);
}
return binding;
}
+ void DeclareImage(const Image& image, u32& binding) {
+ const auto [dim, arrayed] = GetImageDim(image);
+ constexpr int depth = 0;
+ constexpr bool ms = false;
+ constexpr int sampled = 2; // This won't be accessed with a sampler
+ const auto format = image.is_atomic ? spv::ImageFormat::R32ui : spv::ImageFormat::Unknown;
+ const Id image_type = TypeImage(t_uint, dim, depth, arrayed, ms, sampled, format, {});
+ const Id pointer_type = TypePointer(spv::StorageClass::UniformConstant, image_type);
+ const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
+ AddGlobalVariable(Name(id, fmt::format("image_{}", image.index)));
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ if (image.is_read && !image.is_written) {
+ Decorate(id, spv::Decoration::NonWritable);
+ } else if (image.is_written && !image.is_read) {
+ Decorate(id, spv::Decoration::NonReadable);
+ }
+
+ images.emplace(image.index, StorageImage{image_type, id});
+ }
+
bool IsRenderTargetEnabled(u32 rt) const {
for (u32 component = 0; component < 4; ++component) {
if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
@@ -976,6 +1020,10 @@ private:
return stage == ShaderType::TesselationControl;
}
+ bool IsAttributeEnabled(u32 location) const {
+ return stage != ShaderType::Vertex || specialization.enabled_attributes[location];
+ }
+
u32 GetNumInputVertices() const {
switch (stage) {
case ShaderType::Geometry:
@@ -1071,8 +1119,7 @@ private:
void VisitBasicBlock(const NodeBlock& bb) {
for (const auto& node : bb) {
- [[maybe_unused]] const Type type = Visit(node).type;
- ASSERT(type == Type::Void);
+ Visit(node);
}
}
@@ -1192,16 +1239,20 @@ private:
UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
return {v_float_zero, Type::Float};
default:
- if (IsGenericAttribute(attribute)) {
- const u32 location = GetGenericAttributeLocation(attribute);
- const auto type_descriptor = GetAttributeType(location);
- const Type type = type_descriptor.type;
- const Id attribute_id = input_attributes.at(attribute);
- const std::vector elements = {element};
- const Id pointer = ArrayPass(type_descriptor.scalar, attribute_id, elements);
- return {OpLoad(GetTypeDefinition(type), pointer), type};
+ if (!IsGenericAttribute(attribute)) {
+ break;
}
- break;
+ const u32 location = GetGenericAttributeLocation(attribute);
+ if (!IsAttributeEnabled(location)) {
+ // Disabled attributes (also known as constant attributes) always return zero.
+ return {v_float_zero, Type::Float};
+ }
+ const auto type_descriptor = GetAttributeType(location);
+ const Type type = type_descriptor.type;
+ const Id attribute_id = input_attributes.at(attribute);
+ const std::vector elements = {element};
+ const Id pointer = ArrayPass(type_descriptor.scalar, attribute_id, elements);
+ return {OpLoad(GetTypeDefinition(type), pointer), type};
}
UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
return {v_float_zero, Type::Float};
@@ -1237,7 +1288,7 @@ private:
} else {
UNREACHABLE_MSG("Unmanaged offset node type");
}
- pointer = OpAccessChain(t_cbuf_float, buffer_id, Constant(t_uint, 0), buffer_index,
+ pointer = OpAccessChain(t_cbuf_float, buffer_id, v_uint_zero, buffer_index,
buffer_element);
}
return {OpLoad(t_float, pointer), Type::Float};
@@ -1362,7 +1413,9 @@ private:
Expression target{};
if (const auto gpr = std::get_if<GprNode>(&*dest)) {
if (gpr->GetIndex() == Register::ZeroIndex) {
- // Writing to Register::ZeroIndex is a no op
+ // Writing to Register::ZeroIndex is a no op but we still have to visit its source
+ // because it might have side effects.
+ Visit(src);
return {};
}
target = {registers.at(gpr->GetIndex()), Type::Float};
@@ -1584,6 +1637,15 @@ private:
return {OpCompositeConstruct(t_half, low, high), Type::HalfFloat};
}
+ Expression LogicalAddCarry(Operation operation) {
+ const Id op_a = AsUint(Visit(operation[0]));
+ const Id op_b = AsUint(Visit(operation[1]));
+
+ const Id result = OpIAddCarry(TypeStruct({t_uint, t_uint}), op_a, op_b);
+ const Id carry = OpCompositeExtract(t_uint, result, 1);
+ return {OpINotEqual(t_bool, carry, v_uint_zero), Type::Bool};
+ }
+
Expression LogicalAssign(Operation operation) {
const Node& dest = operation[0];
const Node& src = operation[1];
@@ -1609,13 +1671,31 @@ private:
return {};
}
+ Expression LogicalFOrdered(Operation operation) {
+ // Emulate SPIR-V's OpOrdered
+ const Id op_a = AsFloat(Visit(operation[0]));
+ const Id op_b = AsFloat(Visit(operation[1]));
+ const Id is_num_a = OpFOrdEqual(t_bool, op_a, op_a);
+ const Id is_num_b = OpFOrdEqual(t_bool, op_b, op_b);
+ return {OpLogicalAnd(t_bool, is_num_a, is_num_b), Type::Bool};
+ }
+
+ Expression LogicalFUnordered(Operation operation) {
+ // Emulate SPIR-V's OpUnordered
+ const Id op_a = AsFloat(Visit(operation[0]));
+ const Id op_b = AsFloat(Visit(operation[1]));
+ const Id is_nan_a = OpIsNan(t_bool, op_a);
+ const Id is_nan_b = OpIsNan(t_bool, op_b);
+ return {OpLogicalOr(t_bool, is_nan_a, is_nan_b), Type::Bool};
+ }
+
Id GetTextureSampler(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
- ASSERT(!meta.sampler.IsBuffer());
+ ASSERT(!meta.sampler.is_buffer);
- const auto& entry = sampled_images.at(meta.sampler.GetIndex());
+ const auto& entry = sampled_images.at(meta.sampler.index);
Id sampler = entry.variable;
- if (meta.sampler.IsIndexed()) {
+ if (meta.sampler.is_indexed) {
const Id index = AsInt(Visit(meta.index));
sampler = OpAccessChain(entry.sampler_pointer_type, sampler, index);
}
@@ -1624,9 +1704,9 @@ private:
Id GetTextureImage(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
- const u32 index = meta.sampler.GetIndex();
- if (meta.sampler.IsBuffer()) {
- const auto& entry = texel_buffers.at(index);
+ const u32 index = meta.sampler.index;
+ if (meta.sampler.is_buffer) {
+ const auto& entry = uniform_texels.at(index);
return OpLoad(entry.image_type, entry.image);
} else {
const auto& entry = sampled_images.at(index);
@@ -1636,7 +1716,7 @@ private:
Id GetImage(Operation operation) {
const auto& meta = std::get<MetaImage>(operation.GetMeta());
- const auto entry = images.at(meta.image.GetIndex());
+ const auto entry = images.at(meta.image.index);
return OpLoad(entry.image_type, entry.image);
}
@@ -1652,7 +1732,7 @@ private:
}
if (const auto meta = std::get_if<MetaTexture>(&operation.GetMeta())) {
// Add array coordinate for textures
- if (meta->sampler.IsArray()) {
+ if (meta->sampler.is_array) {
Id array = AsInt(Visit(meta->array));
if (type == Type::Float) {
array = OpConvertSToF(t_float, array);
@@ -1758,7 +1838,7 @@ private:
operands.push_back(GetOffsetCoordinates(operation));
}
- if (meta.sampler.IsShadow()) {
+ if (meta.sampler.is_shadow) {
const Id dref = AsFloat(Visit(meta.depth_compare));
return {OpImageSampleDrefExplicitLod(t_float, sampler, coords, dref, mask, operands),
Type::Float};
@@ -1773,7 +1853,7 @@ private:
const Id coords = GetCoordinates(operation, Type::Float);
Id texture{};
- if (meta.sampler.IsShadow()) {
+ if (meta.sampler.is_shadow) {
texture = OpImageDrefGather(t_float4, GetTextureSampler(operation), coords,
AsFloat(Visit(meta.depth_compare)));
} else {
@@ -1800,8 +1880,8 @@ private:
}
const Id lod = AsUint(Visit(operation[0]));
- const std::size_t coords_count = [&]() {
- switch (const auto type = meta.sampler.GetType(); type) {
+ const std::size_t coords_count = [&meta] {
+ switch (const auto type = meta.sampler.type) {
case Tegra::Shader::TextureType::Texture1D:
return 1;
case Tegra::Shader::TextureType::Texture2D:
@@ -1810,7 +1890,7 @@ private:
case Tegra::Shader::TextureType::Texture3D:
return 3;
default:
- UNREACHABLE_MSG("Invalid texture type={}", static_cast<u32>(type));
+ UNREACHABLE_MSG("Invalid texture type={}", static_cast<int>(type));
return 2;
}
}();
@@ -1853,7 +1933,7 @@ private:
const Id image = GetTextureImage(operation);
const Id coords = GetCoordinates(operation, Type::Int);
Id fetch;
- if (meta.lod && !meta.sampler.IsBuffer()) {
+ if (meta.lod && !meta.sampler.is_buffer) {
fetch = OpImageFetch(t_float4, image, coords, spv::ImageOperandsMask::Lod,
AsInt(Visit(meta.lod)));
} else {
@@ -1903,39 +1983,20 @@ private:
return {};
}
- Expression AtomicImageAdd(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
-
- Expression AtomicImageMin(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
-
- Expression AtomicImageMax(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
-
- Expression AtomicImageAnd(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
-
- Expression AtomicImageOr(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
+ template <Id (Module::*func)(Id, Id, Id, Id, Id)>
+ Expression AtomicImage(Operation operation) {
+ const auto& meta{std::get<MetaImage>(operation.GetMeta())};
+ ASSERT(meta.values.size() == 1);
- Expression AtomicImageXor(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
+ const Id coordinate = GetCoordinates(operation, Type::Int);
+ const Id image = images.at(meta.image.index).image;
+ const Id sample = v_uint_zero;
+ const Id pointer = OpImageTexelPointer(t_image_uint, image, coordinate, sample);
- Expression AtomicImageExchange(Operation operation) {
- UNIMPLEMENTED();
- return {};
+ const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device));
+ const Id semantics = v_uint_zero;
+ const Id value = AsUint(Visit(meta.values[0]));
+ return {(this->*func)(t_uint, pointer, scope, semantics, value), Type::Uint};
}
template <Id (Module::*func)(Id, Id, Id, Id, Id)>
@@ -1950,7 +2011,7 @@ private:
return {v_float_zero, Type::Float};
}
const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device));
- const Id semantics = Constant(t_uint, 0);
+ const Id semantics = v_uint_zero;
const Id value = AsUint(Visit(operation[1]));
return {(this->*func)(t_uint, pointer, scope, semantics, value), Type::Uint};
@@ -2148,14 +2209,37 @@ private:
return {OpLoad(t_uint, thread_id), Type::Uint};
}
+ template <std::size_t index>
+ Expression ThreadMask(Operation) {
+ // TODO(Rodrigo): Handle devices with different warp sizes
+ const Id mask = thread_masks[index];
+ return {OpLoad(t_uint, AccessElement(t_in_uint, mask, 0)), Type::Uint};
+ }
+
Expression ShuffleIndexed(Operation operation) {
const Id value = AsFloat(Visit(operation[0]));
const Id index = AsUint(Visit(operation[1]));
return {OpSubgroupReadInvocationKHR(t_float, value, index), Type::Float};
}
- Expression MemoryBarrierGL(Operation) {
- const auto scope = spv::Scope::Device;
+ Expression Barrier(Operation) {
+ if (!ir.IsDecompiled()) {
+ LOG_ERROR(Render_Vulkan, "OpBarrier used by shader is not decompiled");
+ return {};
+ }
+
+ const auto scope = spv::Scope::Workgroup;
+ const auto memory = spv::Scope::Workgroup;
+ const auto semantics =
+ spv::MemorySemanticsMask::WorkgroupMemory | spv::MemorySemanticsMask::AcquireRelease;
+ OpControlBarrier(Constant(t_uint, static_cast<u32>(scope)),
+ Constant(t_uint, static_cast<u32>(memory)),
+ Constant(t_uint, static_cast<u32>(semantics)));
+ return {};
+ }
+
+ template <spv::Scope scope>
+ Expression MemoryBarrier(Operation) {
const auto semantics =
spv::MemorySemanticsMask::AcquireRelease | spv::MemorySemanticsMask::UniformMemory |
spv::MemorySemanticsMask::WorkgroupMemory |
@@ -2502,7 +2586,14 @@ private:
&SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::Float>,
&SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::Float>,
&SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::Float>,
- &SPIRVDecompiler::Unary<&Module::OpIsNan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::LogicalFOrdered,
+ &SPIRVDecompiler::LogicalFUnordered,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordLessThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordLessThanEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordGreaterThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordNotEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFUnordGreaterThanEqual, Type::Bool, Type::Float>,
&SPIRVDecompiler::Binary<&Module::OpSLessThan, Type::Bool, Type::Int>,
&SPIRVDecompiler::Binary<&Module::OpIEqual, Type::Bool, Type::Int>,
@@ -2518,6 +2609,8 @@ private:
&SPIRVDecompiler::Binary<&Module::OpINotEqual, Type::Bool, Type::Uint>,
&SPIRVDecompiler::Binary<&Module::OpUGreaterThanEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::LogicalAddCarry,
+
&SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool2, Type::HalfFloat>,
&SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool2, Type::HalfFloat>,
&SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool2, Type::HalfFloat>,
@@ -2542,11 +2635,11 @@ private:
&SPIRVDecompiler::ImageLoad,
&SPIRVDecompiler::ImageStore,
- &SPIRVDecompiler::AtomicImageAdd,
- &SPIRVDecompiler::AtomicImageAnd,
- &SPIRVDecompiler::AtomicImageOr,
- &SPIRVDecompiler::AtomicImageXor,
- &SPIRVDecompiler::AtomicImageExchange,
+ &SPIRVDecompiler::AtomicImage<&Module::OpAtomicIAdd>,
+ &SPIRVDecompiler::AtomicImage<&Module::OpAtomicAnd>,
+ &SPIRVDecompiler::AtomicImage<&Module::OpAtomicOr>,
+ &SPIRVDecompiler::AtomicImage<&Module::OpAtomicXor>,
+ &SPIRVDecompiler::AtomicImage<&Module::OpAtomicExchange>,
&SPIRVDecompiler::Atomic<&Module::OpAtomicExchange>,
&SPIRVDecompiler::Atomic<&Module::OpAtomicIAdd>,
@@ -2603,9 +2696,16 @@ private:
&SPIRVDecompiler::Vote<&Module::OpSubgroupAllEqualKHR>,
&SPIRVDecompiler::ThreadId,
+ &SPIRVDecompiler::ThreadMask<0>, // Eq
+ &SPIRVDecompiler::ThreadMask<1>, // Ge
+ &SPIRVDecompiler::ThreadMask<2>, // Gt
+ &SPIRVDecompiler::ThreadMask<3>, // Le
+ &SPIRVDecompiler::ThreadMask<4>, // Lt
&SPIRVDecompiler::ShuffleIndexed,
- &SPIRVDecompiler::MemoryBarrierGL,
+ &SPIRVDecompiler::Barrier,
+ &SPIRVDecompiler::MemoryBarrier<spv::Scope::Workgroup>,
+ &SPIRVDecompiler::MemoryBarrier<spv::Scope::Device>,
};
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
@@ -2681,8 +2781,11 @@ private:
Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct);
+ const Id t_image_uint = TypePointer(spv::StorageClass::Image, t_uint);
+
const Id v_float_zero = Constant(t_float, 0.0f);
const Id v_float_one = Constant(t_float, 1.0f);
+ const Id v_uint_zero = Constant(t_uint, 0);
// Nvidia uses these defaults for varyings (e.g. position and generic attributes)
const Id v_varying_default =
@@ -2707,15 +2810,15 @@ private:
std::unordered_map<u8, GenericVaryingDescription> output_attributes;
std::map<u32, Id> constant_buffers;
std::map<GlobalMemoryBase, Id> global_buffers;
- std::map<u32, TexelBuffer> texel_buffers;
+ std::map<u32, TexelBuffer> uniform_texels;
std::map<u32, SampledImage> sampled_images;
std::map<u32, StorageImage> images;
+ std::array<Id, Maxwell::NumRenderTargets> frag_colors{};
Id instance_index{};
Id vertex_index{};
Id base_instance{};
Id base_vertex{};
- std::array<Id, Maxwell::NumRenderTargets> frag_colors{};
Id frag_depth{};
Id frag_coord{};
Id front_facing{};
@@ -2727,6 +2830,7 @@ private:
Id workgroup_id{};
Id local_invocation_id{};
Id thread_id{};
+ std::array<Id, 5> thread_masks{}; // eq, ge, gt, le, lt
VertexIndices in_indices;
VertexIndices out_indices;
@@ -2969,14 +3073,18 @@ ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir) {
entries.global_buffers.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_written);
}
for (const auto& sampler : ir.GetSamplers()) {
- if (sampler.IsBuffer()) {
- entries.texel_buffers.emplace_back(sampler);
+ if (sampler.is_buffer) {
+ entries.uniform_texels.emplace_back(sampler);
} else {
entries.samplers.emplace_back(sampler);
}
}
for (const auto& image : ir.GetImages()) {
- entries.images.emplace_back(image);
+ if (image.type == Tegra::Shader::ImageType::TextureBuffer) {
+ entries.storage_texels.emplace_back(image);
+ } else {
+ entries.images.emplace_back(image);
+ }
}
for (const auto& attribute : ir.GetInputAttributes()) {
if (IsGenericAttribute(attribute)) {
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
index ffea4709e..2b0e90396 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -5,11 +5,7 @@
#pragma once
#include <array>
-#include <bitset>
-#include <memory>
#include <set>
-#include <type_traits>
-#include <utility>
#include <vector>
#include "common/common_types.h"
@@ -25,8 +21,9 @@ class VKDevice;
namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using TexelBufferEntry = VideoCommon::Shader::Sampler;
+using UniformTexelEntry = VideoCommon::Shader::Sampler;
using SamplerEntry = VideoCommon::Shader::Sampler;
+using StorageTexelEntry = VideoCommon::Shader::Image;
using ImageEntry = VideoCommon::Shader::Image;
constexpr u32 DESCRIPTOR_SET = 0;
@@ -70,13 +67,15 @@ private:
struct ShaderEntries {
u32 NumBindings() const {
return static_cast<u32>(const_buffers.size() + global_buffers.size() +
- texel_buffers.size() + samplers.size() + images.size());
+ uniform_texels.size() + samplers.size() + storage_texels.size() +
+ images.size());
}
std::vector<ConstBufferEntry> const_buffers;
std::vector<GlobalBufferEntry> global_buffers;
- std::vector<TexelBufferEntry> texel_buffers;
+ std::vector<UniformTexelEntry> uniform_texels;
std::vector<SamplerEntry> samplers;
+ std::vector<StorageTexelEntry> storage_texels;
std::vector<ImageEntry> images;
std::set<u32> attributes;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
@@ -92,7 +91,8 @@ struct Specialization final {
u32 shared_memory_size{};
// Graphics specific
- std::optional<float> point_size{};
+ std::optional<float> point_size;
+ std::bitset<Maxwell::NumVertexAttributes> enabled_attributes;
std::array<Maxwell::VertexAttribute::Type, Maxwell::NumVertexAttributes> attribute_types{};
bool ndc_minus_one_to_one{};
};
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp
index 784839327..c1a218d76 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp
@@ -4,8 +4,7 @@
#include <cstring>
#include <memory>
-#include <vector>
-#include "common/alignment.h"
+
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_device.h"
@@ -20,13 +19,13 @@ vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, cons
const auto data = std::make_unique<u32[]>(code_size / sizeof(u32));
std::memcpy(data.get(), code_data, code_size);
- VkShaderModuleCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.codeSize = code_size;
- ci.pCode = data.get();
- return device.GetLogical().CreateShaderModule(ci);
+ return device.GetLogical().CreateShaderModule({
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .codeSize = code_size,
+ .pCode = data.get(),
+ });
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h
index be38d6697..d1d3f3cae 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.h
+++ b/src/video_core/renderer_vulkan/vk_shader_util.h
@@ -4,7 +4,6 @@
#pragma once
-#include <vector>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/wrapper.h"
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index 94d954d7a..2fd3b7f39 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -10,37 +10,18 @@
#include "common/bit_util.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
-VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence,
- u64 last_epoch)
- : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {}
+VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer_)
+ : buffer{std::move(buffer_)} {}
-VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept {
- buffer = std::move(rhs.buffer);
- watch = std::move(rhs.watch);
- last_epoch = rhs.last_epoch;
-}
-
-VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default;
-
-VKStagingBufferPool::StagingBuffer& VKStagingBufferPool::StagingBuffer::operator=(
- StagingBuffer&& rhs) noexcept {
- buffer = std::move(rhs.buffer);
- watch = std::move(rhs.watch);
- last_epoch = rhs.last_epoch;
- return *this;
-}
-
-VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler)
- : device{device}, memory_manager{memory_manager}, scheduler{scheduler},
- is_device_integrated{device.IsIntegrated()} {}
+VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device_, VKMemoryManager& memory_manager_,
+ VKScheduler& scheduler_)
+ : device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_} {}
VKStagingBufferPool::~VKStagingBufferPool() = default;
@@ -52,21 +33,19 @@ VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visib
}
void VKStagingBufferPool::TickFrame() {
- ++epoch;
current_delete_level = (current_delete_level + 1) % NumLevels;
ReleaseCache(true);
- if (!is_device_integrated) {
- ReleaseCache(false);
- }
+ ReleaseCache(false);
}
VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) {
- for (auto& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
- if (entry.watch.TryWatch(scheduler.GetFence())) {
- entry.last_epoch = epoch;
- return &*entry.buffer;
+ for (StagingBuffer& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
+ if (!scheduler.IsFree(entry.tick)) {
+ continue;
}
+ entry.tick = scheduler.CurrentTick();
+ return &*entry.buffer;
}
return nullptr;
}
@@ -74,28 +53,29 @@ VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_
VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) {
const u32 log2 = Common::Log2Ceil64(size);
- VkBufferCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.size = 1ULL << log2;
- ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
- VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
- VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
-
auto buffer = std::make_unique<VKBuffer>();
- buffer->handle = device.GetLogical().CreateBuffer(ci);
+ buffer->handle = device.GetLogical().CreateBuffer({
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = 1ULL << log2,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
buffer->commit = memory_manager.Commit(buffer->handle, host_visible);
- auto& entries = GetCache(host_visible)[log2].entries;
- return *entries.emplace_back(std::move(buffer), scheduler.GetFence(), epoch).buffer;
+ std::vector<StagingBuffer>& entries = GetCache(host_visible)[log2].entries;
+ StagingBuffer& entry = entries.emplace_back(std::move(buffer));
+ entry.tick = scheduler.CurrentTick();
+ return *entry.buffer;
}
VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) {
- return is_device_integrated || host_visible ? host_staging_buffers : device_staging_buffers;
+ return host_visible ? host_staging_buffers : device_staging_buffers;
}
void VKStagingBufferPool::ReleaseCache(bool host_visible) {
@@ -113,9 +93,8 @@ u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t lo
auto& entries = staging.entries;
const std::size_t old_size = entries.size();
- const auto is_deleteable = [this](const auto& entry) {
- static constexpr u64 epochs_to_destroy = 180;
- return entry.last_epoch + epochs_to_destroy < epoch && !entry.watch.IsUsed();
+ const auto is_deleteable = [this](const StagingBuffer& entry) {
+ return scheduler.IsFree(entry.tick);
};
const std::size_t begin_offset = staging.delete_index;
const std::size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size);
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index a0840ff8c..2dd5049ac 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -5,20 +5,16 @@
#pragma once
#include <climits>
-#include <unordered_map>
-#include <utility>
#include <vector>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/wrapper.h"
namespace Vulkan {
class VKDevice;
-class VKFenceWatch;
class VKScheduler;
struct VKBuffer final {
@@ -38,16 +34,10 @@ public:
private:
struct StagingBuffer final {
- explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, u64 last_epoch);
- StagingBuffer(StagingBuffer&& rhs) noexcept;
- StagingBuffer(const StagingBuffer&) = delete;
- ~StagingBuffer();
-
- StagingBuffer& operator=(StagingBuffer&& rhs) noexcept;
+ explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer);
std::unique_ptr<VKBuffer> buffer;
- VKFenceWatch watch;
- u64 last_epoch = 0;
+ u64 tick = 0;
};
struct StagingBuffers final {
@@ -71,13 +61,10 @@ private:
const VKDevice& device;
VKMemoryManager& memory_manager;
VKScheduler& scheduler;
- const bool is_device_integrated;
StagingBuffersCache host_staging_buffers;
StagingBuffersCache device_staging_buffers;
- u64 epoch = 0;
-
std::size_t current_delete_level = 0;
};
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index 94a89e388..5d2c4a796 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -36,6 +36,14 @@ Flags MakeInvalidationFlags() {
flags[BlendConstants] = true;
flags[DepthBounds] = true;
flags[StencilProperties] = true;
+ flags[CullMode] = true;
+ flags[DepthBoundsEnable] = true;
+ flags[DepthTestEnable] = true;
+ flags[DepthWriteEnable] = true;
+ flags[DepthCompareOp] = true;
+ flags[FrontFace] = true;
+ flags[StencilOp] = true;
+ flags[StencilTestEnable] = true;
return flags;
}
@@ -75,14 +83,58 @@ void SetupDirtyStencilProperties(Tables& tables) {
table[OFF(stencil_back_func_mask)] = StencilProperties;
}
-} // Anonymous namespace
+void SetupDirtyCullMode(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(cull_face)] = CullMode;
+ table[OFF(cull_test_enabled)] = CullMode;
+}
+
+void SetupDirtyDepthBoundsEnable(Tables& tables) {
+ tables[0][OFF(depth_bounds_enable)] = DepthBoundsEnable;
+}
+
+void SetupDirtyDepthTestEnable(Tables& tables) {
+ tables[0][OFF(depth_test_enable)] = DepthTestEnable;
+}
+
+void SetupDirtyDepthWriteEnable(Tables& tables) {
+ tables[0][OFF(depth_write_enabled)] = DepthWriteEnable;
+}
+
+void SetupDirtyDepthCompareOp(Tables& tables) {
+ tables[0][OFF(depth_test_func)] = DepthCompareOp;
+}
-StateTracker::StateTracker(Core::System& system)
- : system{system}, invalidation_flags{MakeInvalidationFlags()} {}
+void SetupDirtyFrontFace(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(front_face)] = FrontFace;
+ table[OFF(screen_y_control)] = FrontFace;
+}
+
+void SetupDirtyStencilOp(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(stencil_front_op_fail)] = StencilOp;
+ table[OFF(stencil_front_op_zfail)] = StencilOp;
+ table[OFF(stencil_front_op_zpass)] = StencilOp;
+ table[OFF(stencil_front_func_func)] = StencilOp;
+ table[OFF(stencil_back_op_fail)] = StencilOp;
+ table[OFF(stencil_back_op_zfail)] = StencilOp;
+ table[OFF(stencil_back_op_zpass)] = StencilOp;
+ table[OFF(stencil_back_func_func)] = StencilOp;
+
+ // Table 0 is used by StencilProperties
+ tables[1][OFF(stencil_two_side_enable)] = StencilOp;
+}
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
- auto& tables = dirty.tables;
+void SetupDirtyStencilTestEnable(Tables& tables) {
+ tables[0][OFF(stencil_enable)] = StencilTestEnable;
+}
+
+} // Anonymous namespace
+
+StateTracker::StateTracker(Tegra::GPU& gpu)
+ : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
+ auto& tables = gpu.Maxwell3D().dirty.tables;
SetupDirtyRenderTargets(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
@@ -90,10 +142,14 @@ void StateTracker::Initialize() {
SetupDirtyBlendConstants(tables);
SetupDirtyDepthBounds(tables);
SetupDirtyStencilProperties(tables);
-}
-
-void StateTracker::InvalidateCommandBufferState() {
- system.GPU().Maxwell3D().dirty.flags |= invalidation_flags;
+ SetupDirtyCullMode(tables);
+ SetupDirtyDepthBoundsEnable(tables);
+ SetupDirtyDepthTestEnable(tables);
+ SetupDirtyDepthWriteEnable(tables);
+ SetupDirtyDepthCompareOp(tables);
+ SetupDirtyFrontFace(tables);
+ SetupDirtyStencilOp(tables);
+ SetupDirtyStencilTestEnable(tables);
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 03bc415b2..1de789e57 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -26,6 +26,15 @@ enum : u8 {
DepthBounds,
StencilProperties,
+ CullMode,
+ DepthBoundsEnable,
+ DepthTestEnable,
+ DepthWriteEnable,
+ DepthCompareOp,
+ FrontFace,
+ StencilOp,
+ StencilTestEnable,
+
Last
};
static_assert(Last <= std::numeric_limits<u8>::max());
@@ -33,12 +42,15 @@ static_assert(Last <= std::numeric_limits<u8>::max());
} // namespace Dirty
class StateTracker {
-public:
- explicit StateTracker(Core::System& system);
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
- void Initialize();
+public:
+ explicit StateTracker(Tegra::GPU& gpu);
- void InvalidateCommandBufferState();
+ void InvalidateCommandBufferState() {
+ flags |= invalidation_flags;
+ current_topology = INVALID_TOPOLOGY;
+ }
bool TouchViewports() {
return Exchange(Dirty::Viewports, false);
@@ -64,16 +76,60 @@ public:
return Exchange(Dirty::StencilProperties, false);
}
+ bool TouchCullMode() {
+ return Exchange(Dirty::CullMode, false);
+ }
+
+ bool TouchDepthBoundsTestEnable() {
+ return Exchange(Dirty::DepthBoundsEnable, false);
+ }
+
+ bool TouchDepthTestEnable() {
+ return Exchange(Dirty::DepthTestEnable, false);
+ }
+
+ bool TouchDepthBoundsEnable() {
+ return Exchange(Dirty::DepthBoundsEnable, false);
+ }
+
+ bool TouchDepthWriteEnable() {
+ return Exchange(Dirty::DepthWriteEnable, false);
+ }
+
+ bool TouchDepthCompareOp() {
+ return Exchange(Dirty::DepthCompareOp, false);
+ }
+
+ bool TouchFrontFace() {
+ return Exchange(Dirty::FrontFace, false);
+ }
+
+ bool TouchStencilOp() {
+ return Exchange(Dirty::StencilOp, false);
+ }
+
+ bool TouchStencilTestEnable() {
+ return Exchange(Dirty::StencilTestEnable, false);
+ }
+
+ bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) {
+ const bool has_changed = current_topology != new_topology;
+ current_topology = new_topology;
+ return has_changed;
+ }
+
private:
+ static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u);
+
bool Exchange(std::size_t id, bool new_value) const noexcept {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
const bool is_dirty = flags[id];
flags[id] = new_value;
return is_dirty;
}
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
+ Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
index 38a93a01a..1b59612b9 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <limits>
#include <optional>
#include <tuple>
#include <vector>
@@ -10,7 +11,6 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
#include "video_core/renderer_vulkan/wrapper.h"
@@ -22,27 +22,43 @@ namespace {
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
-constexpr u64 STREAM_BUFFER_SIZE = 256 * 1024 * 1024;
+constexpr u64 PREFERRED_STREAM_BUFFER_SIZE = 256 * 1024 * 1024;
-std::optional<u32> FindMemoryType(const VKDevice& device, u32 filter,
- VkMemoryPropertyFlags wanted) {
- const auto properties = device.GetPhysical().GetMemoryProperties();
- for (u32 i = 0; i < properties.memoryTypeCount; i++) {
- if (!(filter & (1 << i))) {
- continue;
- }
- if ((properties.memoryTypes[i].propertyFlags & wanted) == wanted) {
+/// Find a memory type with the passed requirements
+std::optional<u32> FindMemoryType(const VkPhysicalDeviceMemoryProperties& properties,
+ VkMemoryPropertyFlags wanted,
+ u32 filter = std::numeric_limits<u32>::max()) {
+ for (u32 i = 0; i < properties.memoryTypeCount; ++i) {
+ const auto flags = properties.memoryTypes[i].propertyFlags;
+ if ((flags & wanted) == wanted && (filter & (1U << i)) != 0) {
return i;
}
}
return std::nullopt;
}
+/// Get the preferred host visible memory type.
+u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties,
+ u32 filter = std::numeric_limits<u32>::max()) {
+ // Prefer device local host visible allocations. Both AMD and Nvidia now provide one.
+ // Otherwise search for a host visible allocation.
+ static constexpr auto HOST_MEMORY =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ static constexpr auto DYNAMIC_MEMORY = HOST_MEMORY | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+
+ std::optional preferred_type = FindMemoryType(properties, DYNAMIC_MEMORY);
+ if (!preferred_type) {
+ preferred_type = FindMemoryType(properties, HOST_MEMORY);
+ ASSERT_MSG(preferred_type, "No host visible and coherent memory type found");
+ }
+ return preferred_type.value_or(0);
+}
+
} // Anonymous namespace
-VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
+VKStreamBuffer::VKStreamBuffer(const VKDevice& device_, VKScheduler& scheduler_,
VkBufferUsageFlags usage)
- : device{device}, scheduler{scheduler} {
+ : device{device_}, scheduler{scheduler_} {
CreateBuffers(usage);
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
@@ -51,7 +67,7 @@ VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
VKStreamBuffer::~VKStreamBuffer() = default;
std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
- ASSERT(size <= STREAM_BUFFER_SIZE);
+ ASSERT(size <= stream_buffer_size);
mapped_size = size;
if (alignment > 0) {
@@ -61,7 +77,7 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
WaitPendingOperations(offset);
bool invalidated = false;
- if (offset + size > STREAM_BUFFER_SIZE) {
+ if (offset + size > stream_buffer_size) {
// The buffer would overflow, save the amount of used watches and reset the state.
invalidation_mark = current_watch_cursor;
current_watch_cursor = 0;
@@ -94,44 +110,39 @@ void VKStreamBuffer::Unmap(u64 size) {
}
auto& watch = current_watches[current_watch_cursor++];
watch.upper_bound = offset;
- watch.fence.Watch(scheduler.GetFence());
+ watch.tick = scheduler.CurrentTick();
}
void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) {
- VkBufferCreateInfo buffer_ci;
- buffer_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- buffer_ci.pNext = nullptr;
- buffer_ci.flags = 0;
- buffer_ci.size = STREAM_BUFFER_SIZE;
- buffer_ci.usage = usage;
- buffer_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- buffer_ci.queueFamilyIndexCount = 0;
- buffer_ci.pQueueFamilyIndices = nullptr;
-
- const auto& dev = device.GetLogical();
- buffer = dev.CreateBuffer(buffer_ci);
-
- const auto& dld = device.GetDispatchLoader();
- const auto requirements = dev.GetBufferMemoryRequirements(*buffer);
- // Prefer device local host visible allocations (this should hit AMD's pinned memory).
- auto type =
- FindMemoryType(device, requirements.memoryTypeBits,
- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
- VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
- if (!type) {
- // Otherwise search for a host visible allocation.
- type = FindMemoryType(device, requirements.memoryTypeBits,
- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
- VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
- ASSERT_MSG(type, "No host visible and coherent memory type found");
- }
- VkMemoryAllocateInfo memory_ai;
- memory_ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
- memory_ai.pNext = nullptr;
- memory_ai.allocationSize = requirements.size;
- memory_ai.memoryTypeIndex = *type;
-
- memory = dev.AllocateMemory(memory_ai);
+ const auto memory_properties = device.GetPhysical().GetMemoryProperties();
+ const u32 preferred_type = GetMemoryType(memory_properties);
+ const u32 preferred_heap = memory_properties.memoryTypes[preferred_type].heapIndex;
+
+ // Substract from the preferred heap size some bytes to avoid getting out of memory.
+ const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size;
+ // As per DXVK's example, using `heap_size / 2`
+ const VkDeviceSize allocable_size = heap_size / 2;
+ buffer = device.GetLogical().CreateBuffer({
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = std::min(PREFERRED_STREAM_BUFFER_SIZE, allocable_size),
+ .usage = usage,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
+
+ const auto requirements = device.GetLogical().GetBufferMemoryRequirements(*buffer);
+ const u32 required_flags = requirements.memoryTypeBits;
+ stream_buffer_size = static_cast<u64>(requirements.size);
+
+ memory = device.GetLogical().AllocateMemory({
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .allocationSize = requirements.size,
+ .memoryTypeIndex = GetMemoryType(memory_properties, required_flags),
+ });
buffer.BindMemory(*memory, 0);
}
@@ -146,7 +157,7 @@ void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) {
auto& watch = previous_watches[wait_cursor];
wait_bound = watch.upper_bound;
- watch.fence.Wait();
+ scheduler.Wait(watch.tick);
++wait_cursor;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h
index 58ce8b973..5e15ad78f 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.h
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h
@@ -14,7 +14,6 @@
namespace Vulkan {
class VKDevice;
-class VKFence;
class VKFenceWatch;
class VKScheduler;
@@ -35,13 +34,17 @@ public:
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
void Unmap(u64 size);
- VkBuffer GetHandle() const {
+ VkBuffer Handle() const noexcept {
return *buffer;
}
+ u64 Address() const noexcept {
+ return 0;
+ }
+
private:
- struct Watch final {
- VKFenceWatch fence;
+ struct Watch {
+ u64 tick{};
u64 upper_bound{};
};
@@ -56,8 +59,9 @@ private:
const VKDevice& device; ///< Vulkan device manager.
VKScheduler& scheduler; ///< Command scheduler.
- vk::Buffer buffer; ///< Mapped buffer.
- vk::DeviceMemory memory; ///< Memory allocation.
+ vk::Buffer buffer; ///< Mapped buffer.
+ vk::DeviceMemory memory; ///< Memory allocation.
+ u64 stream_buffer_size{}; ///< Stream buffer size.
u64 offset{}; ///< Buffer iterator.
u64 mapped_size{}; ///< Size reserved for the current copy.
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index bffd8f32a..9636a7c65 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -12,7 +12,7 @@
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/renderer_vulkan/wrapper.h"
@@ -56,8 +56,8 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi
} // Anonymous namespace
-VKSwapchain::VKSwapchain(VkSurfaceKHR surface, const VKDevice& device)
- : surface{surface}, device{device} {}
+VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const VKDevice& device_, VKScheduler& scheduler_)
+ : surface{surface_}, device{device_}, scheduler{scheduler_} {}
VKSwapchain::~VKSwapchain() = default;
@@ -75,35 +75,33 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) {
CreateSemaphores();
CreateImageViews();
- fences.resize(image_count, nullptr);
+ resource_ticks.clear();
+ resource_ticks.resize(image_count);
}
void VKSwapchain::AcquireNextImage() {
device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
*present_semaphores[frame_index], {}, &image_index);
- if (auto& fence = fences[image_index]; fence) {
- fence->Wait();
- fence->Release();
- fence = nullptr;
- }
+ scheduler.Wait(resource_ticks[image_index]);
}
-bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
+bool VKSwapchain::Present(VkSemaphore render_semaphore) {
const VkSemaphore present_semaphore{*present_semaphores[frame_index]};
const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore};
const auto present_queue{device.GetPresentQueue()};
bool recreated = false;
- VkPresentInfoKHR present_info;
- present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
- present_info.pNext = nullptr;
- present_info.waitSemaphoreCount = render_semaphore ? 2U : 1U;
- present_info.pWaitSemaphores = semaphores.data();
- present_info.swapchainCount = 1;
- present_info.pSwapchains = swapchain.address();
- present_info.pImageIndices = &image_index;
- present_info.pResults = nullptr;
+ const VkPresentInfoKHR present_info{
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .pNext = nullptr,
+ .waitSemaphoreCount = render_semaphore ? 2U : 1U,
+ .pWaitSemaphores = semaphores.data(),
+ .swapchainCount = 1,
+ .pSwapchains = swapchain.address(),
+ .pImageIndices = &image_index,
+ .pResults = nullptr,
+ };
switch (const VkResult result = present_queue.Present(present_info)) {
case VK_SUCCESS:
@@ -122,8 +120,7 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
break;
}
- ASSERT(fences[image_index] == nullptr);
- fences[image_index] = &fence;
+ resource_ticks[image_index] = scheduler.CurrentTick();
frame_index = (frame_index + 1) % static_cast<u32>(image_count);
return recreated;
}
@@ -147,24 +144,26 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
requested_image_count = capabilities.maxImageCount;
}
- VkSwapchainCreateInfoKHR swapchain_ci;
- swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
- swapchain_ci.pNext = nullptr;
- swapchain_ci.flags = 0;
- swapchain_ci.surface = surface;
- swapchain_ci.minImageCount = requested_image_count;
- swapchain_ci.imageFormat = surface_format.format;
- swapchain_ci.imageColorSpace = surface_format.colorSpace;
- swapchain_ci.imageArrayLayers = 1;
- swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
- swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
- swapchain_ci.queueFamilyIndexCount = 0;
- swapchain_ci.pQueueFamilyIndices = nullptr;
- swapchain_ci.preTransform = capabilities.currentTransform;
- swapchain_ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
- swapchain_ci.presentMode = present_mode;
- swapchain_ci.clipped = VK_FALSE;
- swapchain_ci.oldSwapchain = nullptr;
+ VkSwapchainCreateInfoKHR swapchain_ci{
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .surface = surface,
+ .minImageCount = requested_image_count,
+ .imageFormat = surface_format.format,
+ .imageColorSpace = surface_format.colorSpace,
+ .imageExtent = {},
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .preTransform = capabilities.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = present_mode,
+ .clipped = VK_FALSE,
+ .oldSwapchain = nullptr,
+ };
const u32 graphics_family{device.GetGraphicsFamily()};
const u32 present_family{device.GetPresentFamily()};
@@ -173,8 +172,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
- } else {
- swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
// Request the size again to reduce the possibility of a TOCTOU race condition.
@@ -200,20 +197,29 @@ void VKSwapchain::CreateSemaphores() {
}
void VKSwapchain::CreateImageViews() {
- VkImageViewCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- // ci.image
- ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
- ci.format = image_format;
- ci.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
- VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
- ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- ci.subresourceRange.baseMipLevel = 0;
- ci.subresourceRange.levelCount = 1;
- ci.subresourceRange.baseArrayLayer = 0;
- ci.subresourceRange.layerCount = 1;
+ VkImageViewCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = {},
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = image_format,
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ };
image_views.resize(image_count);
for (std::size_t i = 0; i < image_count; i++) {
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index a35d61345..6b39befdf 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -16,11 +16,11 @@ struct FramebufferLayout;
namespace Vulkan {
class VKDevice;
-class VKFence;
+class VKScheduler;
class VKSwapchain {
public:
- explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device);
+ explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device, VKScheduler& scheduler);
~VKSwapchain();
/// Creates (or recreates) the swapchain with a given size.
@@ -31,7 +31,7 @@ public:
/// Presents the rendered image to the swapchain. Returns true when the swapchains had to be
/// recreated. Takes responsability for the ownership of fence.
- bool Present(VkSemaphore render_semaphore, VKFence& fence);
+ bool Present(VkSemaphore render_semaphore);
/// Returns true when the framebuffer layout has changed.
bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
@@ -74,6 +74,7 @@ private:
const VkSurfaceKHR surface;
const VKDevice& device;
+ VKScheduler& scheduler;
vk::SwapchainKHR swapchain;
@@ -81,7 +82,7 @@ private:
std::vector<VkImage> images;
std::vector<vk::ImageView> image_views;
std::vector<vk::Framebuffer> framebuffers;
- std::vector<VKFence*> fences;
+ std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> present_semaphores;
u32 image_index{};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index de4c23120..f2c8f2ae1 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -10,11 +10,9 @@
#include <variant>
#include <vector>
-#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
-#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/morton.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
@@ -26,7 +24,6 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/surface.h"
-#include "video_core/textures/convert.h"
namespace Vulkan {
@@ -98,17 +95,18 @@ VkImageViewType GetImageViewType(SurfaceTarget target) {
vk::Buffer CreateBuffer(const VKDevice& device, const SurfaceParams& params,
std::size_t host_memory_size) {
// TODO(Rodrigo): Move texture buffer creation to the buffer cache
- VkBufferCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.size = static_cast<VkDeviceSize>(host_memory_size);
- ci.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
- VK_BUFFER_USAGE_TRANSFER_DST_BIT;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
- return device.GetLogical().CreateBuffer(ci);
+ return device.GetLogical().CreateBuffer({
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = static_cast<VkDeviceSize>(host_memory_size),
+ .usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
+ VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
}
VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device,
@@ -116,15 +114,16 @@ VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device,
std::size_t host_memory_size) {
ASSERT(params.IsBuffer());
- VkBufferViewCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.buffer = buffer;
- ci.format = MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format;
- ci.offset = 0;
- ci.range = static_cast<VkDeviceSize>(host_memory_size);
- return ci;
+ return {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .buffer = buffer,
+ .format =
+ MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format,
+ .offset = 0,
+ .range = static_cast<VkDeviceSize>(host_memory_size),
+ };
}
VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceParams& params) {
@@ -133,23 +132,24 @@ VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceP
const auto [format, attachable, storage] =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.pixel_format);
- VkImageCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.imageType = SurfaceTargetToImage(params.target);
- ci.format = format;
- ci.mipLevels = params.num_levels;
- ci.arrayLayers = static_cast<u32>(params.GetNumLayers());
- ci.samples = VK_SAMPLE_COUNT_1_BIT;
- ci.tiling = VK_IMAGE_TILING_OPTIMAL;
- ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- ci.queueFamilyIndexCount = 0;
- ci.pQueueFamilyIndices = nullptr;
- ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-
- ci.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
- VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ VkImageCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = SurfaceTargetToImage(params.target),
+ .format = format,
+ .extent = {},
+ .mipLevels = params.num_levels,
+ .arrayLayers = static_cast<u32>(params.GetNumLayers()),
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
if (attachable) {
ci.usage |= params.IsPixelFormatZeta() ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
@@ -170,6 +170,7 @@ VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceP
ci.extent = {params.width, params.height, 1};
break;
case SurfaceTarget::Texture3D:
+ ci.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
ci.extent = {params.width, params.height, params.depth};
break;
case SurfaceTarget::TextureBuffer:
@@ -179,14 +180,18 @@ VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceP
return ci;
}
+u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::SwizzleSource y_source,
+ Tegra::Texture::SwizzleSource z_source, Tegra::Texture::SwizzleSource w_source) {
+ return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
+ (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
+}
+
} // Anonymous namespace
-CachedSurface::CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
+CachedSurface::CachedSurface(const VKDevice& device, VKMemoryManager& memory_manager,
VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
GPUVAddr gpu_addr, const SurfaceParams& params)
- : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, system{system},
- device{device}, resource_manager{resource_manager},
+ : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, device{device},
memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {
if (params.IsBuffer()) {
buffer = CreateBuffer(device, params, host_memory_size);
@@ -206,9 +211,11 @@ CachedSurface::CachedSurface(Core::System& system, const VKDevice& device,
}
// TODO(Rodrigo): Move this to a virtual function.
- main_view = CreateViewInner(
- ViewParams(params.target, 0, static_cast<u32>(params.GetNumLayers()), 0, params.num_levels),
- true);
+ u32 num_layers = 1;
+ if (params.is_layered || params.target == SurfaceTarget::Texture3D) {
+ num_layers = params.depth;
+ }
+ main_view = CreateView(ViewParams(params.target, 0, num_layers, 0, params.num_levels));
}
CachedSurface::~CachedSurface() = default;
@@ -227,7 +234,7 @@ void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) {
void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) {
UNIMPLEMENTED_IF(params.IsBuffer());
- if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5U) {
+ if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) {
LOG_WARNING(Render_Vulkan, "A1B5G5R5 flushing is stubbed");
}
@@ -256,12 +263,8 @@ void CachedSurface::DecorateSurfaceName() {
}
View CachedSurface::CreateView(const ViewParams& params) {
- return CreateViewInner(params, false);
-}
-
-View CachedSurface::CreateViewInner(const ViewParams& params, bool is_proxy) {
// TODO(Rodrigo): Add name decorations
- return views[params] = std::make_shared<CachedSurfaceView>(device, *this, params, is_proxy);
+ return views[params] = std::make_shared<CachedSurfaceView>(device, *this, params);
}
void CachedSurface::UploadBuffer(const std::vector<u8>& staging_buffer) {
@@ -279,12 +282,10 @@ void CachedSurface::UploadBuffer(const std::vector<u8>& staging_buffer) {
VkBufferMemoryBarrier barrier;
barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
- barrier.dstAccessMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
- barrier.srcQueueFamilyIndex = VK_ACCESS_TRANSFER_WRITE_BIT;
- barrier.dstQueueFamilyIndex = VK_ACCESS_SHADER_READ_BIT;
- barrier.srcQueueFamilyIndex = 0;
- barrier.dstQueueFamilyIndex = 0;
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // They'll be ignored anyway
+ barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = dst_buffer;
barrier.offset = 0;
barrier.size = size;
@@ -321,22 +322,25 @@ void CachedSurface::UploadImage(const std::vector<u8>& staging_buffer) {
}
VkBufferImageCopy CachedSurface::GetBufferImageCopy(u32 level) const {
- VkBufferImageCopy copy;
- copy.bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted);
- copy.bufferRowLength = 0;
- copy.bufferImageHeight = 0;
- copy.imageSubresource.aspectMask = image->GetAspectMask();
- copy.imageSubresource.mipLevel = level;
- copy.imageSubresource.baseArrayLayer = 0;
- copy.imageSubresource.layerCount = static_cast<u32>(params.GetNumLayers());
- copy.imageOffset.x = 0;
- copy.imageOffset.y = 0;
- copy.imageOffset.z = 0;
- copy.imageExtent.width = params.GetMipWidth(level);
- copy.imageExtent.height = params.GetMipHeight(level);
- copy.imageExtent.depth =
- params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1;
- return copy;
+ return {
+ .bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted),
+ .bufferRowLength = 0,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = image->GetAspectMask(),
+ .mipLevel = level,
+ .baseArrayLayer = 0,
+ .layerCount = static_cast<u32>(params.GetNumLayers()),
+ },
+ .imageOffset = {.x = 0, .y = 0, .z = 0},
+ .imageExtent =
+ {
+ .width = params.GetMipWidth(level),
+ .height = params.GetMipHeight(level),
+ .depth = params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1U,
+ },
+ };
}
VkImageSubresourceRange CachedSurface::GetImageSubresourceRange() const {
@@ -345,38 +349,44 @@ VkImageSubresourceRange CachedSurface::GetImageSubresourceRange() const {
}
CachedSurfaceView::CachedSurfaceView(const VKDevice& device, CachedSurface& surface,
- const ViewParams& params, bool is_proxy)
+ const ViewParams& params)
: VideoCommon::ViewBase{params}, params{surface.GetSurfaceParams()},
image{surface.GetImageHandle()}, buffer_view{surface.GetBufferViewHandle()},
aspect_mask{surface.GetAspectMask()}, device{device}, surface{surface},
- base_layer{params.base_layer}, num_layers{params.num_layers}, base_level{params.base_level},
- num_levels{params.num_levels}, image_view_type{image ? GetImageViewType(params.target)
- : VK_IMAGE_VIEW_TYPE_1D} {}
+ base_level{params.base_level}, num_levels{params.num_levels},
+ image_view_type{image ? GetImageViewType(params.target) : VK_IMAGE_VIEW_TYPE_1D} {
+ if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
+ base_layer = 0;
+ num_layers = 1;
+ base_slice = params.base_layer;
+ num_slices = params.num_layers;
+ } else {
+ base_layer = params.base_layer;
+ num_layers = params.num_layers;
+ }
+}
CachedSurfaceView::~CachedSurfaceView() = default;
-VkImageView CachedSurfaceView::GetHandle(SwizzleSource x_source, SwizzleSource y_source,
- SwizzleSource z_source, SwizzleSource w_source) {
- const u32 swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
- if (last_image_view && last_swizzle == swizzle) {
+VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSource y_source,
+ SwizzleSource z_source, SwizzleSource w_source) {
+ const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
+ if (last_image_view && last_swizzle == new_swizzle) {
return last_image_view;
}
- last_swizzle = swizzle;
+ last_swizzle = new_swizzle;
- const auto [entry, is_cache_miss] = view_cache.try_emplace(swizzle);
+ const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle);
auto& image_view = entry->second;
if (!is_cache_miss) {
return last_image_view = *image_view;
}
- auto swizzle_x = MaxwellToVK::SwizzleSource(x_source);
- auto swizzle_y = MaxwellToVK::SwizzleSource(y_source);
- auto swizzle_z = MaxwellToVK::SwizzleSource(z_source);
- auto swizzle_w = MaxwellToVK::SwizzleSource(w_source);
-
- if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5U) {
+ std::array swizzle{MaxwellToVK::SwizzleSource(x_source), MaxwellToVK::SwizzleSource(y_source),
+ MaxwellToVK::SwizzleSource(z_source), MaxwellToVK::SwizzleSource(w_source)};
+ if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) {
// A1B5G5R5 is implemented as A1R5G5B5, we have to change the swizzle here.
- std::swap(swizzle_x, swizzle_z);
+ std::swap(swizzle[0], swizzle[2]);
}
// Games can sample depth or stencil values on textures. This is decided by the swizzle value on
@@ -386,11 +396,11 @@ VkImageView CachedSurfaceView::GetHandle(SwizzleSource x_source, SwizzleSource y
UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G);
const bool is_first = x_source == SwizzleSource::R;
switch (params.pixel_format) {
- case VideoCore::Surface::PixelFormat::Z24S8:
- case VideoCore::Surface::PixelFormat::Z32FS8:
+ case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
+ case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
aspect = is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT;
break;
- case VideoCore::Surface::PixelFormat::S8Z24:
+ case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
aspect = is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
break;
default:
@@ -398,44 +408,100 @@ VkImageView CachedSurfaceView::GetHandle(SwizzleSource x_source, SwizzleSource y
UNIMPLEMENTED();
}
- // Vulkan doesn't seem to understand swizzling of a depth stencil image, use identity
- swizzle_x = VK_COMPONENT_SWIZZLE_R;
- swizzle_y = VK_COMPONENT_SWIZZLE_G;
- swizzle_z = VK_COMPONENT_SWIZZLE_B;
- swizzle_w = VK_COMPONENT_SWIZZLE_A;
+ // Make sure we sample the first component
+ std::transform(
+ swizzle.begin(), swizzle.end(), swizzle.begin(), [](VkComponentSwizzle component) {
+ return component == VK_COMPONENT_SWIZZLE_G ? VK_COMPONENT_SWIZZLE_R : component;
+ });
}
- VkImageViewCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.image = surface.GetImageHandle();
- ci.viewType = image_view_type;
- ci.format = surface.GetImage().GetFormat();
- ci.components = {swizzle_x, swizzle_y, swizzle_z, swizzle_w};
- ci.subresourceRange.aspectMask = aspect;
- ci.subresourceRange.baseMipLevel = base_level;
- ci.subresourceRange.levelCount = num_levels;
- ci.subresourceRange.baseArrayLayer = base_layer;
- ci.subresourceRange.layerCount = num_layers;
- image_view = device.GetLogical().CreateImageView(ci);
+ if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
+ ASSERT(base_slice == 0);
+ ASSERT(num_slices == params.depth);
+ }
+
+ image_view = device.GetLogical().CreateImageView({
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = surface.GetImageHandle(),
+ .viewType = image_view_type,
+ .format = surface.GetImage().GetFormat(),
+ .components =
+ {
+ .r = swizzle[0],
+ .g = swizzle[1],
+ .b = swizzle[2],
+ .a = swizzle[3],
+ },
+ .subresourceRange =
+ {
+ .aspectMask = aspect,
+ .baseMipLevel = base_level,
+ .levelCount = num_levels,
+ .baseArrayLayer = base_layer,
+ .layerCount = num_layers,
+ },
+ });
return last_image_view = *image_view;
}
-VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool)
- : TextureCache(system, rasterizer, device.IsOptimalAstcSupported()), device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
- staging_pool{staging_pool} {}
+VkImageView CachedSurfaceView::GetAttachment() {
+ if (render_target) {
+ return *render_target;
+ }
+
+ VkImageViewCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = surface.GetImageHandle(),
+ .viewType = VK_IMAGE_VIEW_TYPE_1D,
+ .format = surface.GetImage().GetFormat(),
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ {
+ .aspectMask = aspect_mask,
+ .baseMipLevel = base_level,
+ .levelCount = num_levels,
+ .baseArrayLayer = 0,
+ .layerCount = 0,
+ },
+ };
+ if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
+ ci.viewType = num_slices > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
+ ci.subresourceRange.baseArrayLayer = base_slice;
+ ci.subresourceRange.layerCount = num_slices;
+ } else {
+ ci.viewType = image_view_type;
+ ci.subresourceRange.baseArrayLayer = base_layer;
+ ci.subresourceRange.layerCount = num_layers;
+ }
+ render_target = device.GetLogical().CreateImageView(ci);
+ return *render_target;
+}
+
+VKTextureCache::VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const VKDevice& device_,
+ VKMemoryManager& memory_manager_, VKScheduler& scheduler_,
+ VKStagingBufferPool& staging_pool_)
+ : TextureCache(rasterizer, maxwell3d, gpu_memory, device_.IsOptimalAstcSupported()),
+ device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
+ staging_pool_} {}
VKTextureCache::~VKTextureCache() = default;
Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
- return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager,
- scheduler, staging_pool, gpu_addr, params);
+ return std::make_shared<CachedSurface>(device, memory_manager, scheduler, staging_pool,
+ gpu_addr, params);
}
void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
@@ -462,24 +528,40 @@ void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
- VkImageCopy copy;
- copy.srcSubresource.aspectMask = src_surface->GetAspectMask();
- copy.srcSubresource.mipLevel = copy_params.source_level;
- copy.srcSubresource.baseArrayLayer = copy_params.source_z;
- copy.srcSubresource.layerCount = num_layers;
- copy.srcOffset.x = copy_params.source_x;
- copy.srcOffset.y = copy_params.source_y;
- copy.srcOffset.z = 0;
- copy.dstSubresource.aspectMask = dst_surface->GetAspectMask();
- copy.dstSubresource.mipLevel = copy_params.dest_level;
- copy.dstSubresource.baseArrayLayer = dst_base_layer;
- copy.dstSubresource.layerCount = num_layers;
- copy.dstOffset.x = copy_params.dest_x;
- copy.dstOffset.y = copy_params.dest_y;
- copy.dstOffset.z = dst_offset_z;
- copy.extent.width = copy_params.width;
- copy.extent.height = copy_params.height;
- copy.extent.depth = extent_z;
+ const VkImageCopy copy{
+ .srcSubresource =
+ {
+ .aspectMask = src_surface->GetAspectMask(),
+ .mipLevel = copy_params.source_level,
+ .baseArrayLayer = copy_params.source_z,
+ .layerCount = num_layers,
+ },
+ .srcOffset =
+ {
+ .x = static_cast<s32>(copy_params.source_x),
+ .y = static_cast<s32>(copy_params.source_y),
+ .z = 0,
+ },
+ .dstSubresource =
+ {
+ .aspectMask = dst_surface->GetAspectMask(),
+ .mipLevel = copy_params.dest_level,
+ .baseArrayLayer = dst_base_layer,
+ .layerCount = num_layers,
+ },
+ .dstOffset =
+ {
+ .x = static_cast<s32>(copy_params.dest_x),
+ .y = static_cast<s32>(copy_params.dest_y),
+ .z = static_cast<s32>(dst_offset_z),
+ },
+ .extent =
+ {
+ .width = copy_params.width,
+ .height = copy_params.height,
+ .depth = extent_z,
+ },
+ };
const VkImage src_image = src_surface->GetImageHandle();
const VkImage dst_image = dst_surface->GetImageHandle();
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 115595f28..39202feba 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -7,23 +7,13 @@
#include <memory>
#include <unordered_map>
-#include "common/assert.h"
#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/math_util.h"
-#include "video_core/gpu.h"
-#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_vulkan/vk_image.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/texture_cache/surface_base.h"
#include "video_core/texture_cache/texture_cache.h"
-#include "video_core/textures/decoders.h"
-
-namespace Core {
-class System;
-}
namespace VideoCore {
class RasterizerInterface;
@@ -33,7 +23,6 @@ namespace Vulkan {
class RasterizerVulkan;
class VKDevice;
-class VKResourceManager;
class VKScheduler;
class VKStagingBufferPool;
@@ -51,8 +40,7 @@ class CachedSurface final : public VideoCommon::SurfaceBase<View> {
friend CachedSurfaceView;
public:
- explicit CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
+ explicit CachedSurface(const VKDevice& device, VKMemoryManager& memory_manager,
VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
GPUVAddr gpu_addr, const SurfaceParams& params);
~CachedSurface();
@@ -97,7 +85,6 @@ protected:
void DecorateSurfaceName();
View CreateView(const ViewParams& params) override;
- View CreateViewInner(const ViewParams& params, bool is_proxy);
private:
void UploadBuffer(const std::vector<u8>& staging_buffer);
@@ -108,9 +95,7 @@ private:
VkImageSubresourceRange GetImageSubresourceRange() const;
- Core::System& system;
const VKDevice& device;
- VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
VKScheduler& scheduler;
VKStagingBufferPool& staging_pool;
@@ -126,23 +111,20 @@ private:
class CachedSurfaceView final : public VideoCommon::ViewBase {
public:
explicit CachedSurfaceView(const VKDevice& device, CachedSurface& surface,
- const ViewParams& params, bool is_proxy);
+ const ViewParams& params);
~CachedSurfaceView();
- VkImageView GetHandle(Tegra::Texture::SwizzleSource x_source,
- Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source,
- Tegra::Texture::SwizzleSource w_source);
+ VkImageView GetImageView(Tegra::Texture::SwizzleSource x_source,
+ Tegra::Texture::SwizzleSource y_source,
+ Tegra::Texture::SwizzleSource z_source,
+ Tegra::Texture::SwizzleSource w_source);
+
+ VkImageView GetAttachment();
bool IsSameSurface(const CachedSurfaceView& rhs) const {
return &surface == &rhs.surface;
}
- VkImageView GetHandle() {
- return GetHandle(Tegra::Texture::SwizzleSource::R, Tegra::Texture::SwizzleSource::G,
- Tegra::Texture::SwizzleSource::B, Tegra::Texture::SwizzleSource::A);
- }
-
u32 GetWidth() const {
return params.GetMipWidth(base_level);
}
@@ -186,14 +168,6 @@ public:
}
private:
- static u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source,
- Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source,
- Tegra::Texture::SwizzleSource w_source) {
- return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
- (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
- }
-
// Store a copy of these values to avoid double dereference when reading them
const SurfaceParams params;
const VkImage image;
@@ -202,24 +176,27 @@ private:
const VKDevice& device;
CachedSurface& surface;
- const u32 base_layer;
- const u32 num_layers;
const u32 base_level;
const u32 num_levels;
const VkImageViewType image_view_type;
+ u32 base_layer = 0;
+ u32 num_layers = 0;
+ u32 base_slice = 0;
+ u32 num_slices = 0;
VkImageView last_image_view = nullptr;
u32 last_swizzle = 0;
+ vk::ImageView render_target;
std::unordered_map<u32, vk::ImageView> view_cache;
};
class VKTextureCache final : public TextureCacheBase {
public:
- explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool);
+ explicit VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
+ const VKDevice& device, VKMemoryManager& memory_manager,
+ VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
~VKTextureCache();
private:
@@ -234,7 +211,6 @@ private:
void BufferCopy(Surface& src_surface, Surface& dst_surface) override;
const VKDevice& device;
- VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
VKScheduler& scheduler;
VKStagingBufferPool& staging_pool;
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
index 4bfec0077..351c048d2 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
@@ -24,34 +24,25 @@ void VKUpdateDescriptorQueue::TickFrame() {
}
void VKUpdateDescriptorQueue::Acquire() {
- entries.clear();
-}
+ // Minimum number of entries required.
+ // This is the maximum number of entries a single draw call migth use.
+ static constexpr std::size_t MIN_ENTRIES = 0x400;
-void VKUpdateDescriptorQueue::Send(VkDescriptorUpdateTemplateKHR update_template,
- VkDescriptorSet set) {
- if (payload.size() + entries.size() >= payload.max_size()) {
+ if (payload.size() + MIN_ENTRIES >= payload.max_size()) {
LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");
scheduler.WaitWorker();
payload.clear();
}
+ upload_start = &*payload.end();
+}
- const auto payload_start = payload.data() + payload.size();
- for (const auto& entry : entries) {
- if (const auto image = std::get_if<VkDescriptorImageInfo>(&entry)) {
- payload.push_back(*image);
- } else if (const auto buffer = std::get_if<Buffer>(&entry)) {
- payload.emplace_back(*buffer->buffer, buffer->offset, buffer->size);
- } else if (const auto texel = std::get_if<VkBufferView>(&entry)) {
- payload.push_back(*texel);
- } else {
- UNREACHABLE();
- }
- }
-
- scheduler.Record(
- [payload_start, set, update_template, logical = &device.GetLogical()](vk::CommandBuffer) {
- logical->UpdateDescriptorSet(set, update_template, payload_start);
- });
+void VKUpdateDescriptorQueue::Send(VkDescriptorUpdateTemplateKHR update_template,
+ VkDescriptorSet set) {
+ const void* const data = upload_start;
+ const vk::Device* const logical = &device.GetLogical();
+ scheduler.Record([data, logical, set, update_template](vk::CommandBuffer) {
+ logical->UpdateDescriptorSet(set, update_template, data);
+ });
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h
index a9e3d5dba..945320c72 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.h
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h
@@ -4,7 +4,6 @@
#pragma once
-#include <type_traits>
#include <variant>
#include <boost/container/static_vector.hpp>
@@ -16,18 +15,13 @@ namespace Vulkan {
class VKDevice;
class VKScheduler;
-class DescriptorUpdateEntry {
-public:
- explicit DescriptorUpdateEntry() : image{} {}
-
- DescriptorUpdateEntry(VkDescriptorImageInfo image) : image{image} {}
+struct DescriptorUpdateEntry {
+ DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {}
- DescriptorUpdateEntry(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size)
- : buffer{buffer, offset, size} {}
+ DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {}
- DescriptorUpdateEntry(VkBufferView texel_buffer) : texel_buffer{texel_buffer} {}
+ DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {}
-private:
union {
VkDescriptorImageInfo image;
VkDescriptorBufferInfo buffer;
@@ -47,37 +41,34 @@ public:
void Send(VkDescriptorUpdateTemplateKHR update_template, VkDescriptorSet set);
void AddSampledImage(VkSampler sampler, VkImageView image_view) {
- entries.emplace_back(VkDescriptorImageInfo{sampler, image_view, {}});
+ payload.emplace_back(VkDescriptorImageInfo{sampler, image_view, {}});
}
void AddImage(VkImageView image_view) {
- entries.emplace_back(VkDescriptorImageInfo{{}, image_view, {}});
+ payload.emplace_back(VkDescriptorImageInfo{{}, image_view, {}});
}
- void AddBuffer(const VkBuffer* buffer, u64 offset, std::size_t size) {
- entries.push_back(Buffer{buffer, offset, size});
+ void AddBuffer(VkBuffer buffer, u64 offset, std::size_t size) {
+ payload.emplace_back(VkDescriptorBufferInfo{buffer, offset, size});
}
void AddTexelBuffer(VkBufferView texel_buffer) {
- entries.emplace_back(texel_buffer);
+ payload.emplace_back(texel_buffer);
}
- VkImageLayout* GetLastImageLayout() {
- return &std::get<VkDescriptorImageInfo>(entries.back()).imageLayout;
+ VkImageLayout* LastImageLayout() {
+ return &payload.back().image.imageLayout;
}
-private:
- struct Buffer {
- const VkBuffer* buffer = nullptr;
- u64 offset = 0;
- std::size_t size = 0;
- };
- using Variant = std::variant<VkDescriptorImageInfo, Buffer, VkBufferView>;
+ const VkImageLayout* LastImageLayout() const {
+ return &payload.back().image.imageLayout;
+ }
+private:
const VKDevice& device;
VKScheduler& scheduler;
- boost::container::static_vector<Variant, 0x400> entries;
+ const DescriptorUpdateEntry* upload_start = nullptr;
boost::container::static_vector<DescriptorUpdateEntry, 0x10000> payload;
};
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index 9b94dfff1..4e83303d8 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -2,13 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <exception>
#include <memory>
#include <optional>
+#include <string_view>
#include <utility>
#include <vector>
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/renderer_vulkan/wrapper.h"
@@ -16,6 +19,44 @@ namespace Vulkan::vk {
namespace {
+template <typename Func>
+void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld,
+ Func&& func) {
+ // Calling GetProperties calls Vulkan more than needed. But they are supposed to be cheap
+ // functions.
+ std::stable_sort(devices.begin(), devices.end(),
+ [&dld, &func](VkPhysicalDevice lhs, VkPhysicalDevice rhs) {
+ return func(vk::PhysicalDevice(lhs, dld).GetProperties(),
+ vk::PhysicalDevice(rhs, dld).GetProperties());
+ });
+}
+
+void SortPhysicalDevicesPerVendor(std::vector<VkPhysicalDevice>& devices,
+ const InstanceDispatch& dld,
+ std::initializer_list<u32> vendor_ids) {
+ for (auto it = vendor_ids.end(); it != vendor_ids.begin();) {
+ --it;
+ SortPhysicalDevices(devices, dld, [id = *it](const auto& lhs, const auto& rhs) {
+ return lhs.vendorID == id && rhs.vendorID != id;
+ });
+ }
+}
+
+void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld) {
+ // Sort by name, this will set a base and make GPUs with higher numbers appear first
+ // (e.g. GTX 1650 will intentionally be listed before a GTX 1080).
+ SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) {
+ return std::string_view{lhs.deviceName} > std::string_view{rhs.deviceName};
+ });
+ // Prefer discrete over non-discrete
+ SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) {
+ return lhs.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
+ rhs.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
+ });
+ // Prefer Nvidia over AMD, AMD over Intel, Intel over the rest.
+ SortPhysicalDevicesPerVendor(devices, dld, {0x10DE, 0x1002, 0x8086});
+}
+
template <typename T>
bool Proc(T& result, const InstanceDispatch& dld, const char* proc_name,
VkInstance instance = nullptr) noexcept {
@@ -61,14 +102,25 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdPipelineBarrier);
X(vkCmdPushConstants);
X(vkCmdSetBlendConstants);
- X(vkCmdSetCheckpointNV);
X(vkCmdSetDepthBias);
X(vkCmdSetDepthBounds);
+ X(vkCmdSetEvent);
X(vkCmdSetScissor);
X(vkCmdSetStencilCompareMask);
X(vkCmdSetStencilReference);
X(vkCmdSetStencilWriteMask);
X(vkCmdSetViewport);
+ X(vkCmdWaitEvents);
+ X(vkCmdBindVertexBuffers2EXT);
+ X(vkCmdSetCullModeEXT);
+ X(vkCmdSetDepthBoundsTestEnableEXT);
+ X(vkCmdSetDepthCompareOpEXT);
+ X(vkCmdSetDepthTestEnableEXT);
+ X(vkCmdSetDepthWriteEnableEXT);
+ X(vkCmdSetFrontFaceEXT);
+ X(vkCmdSetPrimitiveTopologyEXT);
+ X(vkCmdSetStencilOpEXT);
+ X(vkCmdSetStencilTestEnableEXT);
X(vkCreateBuffer);
X(vkCreateBufferView);
X(vkCreateCommandPool);
@@ -76,6 +128,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCreateDescriptorPool);
X(vkCreateDescriptorSetLayout);
X(vkCreateDescriptorUpdateTemplateKHR);
+ X(vkCreateEvent);
X(vkCreateFence);
X(vkCreateFramebuffer);
X(vkCreateGraphicsPipelines);
@@ -94,6 +147,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkDestroyDescriptorPool);
X(vkDestroyDescriptorSetLayout);
X(vkDestroyDescriptorUpdateTemplateKHR);
+ X(vkDestroyEvent);
X(vkDestroyFence);
X(vkDestroyFramebuffer);
X(vkDestroyImage);
@@ -113,10 +167,11 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkFreeMemory);
X(vkGetBufferMemoryRequirements);
X(vkGetDeviceQueue);
+ X(vkGetEventStatus);
X(vkGetFenceStatus);
X(vkGetImageMemoryRequirements);
X(vkGetQueryPoolResults);
- X(vkGetQueueCheckpointDataNV);
+ X(vkGetSemaphoreCounterValueKHR);
X(vkMapMemory);
X(vkQueueSubmit);
X(vkResetFences);
@@ -125,6 +180,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkUpdateDescriptorSetWithTemplateKHR);
X(vkUpdateDescriptorSets);
X(vkWaitForFences);
+ X(vkWaitSemaphoresKHR);
#undef X
}
@@ -132,7 +188,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
bool Load(InstanceDispatch& dld) noexcept {
#define X(name) Proc(dld.name, dld, #name)
- return X(vkCreateInstance) && X(vkEnumerateInstanceExtensionProperties);
+ return X(vkCreateInstance) && X(vkEnumerateInstanceExtensionProperties) &&
+ X(vkEnumerateInstanceLayerProperties);
#undef X
}
@@ -230,6 +287,22 @@ const char* ToString(VkResult result) noexcept {
return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT";
case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
+ case VkResult::VK_ERROR_UNKNOWN:
+ return "VK_ERROR_UNKNOWN";
+ case VkResult::VK_ERROR_INCOMPATIBLE_VERSION_KHR:
+ return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
+ case VkResult::VK_THREAD_IDLE_KHR:
+ return "VK_THREAD_IDLE_KHR";
+ case VkResult::VK_THREAD_DONE_KHR:
+ return "VK_THREAD_DONE_KHR";
+ case VkResult::VK_OPERATION_DEFERRED_KHR:
+ return "VK_OPERATION_DEFERRED_KHR";
+ case VkResult::VK_OPERATION_NOT_DEFERRED_KHR:
+ return "VK_OPERATION_NOT_DEFERRED_KHR";
+ case VkResult::VK_PIPELINE_COMPILE_REQUIRED_EXT:
+ return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
+ case VkResult::VK_RESULT_MAX_ENUM:
+ return "VK_RESULT_MAX_ENUM";
}
return "Unknown";
}
@@ -271,6 +344,10 @@ void Destroy(VkDevice device, VkDeviceMemory handle, const DeviceDispatch& dld)
dld.vkFreeMemory(device, handle, nullptr);
}
+void Destroy(VkDevice device, VkEvent handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyEvent(device, handle, nullptr);
+}
+
void Destroy(VkDevice device, VkFence handle, const DeviceDispatch& dld) noexcept {
dld.vkDestroyFence(device, handle, nullptr);
}
@@ -339,26 +416,27 @@ VkResult Free(VkDevice device, VkCommandPool handle, Span<VkCommandBuffer> buffe
return VK_SUCCESS;
}
-Instance Instance::Create(Span<const char*> layers, Span<const char*> extensions,
+Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char*> extensions,
InstanceDispatch& dld) noexcept {
- VkApplicationInfo application_info;
- application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
- application_info.pNext = nullptr;
- application_info.pApplicationName = "yuzu Emulator";
- application_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
- application_info.pEngineName = "yuzu Emulator";
- application_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
- application_info.apiVersion = VK_API_VERSION_1_1;
-
- VkInstanceCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.pApplicationInfo = &application_info;
- ci.enabledLayerCount = layers.size();
- ci.ppEnabledLayerNames = layers.data();
- ci.enabledExtensionCount = extensions.size();
- ci.ppEnabledExtensionNames = extensions.data();
+ const VkApplicationInfo application_info{
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = nullptr,
+ .pApplicationName = "yuzu Emulator",
+ .applicationVersion = VK_MAKE_VERSION(0, 1, 0),
+ .pEngineName = "yuzu Emulator",
+ .engineVersion = VK_MAKE_VERSION(0, 1, 0),
+ .apiVersion = version,
+ };
+ const VkInstanceCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .pApplicationInfo = &application_info,
+ .enabledLayerCount = layers.size(),
+ .ppEnabledLayerNames = layers.data(),
+ .enabledExtensionCount = extensions.size(),
+ .ppEnabledExtensionNames = extensions.data(),
+ };
VkInstance instance;
if (dld.vkCreateInstance(&ci, nullptr, &instance) != VK_SUCCESS) {
@@ -383,24 +461,26 @@ std::optional<std::vector<VkPhysicalDevice>> Instance::EnumeratePhysicalDevices(
if (dld->vkEnumeratePhysicalDevices(handle, &num, physical_devices.data()) != VK_SUCCESS) {
return std::nullopt;
}
- return physical_devices;
+ SortPhysicalDevices(physical_devices, *dld);
+ return std::make_optional(std::move(physical_devices));
}
DebugCallback Instance::TryCreateDebugCallback(
PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept {
- VkDebugUtilsMessengerCreateInfoEXT ci;
- ci.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
- ci.pNext = nullptr;
- ci.flags = 0;
- ci.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
- ci.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
- ci.pfnUserCallback = callback;
- ci.pUserData = nullptr;
+ const VkDebugUtilsMessengerCreateInfoEXT ci{
+ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .flags = 0,
+ .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
+ .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
+ .pfnUserCallback = callback,
+ .pUserData = nullptr,
+ };
VkDebugUtilsMessengerEXT messenger;
if (dld->vkCreateDebugUtilsMessengerEXT(handle, &ci, nullptr, &messenger) != VK_SUCCESS) {
@@ -409,17 +489,6 @@ DebugCallback Instance::TryCreateDebugCallback(
return DebugCallback(messenger, handle, *dld);
}
-std::vector<VkCheckpointDataNV> Queue::GetCheckpointDataNV(const DeviceDispatch& dld) const {
- if (!dld.vkGetQueueCheckpointDataNV) {
- return {};
- }
- u32 num;
- dld.vkGetQueueCheckpointDataNV(queue, &num, nullptr);
- std::vector<VkCheckpointDataNV> checkpoints(num);
- dld.vkGetQueueCheckpointDataNV(queue, &num, checkpoints.data());
- return checkpoints;
-}
-
void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
}
@@ -442,12 +511,13 @@ DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) c
}
CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLevel level) const {
- VkCommandBufferAllocateInfo ai;
- ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
- ai.pNext = nullptr;
- ai.commandPool = handle;
- ai.level = level;
- ai.commandBufferCount = static_cast<u32>(num_buffers);
+ const VkCommandBufferAllocateInfo ai{
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .commandPool = handle,
+ .level = level,
+ .commandBufferCount = static_cast<u32>(num_buffers),
+ };
std::unique_ptr buffers = std::make_unique<VkCommandBuffer[]>(num_buffers);
switch (const VkResult result = dld->vkAllocateCommandBuffers(owner, &ai, buffers.get())) {
@@ -469,20 +539,20 @@ std::vector<VkImage> SwapchainKHR::GetImages() const {
}
Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
- Span<const char*> enabled_extensions,
- const VkPhysicalDeviceFeatures2& enabled_features,
+ Span<const char*> enabled_extensions, const void* next,
DeviceDispatch& dld) noexcept {
- VkDeviceCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
- ci.pNext = &enabled_features;
- ci.flags = 0;
- ci.queueCreateInfoCount = queues_ci.size();
- ci.pQueueCreateInfos = queues_ci.data();
- ci.enabledLayerCount = 0;
- ci.ppEnabledLayerNames = nullptr;
- ci.enabledExtensionCount = enabled_extensions.size();
- ci.ppEnabledExtensionNames = enabled_extensions.data();
- ci.pEnabledFeatures = nullptr;
+ const VkDeviceCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = next,
+ .flags = 0,
+ .queueCreateInfoCount = queues_ci.size(),
+ .pQueueCreateInfos = queues_ci.data(),
+ .enabledLayerCount = 0,
+ .ppEnabledLayerNames = nullptr,
+ .enabledExtensionCount = enabled_extensions.size(),
+ .ppEnabledExtensionNames = enabled_extensions.data(),
+ .pEnabledFeatures = nullptr,
+ };
VkDevice device;
if (dld.vkCreateDevice(physical_device, &ci, nullptr, &device) != VK_SUCCESS) {
@@ -523,11 +593,15 @@ ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const {
}
Semaphore Device::CreateSemaphore() const {
- VkSemaphoreCreateInfo ci;
- ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- ci.pNext = nullptr;
- ci.flags = 0;
+ static constexpr VkSemaphoreCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ };
+ return CreateSemaphore(ci);
+}
+Semaphore Device::CreateSemaphore(const VkSemaphoreCreateInfo& ci) const {
VkSemaphore object;
Check(dld->vkCreateSemaphore(handle, &ci, nullptr, &object));
return Semaphore(object, handle, *dld);
@@ -613,6 +687,18 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons
return ShaderModule(object, handle, *dld);
}
+Event Device::CreateEvent() const {
+ static constexpr VkEventCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ };
+
+ VkEvent object;
+ Check(dld->vkCreateEvent(handle, &ci, nullptr, &object));
+ return Event(object, handle, *dld);
+}
+
SwapchainKHR Device::CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const {
VkSwapchainKHR object;
Check(dld->vkCreateSwapchainKHR(handle, &ci, nullptr, &object));
@@ -701,8 +787,7 @@ bool PhysicalDevice::GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR s
return supported == VK_TRUE;
}
-VkSurfaceCapabilitiesKHR PhysicalDevice::GetSurfaceCapabilitiesKHR(VkSurfaceKHR surface) const
- noexcept {
+VkSurfaceCapabilitiesKHR PhysicalDevice::GetSurfaceCapabilitiesKHR(VkSurfaceKHR surface) const {
VkSurfaceCapabilitiesKHR capabilities;
Check(dld->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities));
return capabilities;
@@ -733,6 +818,21 @@ VkPhysicalDeviceMemoryProperties PhysicalDevice::GetMemoryProperties() const noe
return properties;
}
+u32 AvailableVersion(const InstanceDispatch& dld) noexcept {
+ PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
+ if (!Proc(vkEnumerateInstanceVersion, dld, "vkEnumerateInstanceVersion")) {
+ // If the procedure is not found, Vulkan 1.0 is assumed
+ return VK_API_VERSION_1_0;
+ }
+ u32 version;
+ if (const VkResult result = vkEnumerateInstanceVersion(&version); result != VK_SUCCESS) {
+ LOG_ERROR(Render_Vulkan, "vkEnumerateInstanceVersion returned {}, assuming Vulkan 1.1",
+ ToString(result));
+ return VK_API_VERSION_1_1;
+ }
+ return version;
+}
+
std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
const InstanceDispatch& dld) {
u32 num;
@@ -747,4 +847,17 @@ std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProp
return properties;
}
+std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
+ const InstanceDispatch& dld) {
+ u32 num;
+ if (dld.vkEnumerateInstanceLayerProperties(&num, nullptr) != VK_SUCCESS) {
+ return std::nullopt;
+ }
+ std::vector<VkLayerProperties> properties(num);
+ if (dld.vkEnumerateInstanceLayerProperties(&num, properties.data()) != VK_SUCCESS) {
+ return std::nullopt;
+ }
+ return properties;
+}
+
} // namespace Vulkan::vk
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
index fb3657819..f64919623 100644
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ b/src/video_core/renderer_vulkan/wrapper.h
@@ -141,6 +141,7 @@ struct InstanceDispatch {
PFN_vkCreateInstance vkCreateInstance;
PFN_vkDestroyInstance vkDestroyInstance;
PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
+ PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
PFN_vkCreateDevice vkCreateDevice;
@@ -197,14 +198,25 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
PFN_vkCmdPushConstants vkCmdPushConstants;
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
- PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV;
PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
+ PFN_vkCmdSetEvent vkCmdSetEvent;
PFN_vkCmdSetScissor vkCmdSetScissor;
PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
PFN_vkCmdSetViewport vkCmdSetViewport;
+ PFN_vkCmdWaitEvents vkCmdWaitEvents;
+ PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
+ PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
+ PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
+ PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
+ PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
+ PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
+ PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
+ PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
+ PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
+ PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
PFN_vkCreateBuffer vkCreateBuffer;
PFN_vkCreateBufferView vkCreateBufferView;
PFN_vkCreateCommandPool vkCreateCommandPool;
@@ -212,6 +224,7 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR;
+ PFN_vkCreateEvent vkCreateEvent;
PFN_vkCreateFence vkCreateFence;
PFN_vkCreateFramebuffer vkCreateFramebuffer;
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
@@ -230,6 +243,7 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR;
+ PFN_vkDestroyEvent vkDestroyEvent;
PFN_vkDestroyFence vkDestroyFence;
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
PFN_vkDestroyImage vkDestroyImage;
@@ -249,10 +263,11 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkFreeMemory vkFreeMemory;
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
PFN_vkGetDeviceQueue vkGetDeviceQueue;
+ PFN_vkGetEventStatus vkGetEventStatus;
PFN_vkGetFenceStatus vkGetFenceStatus;
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
- PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV;
+ PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR;
PFN_vkMapMemory vkMapMemory;
PFN_vkQueueSubmit vkQueueSubmit;
PFN_vkResetFences vkResetFences;
@@ -261,6 +276,7 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR;
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
PFN_vkWaitForFences vkWaitForFences;
+ PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR;
};
/// Loads instance agnostic function pointers.
@@ -281,6 +297,7 @@ void Destroy(VkDevice, VkDescriptorPool, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkDescriptorSetLayout, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkDescriptorUpdateTemplateKHR, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkDeviceMemory, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkEvent, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkFence, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkFramebuffer, const DeviceDispatch&) noexcept;
void Destroy(VkDevice, VkImage, const DeviceDispatch&) noexcept;
@@ -535,7 +552,6 @@ using PipelineLayout = Handle<VkPipelineLayout, VkDevice, DeviceDispatch>;
using QueryPool = Handle<VkQueryPool, VkDevice, DeviceDispatch>;
using RenderPass = Handle<VkRenderPass, VkDevice, DeviceDispatch>;
using Sampler = Handle<VkSampler, VkDevice, DeviceDispatch>;
-using Semaphore = Handle<VkSemaphore, VkDevice, DeviceDispatch>;
using ShaderModule = Handle<VkShaderModule, VkDevice, DeviceDispatch>;
using SurfaceKHR = Handle<VkSurfaceKHR, VkInstance, InstanceDispatch>;
@@ -548,7 +564,7 @@ class Instance : public Handle<VkInstance, NoOwner, InstanceDispatch> {
public:
/// Creates a Vulkan instance. Use "operator bool" for error handling.
- static Instance Create(Span<const char*> layers, Span<const char*> extensions,
+ static Instance Create(u32 version, Span<const char*> layers, Span<const char*> extensions,
InstanceDispatch& dld) noexcept;
/// Enumerates physical devices.
@@ -567,12 +583,9 @@ public:
/// Construct a queue handle.
constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {}
- /// Returns the checkpoint data.
- /// @note Returns an empty vector when the function pointer is not present.
- std::vector<VkCheckpointDataNV> GetCheckpointDataNV(const DeviceDispatch& dld) const;
-
- void Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const {
- Check(dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence));
+ VkResult Submit(Span<VkSubmitInfo> submit_infos,
+ VkFence fence = VK_NULL_HANDLE) const noexcept {
+ return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
}
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
@@ -654,13 +667,59 @@ public:
std::vector<VkImage> GetImages() const;
};
+class Event : public Handle<VkEvent, VkDevice, DeviceDispatch> {
+ using Handle<VkEvent, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ VkResult GetStatus() const noexcept {
+ return dld->vkGetEventStatus(owner, handle);
+ }
+};
+
+class Semaphore : public Handle<VkSemaphore, VkDevice, DeviceDispatch> {
+ using Handle<VkSemaphore, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ [[nodiscard]] u64 GetCounter() const {
+ u64 value;
+ Check(dld->vkGetSemaphoreCounterValueKHR(owner, handle, &value));
+ return value;
+ }
+
+ /**
+ * Waits for a timeline semaphore on the host.
+ *
+ * @param value Value to wait
+ * @param timeout Time in nanoseconds to timeout
+ * @return True on successful wait, false on timeout
+ */
+ bool Wait(u64 value, u64 timeout = std::numeric_limits<u64>::max()) const {
+ const VkSemaphoreWaitInfoKHR wait_info{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .semaphoreCount = 1,
+ .pSemaphores = &handle,
+ .pValues = &value,
+ };
+ const VkResult result = dld->vkWaitSemaphoresKHR(owner, &wait_info, timeout);
+ switch (result) {
+ case VK_SUCCESS:
+ return true;
+ case VK_TIMEOUT:
+ return false;
+ default:
+ throw Exception(result);
+ }
+ }
+};
+
class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> {
using Handle<VkDevice, NoOwner, DeviceDispatch>::Handle;
public:
static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
- Span<const char*> enabled_extensions,
- const VkPhysicalDeviceFeatures2& enabled_features,
+ Span<const char*> enabled_extensions, const void* next,
DeviceDispatch& dld) noexcept;
Queue GetQueue(u32 family_index) const noexcept;
@@ -675,6 +734,8 @@ public:
Semaphore CreateSemaphore() const;
+ Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const;
+
Fence CreateFence(const VkFenceCreateInfo& ci) const;
DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const;
@@ -702,6 +763,8 @@ public:
ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
+ Event CreateEvent() const;
+
SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
DeviceMemory TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept;
@@ -734,18 +797,11 @@ public:
dld->vkResetQueryPoolEXT(handle, query_pool, first, count);
}
- void GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
- void* data, VkDeviceSize stride, VkQueryResultFlags flags) const {
- Check(dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
- flags));
- }
-
- template <typename T>
- T GetQueryResult(VkQueryPool query_pool, u32 first, VkQueryResultFlags flags) const {
- static_assert(std::is_trivially_copyable_v<T>);
- T value;
- GetQueryResults(query_pool, first, 1, sizeof(T), &value, sizeof(T), flags);
- return value;
+ VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
+ void* data, VkDeviceSize stride,
+ VkQueryResultFlags flags) const noexcept {
+ return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
+ flags);
}
};
@@ -776,7 +832,7 @@ public:
bool GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR) const;
- VkSurfaceCapabilitiesKHR GetSurfaceCapabilitiesKHR(VkSurfaceKHR) const noexcept;
+ VkSurfaceCapabilitiesKHR GetSurfaceCapabilitiesKHR(VkSurfaceKHR) const;
std::vector<VkSurfaceFormatKHR> GetSurfaceFormatsKHR(VkSurfaceKHR) const;
@@ -835,8 +891,8 @@ public:
dld->vkCmdBindPipeline(handle, bind_point, pipeline);
}
- void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) const
- noexcept {
+ void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset,
+ VkIndexType index_type) const noexcept {
dld->vkCmdBindIndexBuffer(handle, buffer, offset, index_type);
}
@@ -849,8 +905,8 @@ public:
BindVertexBuffers(binding, 1, &buffer, &offset);
}
- void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex, u32 first_instance) const
- noexcept {
+ void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex,
+ u32 first_instance) const noexcept {
dld->vkCmdDraw(handle, vertex_count, instance_count, first_vertex, first_instance);
}
@@ -860,15 +916,15 @@ public:
first_instance);
}
- void ClearAttachments(Span<VkClearAttachment> attachments, Span<VkClearRect> rects) const
- noexcept {
+ void ClearAttachments(Span<VkClearAttachment> attachments,
+ Span<VkClearRect> rects) const noexcept {
dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
rects.data());
}
void BlitImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
- VkImageLayout dst_layout, Span<VkImageBlit> regions, VkFilter filter) const
- noexcept {
+ VkImageLayout dst_layout, Span<VkImageBlit> regions,
+ VkFilter filter) const noexcept {
dld->vkCmdBlitImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
regions.data(), filter);
}
@@ -893,8 +949,8 @@ public:
regions.data());
}
- void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, Span<VkBufferCopy> regions) const
- noexcept {
+ void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
+ Span<VkBufferCopy> regions) const noexcept {
dld->vkCmdCopyBuffer(handle, src_buffer, dst_buffer, regions.size(), regions.data());
}
@@ -910,8 +966,8 @@ public:
regions.data());
}
- void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, u32 data) const
- noexcept {
+ void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
+ u32 data) const noexcept {
dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
}
@@ -920,10 +976,6 @@ public:
dld->vkCmdPushConstants(handle, layout, flags, offset, size, values);
}
- void SetCheckpointNV(const void* checkpoint_marker) const noexcept {
- dld->vkCmdSetCheckpointNV(handle, checkpoint_marker);
- }
-
void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept {
dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data());
}
@@ -956,6 +1008,63 @@ public:
dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
}
+ void SetEvent(VkEvent event, VkPipelineStageFlags stage_flags) const noexcept {
+ dld->vkCmdSetEvent(handle, event, stage_flags);
+ }
+
+ void WaitEvents(Span<VkEvent> events, VkPipelineStageFlags src_stage_mask,
+ VkPipelineStageFlags dst_stage_mask, Span<VkMemoryBarrier> memory_barriers,
+ Span<VkBufferMemoryBarrier> buffer_barriers,
+ Span<VkImageMemoryBarrier> image_barriers) const noexcept {
+ dld->vkCmdWaitEvents(handle, events.size(), events.data(), src_stage_mask, dst_stage_mask,
+ memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(),
+ buffer_barriers.data(), image_barriers.size(), image_barriers.data());
+ }
+
+ void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
+ const VkDeviceSize* offsets, const VkDeviceSize* sizes,
+ const VkDeviceSize* strides) const noexcept {
+ dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
+ sizes, strides);
+ }
+
+ void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
+ dld->vkCmdSetCullModeEXT(handle, cull_mode);
+ }
+
+ void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
+ }
+
+ void SetDepthTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthWriteEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
+ dld->vkCmdSetFrontFaceEXT(handle, front_face);
+ }
+
+ void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
+ dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
+ }
+
+ void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
+ VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
+ }
+
+ void SetStencilTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
const VkDeviceSize* offsets,
const VkDeviceSize* sizes) const noexcept {
@@ -981,7 +1090,12 @@ private:
const DeviceDispatch* dld;
};
+u32 AvailableVersion(const InstanceDispatch& dld) noexcept;
+
std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
const InstanceDispatch& dld);
+std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
+ const InstanceDispatch& dld);
+
} // namespace Vulkan::vk
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
index cca13bcde..8e5a22ab3 100644
--- a/src/video_core/shader/ast.h
+++ b/src/video_core/shader/ast.h
@@ -199,55 +199,48 @@ public:
}
std::optional<u32> GetGotoLabel() const {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTGoto>(&data)) {
return {inner->label};
}
- return {};
+ return std::nullopt;
}
Expr GetGotoCondition() const {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTGoto>(&data)) {
return inner->condition;
}
return nullptr;
}
void MarkLabelUnused() {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (auto* inner = std::get_if<ASTLabel>(&data)) {
inner->unused = true;
}
}
bool IsLabelUnused() const {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTLabel>(&data)) {
return inner->unused;
}
return true;
}
std::optional<u32> GetLabelIndex() const {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTLabel>(&data)) {
return {inner->index};
}
- return {};
+ return std::nullopt;
}
Expr GetIfCondition() const {
- auto inner = std::get_if<ASTIfThen>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTIfThen>(&data)) {
return inner->condition;
}
return nullptr;
}
void SetGotoCondition(Expr new_condition) {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (auto* inner = std::get_if<ASTGoto>(&data)) {
inner->condition = std::move(new_condition);
}
}
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
new file mode 100644
index 000000000..6920afdf2
--- /dev/null
+++ b/src/video_core/shader/async_shaders.cpp
@@ -0,0 +1,216 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_base.h"
+#include "video_core/renderer_opengl/gl_shader_cache.h"
+#include "video_core/shader/async_shaders.h"
+
+namespace VideoCommon::Shader {
+
+AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window) : emu_window(emu_window) {}
+
+AsyncShaders::~AsyncShaders() {
+ KillWorkers();
+}
+
+void AsyncShaders::AllocateWorkers() {
+ // Use at least one thread
+ u32 num_workers = 1;
+
+ // Deduce how many more threads we can use
+ const u32 thread_count = std::thread::hardware_concurrency();
+ if (thread_count >= 8) {
+ // Increase async workers by 1 for every 2 threads >= 8
+ num_workers += 1 + (thread_count - 8) / 2;
+ }
+
+ // If we already have workers queued, ignore
+ if (num_workers == worker_threads.size()) {
+ return;
+ }
+
+ // If workers already exist, clear them
+ if (!worker_threads.empty()) {
+ FreeWorkers();
+ }
+
+ // Create workers
+ for (std::size_t i = 0; i < num_workers; i++) {
+ context_list.push_back(emu_window.CreateSharedContext());
+ worker_threads.emplace_back(&AsyncShaders::ShaderCompilerThread, this,
+ context_list[i].get());
+ }
+}
+
+void AsyncShaders::FreeWorkers() {
+ // Mark all threads to quit
+ is_thread_exiting.store(true);
+ cv.notify_all();
+ for (auto& thread : worker_threads) {
+ thread.join();
+ }
+ // Clear our shared contexts
+ context_list.clear();
+
+ // Clear our worker threads
+ worker_threads.clear();
+}
+
+void AsyncShaders::KillWorkers() {
+ is_thread_exiting.store(true);
+ for (auto& thread : worker_threads) {
+ thread.detach();
+ }
+ // Clear our shared contexts
+ context_list.clear();
+
+ // Clear our worker threads
+ worker_threads.clear();
+}
+
+bool AsyncShaders::HasWorkQueued() const {
+ return !pending_queue.empty();
+}
+
+bool AsyncShaders::HasCompletedWork() const {
+ std::shared_lock lock{completed_mutex};
+ return !finished_work.empty();
+}
+
+bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const {
+ const auto& regs = gpu.Maxwell3D().regs;
+
+ // If something is using depth, we can assume that games are not rendering anything which will
+ // be used one time.
+ if (regs.zeta_enable) {
+ return true;
+ }
+
+ // If games are using a small index count, we can assume these are full screen quads. Usually
+ // these shaders are only used once for building textures so we can assume they can't be built
+ // async
+ if (regs.index_array.count <= 6 || regs.vertex_buffer.count <= 6) {
+ return false;
+ }
+
+ return true;
+}
+
+std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
+ std::vector<Result> results;
+ {
+ std::unique_lock lock{completed_mutex};
+ results = std::move(finished_work);
+ finished_work.clear();
+ }
+ return results;
+}
+
+void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
+ Tegra::Engines::ShaderType shader_type, u64 uid,
+ std::vector<u64> code, std::vector<u64> code_b,
+ u32 main_offset, CompilerSettings compiler_settings,
+ const Registry& registry, VAddr cpu_addr) {
+ std::unique_lock lock(queue_mutex);
+ pending_queue.push({
+ .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL,
+ .device = &device,
+ .shader_type = shader_type,
+ .uid = uid,
+ .code = std::move(code),
+ .code_b = std::move(code_b),
+ .main_offset = main_offset,
+ .compiler_settings = compiler_settings,
+ .registry = registry,
+ .cpu_address = cpu_addr,
+ });
+ cv.notify_one();
+}
+
+void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
+ const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler,
+ Vulkan::VKDescriptorPool& descriptor_pool,
+ Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
+ Vulkan::VKRenderPassCache& renderpass_cache,
+ std::vector<VkDescriptorSetLayoutBinding> bindings,
+ Vulkan::SPIRVProgram program,
+ Vulkan::GraphicsPipelineCacheKey key) {
+ std::unique_lock lock(queue_mutex);
+ pending_queue.push({
+ .backend = Backend::Vulkan,
+ .pp_cache = pp_cache,
+ .vk_device = &device,
+ .scheduler = &scheduler,
+ .descriptor_pool = &descriptor_pool,
+ .update_descriptor_queue = &update_descriptor_queue,
+ .renderpass_cache = &renderpass_cache,
+ .bindings = std::move(bindings),
+ .program = std::move(program),
+ .key = key,
+ });
+ cv.notify_one();
+}
+
+void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) {
+ while (!is_thread_exiting.load(std::memory_order_relaxed)) {
+ std::unique_lock lock{queue_mutex};
+ cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; });
+ if (is_thread_exiting) {
+ return;
+ }
+
+ // Partial lock to allow all threads to read at the same time
+ if (!HasWorkQueued()) {
+ continue;
+ }
+ // Another thread beat us, just unlock and wait for the next load
+ if (pending_queue.empty()) {
+ continue;
+ }
+
+ // Pull work from queue
+ WorkerParams work = std::move(pending_queue.front());
+ pending_queue.pop();
+ lock.unlock();
+
+ if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) {
+ const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry);
+ const auto scope = context->Acquire();
+ auto program =
+ OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry);
+ Result result{};
+ result.backend = work.backend;
+ result.cpu_address = work.cpu_address;
+ result.uid = work.uid;
+ result.code = std::move(work.code);
+ result.code_b = std::move(work.code_b);
+ result.shader_type = work.shader_type;
+
+ if (work.backend == Backend::OpenGL) {
+ result.program.opengl = std::move(program->source_program);
+ } else if (work.backend == Backend::GLASM) {
+ result.program.glasm = std::move(program->assembly_program);
+ }
+
+ {
+ std::unique_lock complete_lock(completed_mutex);
+ finished_work.push_back(std::move(result));
+ }
+ } else if (work.backend == Backend::Vulkan) {
+ auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
+ *work.vk_device, *work.scheduler, *work.descriptor_pool,
+ *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings,
+ work.program);
+
+ work.pp_cache->EmplacePipeline(std::move(pipeline));
+ }
+ }
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
new file mode 100644
index 000000000..7a99e1dc5
--- /dev/null
+++ b/src/video_core/shader/async_shaders.h
@@ -0,0 +1,147 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <shared_mutex>
+#include <thread>
+
+// This header includes both Vulkan and OpenGL headers, this has to be fixed
+// Unfortunately, including OpenGL will include Windows.h that defines macros that can cause issues.
+// Forcefully include glad early and undefine macros
+#include <glad/glad.h>
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#ifdef CreateSemaphore
+#undef CreateSemaphore
+#endif
+
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+
+namespace Core::Frontend {
+class EmuWindow;
+class GraphicsContext;
+} // namespace Core::Frontend
+
+namespace Tegra {
+class GPU;
+}
+
+namespace Vulkan {
+class VKPipelineCache;
+}
+
+namespace VideoCommon::Shader {
+
+class AsyncShaders {
+public:
+ enum class Backend {
+ OpenGL,
+ GLASM,
+ Vulkan,
+ };
+
+ struct ResultPrograms {
+ OpenGL::OGLProgram opengl;
+ OpenGL::OGLAssemblyProgram glasm;
+ };
+
+ struct Result {
+ u64 uid;
+ VAddr cpu_address;
+ Backend backend;
+ ResultPrograms program;
+ std::vector<u64> code;
+ std::vector<u64> code_b;
+ Tegra::Engines::ShaderType shader_type;
+ };
+
+ explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window);
+ ~AsyncShaders();
+
+ /// Start up shader worker threads
+ void AllocateWorkers();
+
+ /// Clear the shader queue and kill all worker threads
+ void FreeWorkers();
+
+ // Force end all threads
+ void KillWorkers();
+
+ /// Check to see if any shaders have actually been compiled
+ [[nodiscard]] bool HasCompletedWork() const;
+
+ /// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build
+ /// every shader async as some shaders are only built and executed once. We try to "guess" which
+ /// shader would be used only once
+ [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const;
+
+ /// Pulls completed compiled shaders
+ [[nodiscard]] std::vector<Result> GetCompletedWork();
+
+ void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type,
+ u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset,
+ CompilerSettings compiler_settings, const Registry& registry,
+ VAddr cpu_addr);
+
+ void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device,
+ Vulkan::VKScheduler& scheduler,
+ Vulkan::VKDescriptorPool& descriptor_pool,
+ Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
+ Vulkan::VKRenderPassCache& renderpass_cache,
+ std::vector<VkDescriptorSetLayoutBinding> bindings,
+ Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key);
+
+private:
+ void ShaderCompilerThread(Core::Frontend::GraphicsContext* context);
+
+ /// Check our worker queue to see if we have any work queued already
+ [[nodiscard]] bool HasWorkQueued() const;
+
+ struct WorkerParams {
+ Backend backend;
+ // For OGL
+ const OpenGL::Device* device;
+ Tegra::Engines::ShaderType shader_type;
+ u64 uid;
+ std::vector<u64> code;
+ std::vector<u64> code_b;
+ u32 main_offset;
+ CompilerSettings compiler_settings;
+ std::optional<Registry> registry;
+ VAddr cpu_address;
+
+ // For Vulkan
+ Vulkan::VKPipelineCache* pp_cache;
+ const Vulkan::VKDevice* vk_device;
+ Vulkan::VKScheduler* scheduler;
+ Vulkan::VKDescriptorPool* descriptor_pool;
+ Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue;
+ Vulkan::VKRenderPassCache* renderpass_cache;
+ std::vector<VkDescriptorSetLayoutBinding> bindings;
+ Vulkan::SPIRVProgram program;
+ Vulkan::GraphicsPipelineCacheKey key;
+ };
+
+ std::condition_variable cv;
+ mutable std::mutex queue_mutex;
+ mutable std::shared_mutex completed_mutex;
+ std::atomic<bool> is_thread_exiting{};
+ std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list;
+ std::vector<std::thread> worker_threads;
+ std::queue<WorkerParams> pending_queue;
+ std::vector<Result> finished_work;
+ Core::Frontend::EmuWindow& emu_window;
+};
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index 2e2711350..4c8971615 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -13,6 +13,7 @@
#include "common/common_types.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/control_flow.h"
+#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
@@ -115,17 +116,6 @@ Pred GetPredicate(u32 index, bool negated) {
return static_cast<Pred>(static_cast<u64>(index) + (negated ? 8ULL : 0ULL));
}
-/**
- * Returns whether the instruction at the specified offset is a 'sched' instruction.
- * Sched instructions always appear before a sequence of 3 instructions.
- */
-constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
- constexpr u32 SchedPeriod = 4;
- u32 absolute_offset = offset - main_offset;
-
- return (absolute_offset % SchedPeriod) == 0;
-}
-
enum class ParseResult : u32 {
ControlCaught,
BlockEnd,
@@ -197,24 +187,26 @@ std::optional<std::pair<BufferInfo, u64>> TrackLDC(const CFGRebuildState& state,
std::optional<u64> TrackSHLRegister(const CFGRebuildState& state, u32& pos,
u64 ldc_tracked_register) {
- return TrackInstruction<u64>(state, pos,
- [ldc_tracked_register](auto instr, const auto& opcode) {
- return opcode.GetId() == OpCode::Id::SHL_IMM &&
- instr.gpr0.Value() == ldc_tracked_register;
- },
- [](auto instr, const auto&) { return instr.gpr8.Value(); });
+ return TrackInstruction<u64>(
+ state, pos,
+ [ldc_tracked_register](auto instr, const auto& opcode) {
+ return opcode.GetId() == OpCode::Id::SHL_IMM &&
+ instr.gpr0.Value() == ldc_tracked_register;
+ },
+ [](auto instr, const auto&) { return instr.gpr8.Value(); });
}
std::optional<u32> TrackIMNMXValue(const CFGRebuildState& state, u32& pos,
u64 shl_tracked_register) {
- return TrackInstruction<u32>(state, pos,
- [shl_tracked_register](auto instr, const auto& opcode) {
- return opcode.GetId() == OpCode::Id::IMNMX_IMM &&
- instr.gpr0.Value() == shl_tracked_register;
- },
- [](auto instr, const auto&) {
- return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1);
- });
+ return TrackInstruction<u32>(
+ state, pos,
+ [shl_tracked_register](auto instr, const auto& opcode) {
+ return opcode.GetId() == OpCode::Id::IMNMX_IMM &&
+ instr.gpr0.Value() == shl_tracked_register;
+ },
+ [](auto instr, const auto&) {
+ return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1);
+ });
}
std::optional<BranchIndirectInfo> TrackBranchIndirectInfo(const CFGRebuildState& state, u32 pos) {
@@ -484,17 +476,17 @@ bool TryInspectAddress(CFGRebuildState& state) {
}
case BlockCollision::Inside: {
// This case is the tricky one:
- // We need to Split the block in 2 sepparate blocks
+ // We need to split the block into 2 separate blocks
const u32 end = state.block_info[block_index].end;
BlockInfo& new_block = CreateBlockInfo(state, address, end);
BlockInfo& current_block = state.block_info[block_index];
current_block.end = address - 1;
- new_block.branch = current_block.branch;
+ new_block.branch = std::move(current_block.branch);
BlockBranchInfo forward_branch = MakeBranchInfo<SingleBranch>();
const auto branch = std::get_if<SingleBranch>(forward_branch.get());
branch->address = address;
branch->ignore = true;
- current_block.branch = forward_branch;
+ current_block.branch = std::move(forward_branch);
return true;
}
default:
@@ -555,13 +547,13 @@ bool TryQuery(CFGRebuildState& state) {
gather_labels(q2.ssy_stack, state.ssy_labels, block);
gather_labels(q2.pbk_stack, state.pbk_labels, block);
if (std::holds_alternative<SingleBranch>(*block.branch)) {
- const auto branch = std::get_if<SingleBranch>(block.branch.get());
+ auto* branch = std::get_if<SingleBranch>(block.branch.get());
if (!branch->condition.IsUnconditional()) {
q2.address = block.end + 1;
state.queries.push_back(q2);
}
- Query conditional_query{q2};
+ auto& conditional_query = state.queries.emplace_back(q2);
if (branch->is_sync) {
if (branch->address == unassigned_branch) {
branch->address = conditional_query.ssy_stack.top();
@@ -575,23 +567,21 @@ bool TryQuery(CFGRebuildState& state) {
conditional_query.pbk_stack.pop();
}
conditional_query.address = branch->address;
- state.queries.push_back(std::move(conditional_query));
return true;
}
- const auto multi_branch = std::get_if<MultiBranch>(block.branch.get());
+
+ const auto* multi_branch = std::get_if<MultiBranch>(block.branch.get());
for (const auto& branch_case : multi_branch->branches) {
- Query conditional_query{q2};
+ auto& conditional_query = state.queries.emplace_back(q2);
conditional_query.address = branch_case.address;
- state.queries.push_back(std::move(conditional_query));
}
+
return true;
}
-} // Anonymous namespace
-
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
- const auto get_expr = ([&](const Condition& cond) -> Expr {
- Expr result{};
+ const auto get_expr = [](const Condition& cond) -> Expr {
+ Expr result;
if (cond.cc != ConditionCode::T) {
result = MakeExpr<ExprCondCode>(cond.cc);
}
@@ -604,10 +594,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
}
Expr extra = MakeExpr<ExprPredicate>(pred);
if (negate) {
- extra = MakeExpr<ExprNot>(extra);
+ extra = MakeExpr<ExprNot>(std::move(extra));
}
if (result) {
- return MakeExpr<ExprAnd>(extra, result);
+ return MakeExpr<ExprAnd>(std::move(extra), std::move(result));
}
return extra;
}
@@ -615,9 +605,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
return result;
}
return MakeExpr<ExprBoolean>(true);
- });
+ };
+
if (std::holds_alternative<SingleBranch>(*branch_info)) {
- const auto branch = std::get_if<SingleBranch>(branch_info.get());
+ const auto* branch = std::get_if<SingleBranch>(branch_info.get());
if (branch->address < 0) {
if (branch->kill) {
mm.InsertReturn(get_expr(branch->condition), true);
@@ -629,7 +620,7 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
mm.InsertGoto(get_expr(branch->condition), branch->address);
return;
}
- const auto multi_branch = std::get_if<MultiBranch>(branch_info.get());
+ const auto* multi_branch = std::get_if<MultiBranch>(branch_info.get());
for (const auto& branch_case : multi_branch->branches) {
mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value),
branch_case.address);
@@ -655,6 +646,8 @@ void DecompileShader(CFGRebuildState& state) {
state.manager->Decompile();
}
+} // Anonymous namespace
+
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 start_address,
const CompilerSettings& settings,
Registry& registry) {
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index 87ac9ac6c..eeac328a6 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -13,6 +13,7 @@
#include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h"
#include "video_core/shader/control_flow.h"
+#include "video_core/shader/memory_util.h"
#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
@@ -23,17 +24,6 @@ using Tegra::Shader::OpCode;
namespace {
-/**
- * Returns whether the instruction at the specified offset is a 'sched' instruction.
- * Sched instructions always appear before a sequence of 3 instructions.
- */
-constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
- constexpr u32 SchedPeriod = 4;
- u32 absolute_offset = offset - main_offset;
-
- return (absolute_offset % SchedPeriod) == 0;
-}
-
void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver,
const std::list<Sampler>& used_samplers) {
if (gpu_driver.IsTextureHandlerSizeKnown() || used_samplers.size() <= 1) {
@@ -42,11 +32,11 @@ void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver,
u32 count{};
std::vector<u32> bound_offsets;
for (const auto& sampler : used_samplers) {
- if (sampler.IsBindless()) {
+ if (sampler.is_bindless) {
continue;
}
++count;
- bound_offsets.emplace_back(sampler.GetOffset());
+ bound_offsets.emplace_back(sampler.offset);
}
if (count > 1) {
gpu_driver.DeduceTextureHandlerSize(std::move(bound_offsets));
@@ -56,14 +46,14 @@ void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver,
std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce,
VideoCore::GuestDriverProfile& gpu_driver,
const std::list<Sampler>& used_samplers) {
- const u32 base_offset = sampler_to_deduce.GetOffset();
+ const u32 base_offset = sampler_to_deduce.offset;
u32 max_offset{std::numeric_limits<u32>::max()};
for (const auto& sampler : used_samplers) {
- if (sampler.IsBindless()) {
+ if (sampler.is_bindless) {
continue;
}
- if (sampler.GetOffset() > base_offset) {
- max_offset = std::min(sampler.GetOffset(), max_offset);
+ if (sampler.offset > base_offset) {
+ max_offset = std::min(sampler.offset, max_offset);
}
}
if (max_offset == std::numeric_limits<u32>::max()) {
@@ -265,7 +255,7 @@ void ShaderIR::InsertControlFlow(NodeBlock& bb, const ShaderBlock& block) {
Node n = Operation(OperationCode::Branch, Immediate(branch_case.address));
Node op_b = Immediate(branch_case.cmp_value);
Node condition =
- GetPredicateComparisonInteger(Tegra::Shader::PredCondition::Equal, false, op_a, op_b);
+ GetPredicateComparisonInteger(Tegra::Shader::PredCondition::EQ, false, op_a, op_b);
auto result = Conditional(condition, {n});
bb.push_back(result);
global_code.push_back(result);
@@ -363,14 +353,14 @@ void ShaderIR::PostDecode() {
return;
}
for (auto& sampler : used_samplers) {
- if (!sampler.IsIndexed()) {
+ if (!sampler.is_indexed) {
continue;
}
if (const auto size = TryDeduceSamplerSize(sampler, gpu_driver, used_samplers)) {
- sampler.SetSize(*size);
+ sampler.size = *size;
} else {
LOG_CRITICAL(HW_GPU, "Failed to deduce size of indexed sampler");
- sampler.SetSize(1);
+ sampler.size = 1;
}
}
}
diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp
index 4db329fa5..afef5948d 100644
--- a/src/video_core/shader/decode/arithmetic.cpp
+++ b/src/video_core/shader/decode/arithmetic.cpp
@@ -137,7 +137,8 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::FCMP_RR:
- case OpCode::Id::FCMP_RC: {
+ case OpCode::Id::FCMP_RC:
+ case OpCode::Id::FCMP_IMMR: {
UNIMPLEMENTED_IF(instr.fcmp.ftz == 0);
Node op_c = GetRegister(instr.gpr39);
Node comp = GetPredicateComparisonFloat(instr.fcmp.cond, std::move(op_c), Immediate(0.0f));
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index ee7d9a29d..88103fede 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -19,22 +19,49 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- if (opcode->get().GetId() == OpCode::Id::HADD2_C ||
- opcode->get().GetId() == OpCode::Id::HADD2_R) {
+ bool negate_a = false;
+ bool negate_b = false;
+ bool absolute_a = false;
+ bool absolute_b = false;
+
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HADD2_R:
if (instr.alu_half.ftz == 0) {
LOG_DEBUG(HW_GPU, "{} without FTZ is not implemented", opcode->get().GetName());
}
+ negate_a = ((instr.value >> 43) & 1) != 0;
+ negate_b = ((instr.value >> 31) & 1) != 0;
+ absolute_a = ((instr.value >> 44) & 1) != 0;
+ absolute_b = ((instr.value >> 30) & 1) != 0;
+ break;
+ case OpCode::Id::HADD2_C:
+ if (instr.alu_half.ftz == 0) {
+ LOG_DEBUG(HW_GPU, "{} without FTZ is not implemented", opcode->get().GetName());
+ }
+ negate_a = ((instr.value >> 43) & 1) != 0;
+ negate_b = ((instr.value >> 56) & 1) != 0;
+ absolute_a = ((instr.value >> 44) & 1) != 0;
+ absolute_b = ((instr.value >> 54) & 1) != 0;
+ break;
+ case OpCode::Id::HMUL2_R:
+ negate_a = ((instr.value >> 43) & 1) != 0;
+ absolute_a = ((instr.value >> 44) & 1) != 0;
+ absolute_b = ((instr.value >> 30) & 1) != 0;
+ break;
+ case OpCode::Id::HMUL2_C:
+ negate_b = ((instr.value >> 31) & 1) != 0;
+ absolute_a = ((instr.value >> 44) & 1) != 0;
+ absolute_b = ((instr.value >> 54) & 1) != 0;
+ break;
+ default:
+ UNREACHABLE();
+ break;
}
- const bool negate_a =
- opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
- const bool negate_b =
- opcode->get().GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
-
Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
- op_a = GetOperandAbsNegHalf(op_a, instr.alu_half.abs_a, negate_a);
+ op_a = GetOperandAbsNegHalf(op_a, absolute_a, negate_a);
- auto [type_b, op_b] = [&]() -> std::tuple<HalfType, Node> {
+ auto [type_b, op_b] = [this, instr, opcode]() -> std::pair<HalfType, Node> {
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_C:
case OpCode::Id::HMUL2_C:
@@ -48,17 +75,16 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
}
}();
op_b = UnpackHalfFloat(op_b, type_b);
- // redeclaration to avoid a bug in clang with reusing local bindings in lambdas
- Node op_b_alt = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b);
+ op_b = GetOperandAbsNegHalf(op_b, absolute_b, negate_b);
- Node value = [&]() {
+ Node value = [this, opcode, op_a, op_b = op_b] {
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_C:
case OpCode::Id::HADD2_R:
- return Operation(OperationCode::HAdd, PRECISE, op_a, op_b_alt);
+ return Operation(OperationCode::HAdd, PRECISE, op_a, op_b);
case OpCode::Id::HMUL2_C:
case OpCode::Id::HMUL2_R:
- return Operation(OperationCode::HMul, PRECISE, op_a, op_b_alt);
+ return Operation(OperationCode::HMul, PRECISE, op_a, op_b);
default:
UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", opcode->get().GetName());
return Immediate(0);
diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp
index 0f4c3103a..73155966f 100644
--- a/src/video_core/shader/decode/arithmetic_integer.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer.cpp
@@ -35,15 +35,38 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
case OpCode::Id::IADD_C:
case OpCode::Id::IADD_R:
case OpCode::Id::IADD_IMM: {
- UNIMPLEMENTED_IF_MSG(instr.alu.saturate_d, "IADD saturation not implemented");
+ UNIMPLEMENTED_IF_MSG(instr.alu.saturate_d, "IADD.SAT");
+ UNIMPLEMENTED_IF_MSG(instr.iadd.x && instr.generates_cc, "IADD.X Rd.CC");
op_a = GetOperandAbsNegInteger(op_a, false, instr.alu_integer.negate_a, true);
op_b = GetOperandAbsNegInteger(op_b, false, instr.alu_integer.negate_b, true);
- const Node value = Operation(OperationCode::IAdd, PRECISE, op_a, op_b);
+ Node value = Operation(OperationCode::UAdd, op_a, op_b);
- SetInternalFlagsFromInteger(bb, value, instr.generates_cc);
- SetRegister(bb, instr.gpr0, value);
+ if (instr.iadd.x) {
+ Node carry = GetInternalFlag(InternalFlag::Carry);
+ Node x = Operation(OperationCode::Select, std::move(carry), Immediate(1), Immediate(0));
+ value = Operation(OperationCode::UAdd, std::move(value), std::move(x));
+ }
+
+ if (instr.generates_cc) {
+ const Node i0 = Immediate(0);
+
+ Node zero = Operation(OperationCode::LogicalIEqual, value, i0);
+ Node sign = Operation(OperationCode::LogicalILessThan, value, i0);
+ Node carry = Operation(OperationCode::LogicalAddCarry, op_a, op_b);
+
+ Node pos_a = Operation(OperationCode::LogicalIGreaterThan, op_a, i0);
+ Node pos_b = Operation(OperationCode::LogicalIGreaterThan, op_b, i0);
+ Node pos = Operation(OperationCode::LogicalAnd, std::move(pos_a), std::move(pos_b));
+ Node overflow = Operation(OperationCode::LogicalAnd, pos, sign);
+
+ SetInternalFlag(bb, InternalFlag::Zero, std::move(zero));
+ SetInternalFlag(bb, InternalFlag::Sign, std::move(sign));
+ SetInternalFlag(bb, InternalFlag::Carry, std::move(carry));
+ SetInternalFlag(bb, InternalFlag::Overflow, std::move(overflow));
+ }
+ SetRegister(bb, instr.gpr0, std::move(value));
break;
}
case OpCode::Id::IADD3_C:
@@ -75,12 +98,12 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
op_b = GetOperandAbsNegInteger(op_b, false, instr.iadd3.neg_b, true);
op_c = GetOperandAbsNegInteger(op_c, false, instr.iadd3.neg_c, true);
- const Node value = [&]() {
- const Node add_ab = Operation(OperationCode::IAdd, NO_PRECISE, op_a, op_b);
+ const Node value = [&] {
+ Node add_ab = Operation(OperationCode::IAdd, NO_PRECISE, op_a, op_b);
if (opcode->get().GetId() != OpCode::Id::IADD3_R) {
return Operation(OperationCode::IAdd, NO_PRECISE, add_ab, op_c);
}
- const Node shifted = [&]() {
+ const Node shifted = [&] {
switch (instr.iadd3.mode) {
case Tegra::Shader::IAdd3Mode::RightShift:
// TODO(tech4me): According to
@@ -249,8 +272,8 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
}
case OpCode::Id::LEA_IMM: {
const bool neg = instr.lea.imm.neg != 0;
- return {Immediate(static_cast<u32>(instr.lea.imm.entry_a)),
- GetOperandAbsNegInteger(GetRegister(instr.gpr8), false, neg, true),
+ return {GetOperandAbsNegInteger(GetRegister(instr.gpr8), false, neg, true),
+ Immediate(static_cast<u32>(instr.lea.imm.entry_a)),
Immediate(static_cast<u32>(instr.lea.imm.entry_b))};
}
case OpCode::Id::LEA_RZ: {
diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
index 73880db0e..2a30aab2b 100644
--- a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
@@ -28,23 +28,26 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) {
case OpCode::Id::IADD32I: {
UNIMPLEMENTED_IF_MSG(instr.iadd32i.saturate, "IADD32I saturation is not implemented");
- op_a = GetOperandAbsNegInteger(op_a, false, instr.iadd32i.negate_a, true);
+ op_a = GetOperandAbsNegInteger(std::move(op_a), false, instr.iadd32i.negate_a != 0, true);
- const Node value = Operation(OperationCode::IAdd, PRECISE, op_a, op_b);
+ Node value = Operation(OperationCode::IAdd, PRECISE, std::move(op_a), std::move(op_b));
- SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc);
- SetRegister(bb, instr.gpr0, value);
+ SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc != 0);
+ SetRegister(bb, instr.gpr0, std::move(value));
break;
}
case OpCode::Id::LOP32I: {
- if (instr.alu.lop32i.invert_a)
- op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_a);
+ if (instr.alu.lop32i.invert_a) {
+ op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_a));
+ }
- if (instr.alu.lop32i.invert_b)
- op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_b);
+ if (instr.alu.lop32i.invert_b) {
+ op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_b));
+ }
- WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, op_a, op_b,
- PredicateResultMode::None, Pred::UnusedIndex, instr.op_32.generates_cc);
+ WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, std::move(op_a),
+ std::move(op_b), PredicateResultMode::None, Pred::UnusedIndex,
+ instr.op_32.generates_cc != 0);
break;
}
default:
@@ -58,14 +61,14 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) {
void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation logic_op, Node op_a,
Node op_b, PredicateResultMode predicate_mode, Pred predicate,
bool sets_cc) {
- const Node result = [&]() {
+ Node result = [&] {
switch (logic_op) {
case LogicOperation::And:
- return Operation(OperationCode::IBitwiseAnd, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseAnd, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::Or:
- return Operation(OperationCode::IBitwiseOr, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseOr, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::Xor:
- return Operation(OperationCode::IBitwiseXor, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseXor, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::PassB:
return op_b;
default:
@@ -84,8 +87,8 @@ void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation
return;
case PredicateResultMode::NotZero: {
// Set the predicate to true if the result is not zero.
- const Node compare = Operation(OperationCode::LogicalINotEqual, result, Immediate(0));
- SetPredicate(bb, static_cast<u64>(predicate), compare);
+ Node compare = Operation(OperationCode::LogicalINotEqual, std::move(result), Immediate(0));
+ SetPredicate(bb, static_cast<u64>(predicate), std::move(compare));
break;
}
default:
diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp
index 848e46874..b2e88fa20 100644
--- a/src/video_core/shader/decode/half_set.cpp
+++ b/src/video_core/shader/decode/half_set.cpp
@@ -13,55 +13,101 @@
namespace VideoCommon::Shader {
+using std::move;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
+using Tegra::Shader::PredCondition;
u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- if (instr.hset2.ftz == 0) {
- LOG_DEBUG(HW_GPU, "{} without FTZ is not implemented", opcode->get().GetName());
+ PredCondition cond;
+ bool bf;
+ bool ftz;
+ bool neg_a;
+ bool abs_a;
+ bool neg_b;
+ bool abs_b;
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HSET2_C:
+ case OpCode::Id::HSET2_IMM:
+ cond = instr.hsetp2.cbuf_and_imm.cond;
+ bf = instr.Bit(53);
+ ftz = instr.Bit(54);
+ neg_a = instr.Bit(43);
+ abs_a = instr.Bit(44);
+ neg_b = instr.Bit(56);
+ abs_b = instr.Bit(54);
+ break;
+ case OpCode::Id::HSET2_R:
+ cond = instr.hsetp2.reg.cond;
+ bf = instr.Bit(49);
+ ftz = instr.Bit(50);
+ neg_a = instr.Bit(43);
+ abs_a = instr.Bit(44);
+ neg_b = instr.Bit(31);
+ abs_b = instr.Bit(30);
+ break;
+ default:
+ UNREACHABLE();
}
- Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hset2.type_a);
- op_a = GetOperandAbsNegHalf(op_a, instr.hset2.abs_a, instr.hset2.negate_a);
-
- Node op_b = [&]() {
+ Node op_b = [this, instr, opcode] {
switch (opcode->get().GetId()) {
+ case OpCode::Id::HSET2_C:
+ // Inform as unimplemented as this is not tested.
+ UNIMPLEMENTED_MSG("HSET2_C is not implemented");
+ return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
case OpCode::Id::HSET2_R:
return GetRegister(instr.gpr20);
+ case OpCode::Id::HSET2_IMM:
+ return UnpackHalfImmediate(instr, true);
default:
UNREACHABLE();
- return Immediate(0);
+ return Node{};
}
}();
- op_b = UnpackHalfFloat(op_b, instr.hset2.type_b);
- op_b = GetOperandAbsNegHalf(op_b, instr.hset2.abs_b, instr.hset2.negate_b);
- const Node second_pred = GetPredicate(instr.hset2.pred39, instr.hset2.neg_pred);
+ if (!ftz) {
+ LOG_DEBUG(HW_GPU, "{} without FTZ is not implemented", opcode->get().GetName());
+ }
+
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hset2.type_a);
+ op_a = GetOperandAbsNegHalf(op_a, abs_a, neg_a);
+
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HSET2_R:
+ op_b = GetOperandAbsNegHalf(move(op_b), abs_b, neg_b);
+ [[fallthrough]];
+ case OpCode::Id::HSET2_C:
+ op_b = UnpackHalfFloat(move(op_b), instr.hset2.type_b);
+ break;
+ default:
+ break;
+ }
- const Node comparison_pair = GetPredicateComparisonHalf(instr.hset2.cond, op_a, op_b);
+ Node second_pred = GetPredicate(instr.hset2.pred39, instr.hset2.neg_pred);
+
+ Node comparison_pair = GetPredicateComparisonHalf(cond, op_a, op_b);
const OperationCode combiner = GetPredicateCombiner(instr.hset2.op);
// HSET2 operates on each half float in the pack.
std::array<Node, 2> values;
for (u32 i = 0; i < 2; ++i) {
- const u32 raw_value = instr.hset2.bf ? 0x3c00 : 0xffff;
- const Node true_value = Immediate(raw_value << (i * 16));
- const Node false_value = Immediate(0);
-
- const Node comparison =
- Operation(OperationCode::LogicalPick2, comparison_pair, Immediate(i));
- const Node predicate = Operation(combiner, comparison, second_pred);
+ const u32 raw_value = bf ? 0x3c00 : 0xffff;
+ Node true_value = Immediate(raw_value << (i * 16));
+ Node false_value = Immediate(0);
+ Node comparison = Operation(OperationCode::LogicalPick2, comparison_pair, Immediate(i));
+ Node predicate = Operation(combiner, comparison, second_pred);
values[i] =
- Operation(OperationCode::Select, NO_PRECISE, predicate, true_value, false_value);
+ Operation(OperationCode::Select, predicate, move(true_value), move(false_value));
}
- const Node value = Operation(OperationCode::UBitwiseOr, NO_PRECISE, values[0], values[1]);
- SetRegister(bb, instr.gpr0, value);
+ Node value = Operation(OperationCode::UBitwiseOr, values[0], values[1]);
+ SetRegister(bb, instr.gpr0, move(value));
return pc;
}
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index 08ebca38b..1ed4212ee 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -31,11 +31,11 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
std::size_t component) {
const TextureFormat format{descriptor.format};
switch (format) {
- case TextureFormat::R16_G16_B16_A16:
- case TextureFormat::R32_G32_B32_A32:
- case TextureFormat::R32_G32_B32:
- case TextureFormat::R32_G32:
- case TextureFormat::R16_G16:
+ case TextureFormat::R16G16B16A16:
+ case TextureFormat::R32G32B32A32:
+ case TextureFormat::R32G32B32:
+ case TextureFormat::R32G32:
+ case TextureFormat::R16G16:
case TextureFormat::R32:
case TextureFormat::R16:
case TextureFormat::R8:
@@ -97,6 +97,7 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
break;
case TextureFormat::B5G6R5:
case TextureFormat::B6G5R5:
+ case TextureFormat::B10G11R11:
if (component == 0) {
return descriptor.b_type;
}
@@ -107,9 +108,9 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
return descriptor.r_type;
}
break;
- case TextureFormat::G8R24:
- case TextureFormat::G24R8:
- case TextureFormat::G8R8:
+ case TextureFormat::R24G8:
+ case TextureFormat::R8G24:
+ case TextureFormat::R8G8:
case TextureFormat::G4R4:
if (component == 0) {
return descriptor.g_type;
@@ -118,6 +119,8 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
return descriptor.r_type;
}
break;
+ default:
+ break;
}
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return ComponentType::FLOAT;
@@ -136,15 +139,15 @@ bool IsComponentEnabled(std::size_t component_mask, std::size_t component) {
u32 GetComponentSize(TextureFormat format, std::size_t component) {
switch (format) {
- case TextureFormat::R32_G32_B32_A32:
+ case TextureFormat::R32G32B32A32:
return 32;
- case TextureFormat::R16_G16_B16_A16:
+ case TextureFormat::R16G16B16A16:
return 16;
- case TextureFormat::R32_G32_B32:
+ case TextureFormat::R32G32B32:
return component <= 2 ? 32 : 0;
- case TextureFormat::R32_G32:
+ case TextureFormat::R32G32:
return component <= 1 ? 32 : 0;
- case TextureFormat::R16_G16:
+ case TextureFormat::R16G16:
return component <= 1 ? 16 : 0;
case TextureFormat::R32:
return component == 0 ? 32 : 0;
@@ -191,7 +194,15 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
return 6;
}
return 0;
- case TextureFormat::G8R24:
+ case TextureFormat::B10G11R11:
+ if (component == 1 || component == 2) {
+ return 11;
+ }
+ if (component == 0) {
+ return 10;
+ }
+ return 0;
+ case TextureFormat::R24G8:
if (component == 0) {
return 8;
}
@@ -199,7 +210,7 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
return 24;
}
return 0;
- case TextureFormat::G24R8:
+ case TextureFormat::R8G24:
if (component == 0) {
return 24;
}
@@ -207,7 +218,7 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
return 8;
}
return 0;
- case TextureFormat::G8R8:
+ case TextureFormat::R8G8:
return (component == 0 || component == 1) ? 8 : 0;
case TextureFormat::G4R4:
return (component == 0 || component == 1) ? 4 : 0;
@@ -223,24 +234,25 @@ std::size_t GetImageComponentMask(TextureFormat format) {
constexpr u8 B = 0b0100;
constexpr u8 A = 0b1000;
switch (format) {
- case TextureFormat::R32_G32_B32_A32:
- case TextureFormat::R16_G16_B16_A16:
+ case TextureFormat::R32G32B32A32:
+ case TextureFormat::R16G16B16A16:
case TextureFormat::A8R8G8B8:
case TextureFormat::A2B10G10R10:
case TextureFormat::A4B4G4R4:
case TextureFormat::A5B5G5R1:
case TextureFormat::A1B5G5R5:
return std::size_t{R | G | B | A};
- case TextureFormat::R32_G32_B32:
+ case TextureFormat::R32G32B32:
case TextureFormat::R32_B24G8:
case TextureFormat::B5G6R5:
case TextureFormat::B6G5R5:
+ case TextureFormat::B10G11R11:
return std::size_t{R | G | B};
- case TextureFormat::R32_G32:
- case TextureFormat::R16_G16:
- case TextureFormat::G8R24:
- case TextureFormat::G24R8:
- case TextureFormat::G8R8:
+ case TextureFormat::R32G32:
+ case TextureFormat::R16G16:
+ case TextureFormat::R24G8:
+ case TextureFormat::R8G24:
+ case TextureFormat::R8G8:
case TextureFormat::G4R4:
return std::size_t{R | G};
case TextureFormat::R32:
@@ -299,7 +311,7 @@ std::pair<Node, bool> ShaderIR::GetComponentValue(ComponentType component_type,
return {std::move(original_value), true};
}
default:
- UNIMPLEMENTED_MSG("Unimplement component type={}", component_type);
+ UNIMPLEMENTED_MSG("Unimplemented component type={}", component_type);
return {std::move(original_value), true};
}
}
@@ -352,8 +364,10 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
registry.ObtainBoundSampler(static_cast<u32>(instr.image.index.Value()));
} else {
const Node image_register = GetRegister(instr.gpr39);
- const auto [base_image, buffer, offset] = TrackCbuf(
- image_register, global_code, static_cast<s64>(global_code.size()));
+ const auto result = TrackCbuf(image_register, global_code,
+ static_cast<s64>(global_code.size()));
+ const auto buffer = std::get<1>(result);
+ const auto offset = std::get<2>(result);
descriptor = registry.ObtainBindlessSampler(buffer, offset);
}
if (!descriptor) {
@@ -453,11 +467,14 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
return OperationCode::AtomicImageXor;
case Tegra::Shader::ImageAtomicOperation::Exch:
return OperationCode::AtomicImageExchange;
+ default:
+ break;
}
+ break;
default:
break;
}
- UNIMPLEMENTED_MSG("Unimplemented operation={} type={}",
+ UNIMPLEMENTED_MSG("Unimplemented operation={}, type={}",
static_cast<u64>(instr.suatom_d.operation.Value()),
static_cast<u64>(instr.suatom_d.operation_type.Value()));
return OperationCode::AtomicImageAdd;
@@ -483,11 +500,10 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
const auto offset = static_cast<u32>(image.index.Value());
- const auto it =
- std::find_if(std::begin(used_images), std::end(used_images),
- [offset](const Image& entry) { return entry.GetOffset() == offset; });
+ const auto it = std::find_if(std::begin(used_images), std::end(used_images),
+ [offset](const Image& entry) { return entry.offset == offset; });
if (it != std::end(used_images)) {
- ASSERT(!it->IsBindless() && it->GetType() == it->GetType());
+ ASSERT(!it->is_bindless && it->type == type);
return *it;
}
@@ -497,16 +513,18 @@ Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType t
Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
const Node image_register = GetRegister(reg);
- const auto [base_image, buffer, offset] =
+ const auto result =
TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()));
- const auto it =
- std::find_if(std::begin(used_images), std::end(used_images),
- [buffer = buffer, offset = offset](const Image& entry) {
- return entry.GetBuffer() == buffer && entry.GetOffset() == offset;
- });
+ const auto buffer = std::get<1>(result);
+ const auto offset = std::get<2>(result);
+
+ const auto it = std::find_if(std::begin(used_images), std::end(used_images),
+ [buffer, offset](const Image& entry) {
+ return entry.buffer == buffer && entry.offset == offset;
+ });
if (it != std::end(used_images)) {
- ASSERT(it->IsBindless() && it->GetType() == it->GetType());
+ ASSERT(it->is_bindless && it->type == type);
return *it;
}
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 8112ead3e..e2bba88dd 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -386,8 +386,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::RED: {
- UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32);
- UNIMPLEMENTED_IF_MSG(instr.red.operation != AtomicOp::Add);
+ UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32, "type={}",
+ static_cast<int>(instr.red.type.Value()));
const auto [real_address, base_address, descriptor] =
TrackGlobalMemory(bb, instr, true, true);
if (!real_address || !base_address) {
@@ -396,7 +396,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
Node value = GetRegister(instr.gpr0);
- bb.push_back(Operation(OperationCode::ReduceIAdd, move(gmem), move(value)));
+ bb.push_back(Operation(GetAtomOperation(instr.red.operation), move(gmem), move(value)));
break;
}
case OpCode::Id::ATOM: {
@@ -472,14 +472,14 @@ std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackGlobalMemory(NodeBlock&
const auto [base_address, index, offset] =
TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()));
- ASSERT_OR_EXECUTE_MSG(base_address != nullptr,
- { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); },
- "Global memory tracking failed");
+ ASSERT_OR_EXECUTE_MSG(
+ base_address != nullptr, { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); },
+ "Global memory tracking failed");
bb.push_back(Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", index, offset)));
const GlobalMemoryBase descriptor{index, offset};
- const auto& [entry, is_new] = used_global_memory.try_emplace(descriptor);
+ const auto& entry = used_global_memory.try_emplace(descriptor).first;
auto& usage = entry->second;
usage.is_written |= is_write;
usage.is_read |= is_read;
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index d4f95b18c..29a7cfbfe 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -75,15 +75,14 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
const Node value = [this, instr] {
switch (instr.sys20) {
case SystemVariable::LaneId:
- LOG_WARNING(HW_GPU, "S2R instruction with LaneId is incomplete");
- return Immediate(0U);
+ return Operation(OperationCode::ThreadId);
case SystemVariable::InvocationId:
return Operation(OperationCode::InvocationId);
case SystemVariable::Ydirection:
return Operation(OperationCode::YNegate);
case SystemVariable::InvocationInfo:
LOG_WARNING(HW_GPU, "S2R instruction with InvocationInfo is incomplete");
- return Immediate(0U);
+ return Immediate(0x00ff'0000U);
case SystemVariable::WscaleFactorXY:
UNIMPLEMENTED_MSG("S2R WscaleFactorXY is not implemented");
return Immediate(0U);
@@ -109,6 +108,27 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
return Operation(OperationCode::WorkGroupIdY);
case SystemVariable::CtaIdZ:
return Operation(OperationCode::WorkGroupIdZ);
+ case SystemVariable::EqMask:
+ case SystemVariable::LtMask:
+ case SystemVariable::LeMask:
+ case SystemVariable::GtMask:
+ case SystemVariable::GeMask:
+ uses_warps = true;
+ switch (instr.sys20) {
+ case SystemVariable::EqMask:
+ return Operation(OperationCode::ThreadEqMask);
+ case SystemVariable::LtMask:
+ return Operation(OperationCode::ThreadLtMask);
+ case SystemVariable::LeMask:
+ return Operation(OperationCode::ThreadLeMask);
+ case SystemVariable::GtMask:
+ return Operation(OperationCode::ThreadGtMask);
+ case SystemVariable::GeMask:
+ return Operation(OperationCode::ThreadGeMask);
+ default:
+ UNREACHABLE();
+ return Immediate(0u);
+ }
default:
UNIMPLEMENTED_MSG("Unhandled system move: {}",
static_cast<u32>(instr.sys20.Value()));
@@ -272,10 +292,25 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
SetRegister(bb, instr.gpr0, GetRegister(instr.gpr8));
break;
}
+ case OpCode::Id::BAR: {
+ UNIMPLEMENTED_IF_MSG(instr.value != 0xF0A81B8000070000ULL, "BAR is not BAR.SYNC 0x0");
+ bb.push_back(Operation(OperationCode::Barrier));
+ break;
+ }
case OpCode::Id::MEMBAR: {
- UNIMPLEMENTED_IF(instr.membar.type != Tegra::Shader::MembarType::GL);
UNIMPLEMENTED_IF(instr.membar.unknown != Tegra::Shader::MembarUnknown::Default);
- bb.push_back(Operation(OperationCode::MemoryBarrierGL));
+ const OperationCode type = [instr] {
+ switch (instr.membar.type) {
+ case Tegra::Shader::MembarType::CTA:
+ return OperationCode::MemoryBarrierGroup;
+ case Tegra::Shader::MembarType::GL:
+ return OperationCode::MemoryBarrierGlobal;
+ default:
+ UNIMPLEMENTED_MSG("MEMBAR type={}", static_cast<int>(instr.membar.type.Value()));
+ return OperationCode::MemoryBarrierGlobal;
+ }
+ }();
+ bb.push_back(Operation(type));
break;
}
case OpCode::Id::DEPBAR: {
diff --git a/src/video_core/shader/decode/register_set_predicate.cpp b/src/video_core/shader/decode/register_set_predicate.cpp
index 8d54cce34..6116c31aa 100644
--- a/src/video_core/shader/decode/register_set_predicate.cpp
+++ b/src/video_core/shader/decode/register_set_predicate.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <utility>
+
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
@@ -10,20 +12,20 @@
namespace VideoCommon::Shader {
+using std::move;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
namespace {
-constexpr u64 NUM_PROGRAMMABLE_PREDICATES = 7;
-}
+constexpr u64 NUM_CONDITION_CODES = 4;
+constexpr u64 NUM_PREDICATES = 7;
+} // namespace
u32 ShaderIR::DecodeRegisterSetPredicate(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- UNIMPLEMENTED_IF(instr.p2r_r2p.mode != Tegra::Shader::R2pMode::Pr);
-
- const Node apply_mask = [&] {
+ Node apply_mask = [this, opcode, instr] {
switch (opcode->get().GetId()) {
case OpCode::Id::R2P_IMM:
case OpCode::Id::P2R_IMM:
@@ -34,39 +36,43 @@ u32 ShaderIR::DecodeRegisterSetPredicate(NodeBlock& bb, u32 pc) {
}
}();
- const auto offset = static_cast<u32>(instr.p2r_r2p.byte) * 8;
+ const u32 offset = static_cast<u32>(instr.p2r_r2p.byte) * 8;
+
+ const bool cc = instr.p2r_r2p.mode == Tegra::Shader::R2pMode::Cc;
+ const u64 num_entries = cc ? NUM_CONDITION_CODES : NUM_PREDICATES;
+ const auto get_entry = [this, cc](u64 entry) {
+ return cc ? GetInternalFlag(static_cast<InternalFlag>(entry)) : GetPredicate(entry);
+ };
switch (opcode->get().GetId()) {
case OpCode::Id::R2P_IMM: {
- const Node mask = GetRegister(instr.gpr8);
+ Node mask = GetRegister(instr.gpr8);
- for (u64 pred = 0; pred < NUM_PROGRAMMABLE_PREDICATES; ++pred) {
- const auto shift = static_cast<u32>(pred);
+ for (u64 entry = 0; entry < num_entries; ++entry) {
+ const u32 shift = static_cast<u32>(entry);
- const Node apply_compare = BitfieldExtract(apply_mask, shift, 1);
- const Node condition =
- Operation(OperationCode::LogicalUNotEqual, apply_compare, Immediate(0));
+ Node apply = BitfieldExtract(apply_mask, shift, 1);
+ Node condition = Operation(OperationCode::LogicalUNotEqual, apply, Immediate(0));
- const Node value_compare = BitfieldExtract(mask, offset + shift, 1);
- const Node value =
- Operation(OperationCode::LogicalUNotEqual, value_compare, Immediate(0));
+ Node compare = BitfieldExtract(mask, offset + shift, 1);
+ Node value = Operation(OperationCode::LogicalUNotEqual, move(compare), Immediate(0));
- const Node code = Operation(OperationCode::LogicalAssign, GetPredicate(pred), value);
- bb.push_back(Conditional(condition, {code}));
+ Node code = Operation(OperationCode::LogicalAssign, get_entry(entry), move(value));
+ bb.push_back(Conditional(condition, {move(code)}));
}
break;
}
case OpCode::Id::P2R_IMM: {
Node value = Immediate(0);
- for (u64 pred = 0; pred < NUM_PROGRAMMABLE_PREDICATES; ++pred) {
- Node bit = Operation(OperationCode::Select, GetPredicate(pred), Immediate(1U << pred),
+ for (u64 entry = 0; entry < num_entries; ++entry) {
+ Node bit = Operation(OperationCode::Select, get_entry(entry), Immediate(1U << entry),
Immediate(0));
- value = Operation(OperationCode::UBitwiseOr, std::move(value), std::move(bit));
+ value = Operation(OperationCode::UBitwiseOr, move(value), move(bit));
}
- value = Operation(OperationCode::UBitwiseAnd, std::move(value), apply_mask);
- value = BitfieldInsert(GetRegister(instr.gpr8), std::move(value), offset, 8);
+ value = Operation(OperationCode::UBitwiseAnd, move(value), apply_mask);
+ value = BitfieldInsert(GetRegister(instr.gpr8), move(value), offset, 8);
- SetRegister(bb, instr.gpr0, std::move(value));
+ SetRegister(bb, instr.gpr0, move(value));
break;
}
default:
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index 3b391d3e6..d4ffa8014 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -23,7 +23,6 @@ Node IsFull(Node shift) {
}
Node Shift(OperationCode opcode, Node value, Node shift) {
- Node is_full = Operation(OperationCode::LogicalIEqual, shift, Immediate(32));
Node shifted = Operation(opcode, move(value), shift);
return Operation(OperationCode::Select, IsFull(move(shift)), Immediate(0), move(shifted));
}
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index 6c4a1358b..02fdccd86 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -139,15 +139,15 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
}
const Node component = Immediate(static_cast<u32>(instr.tld4s.component));
- const SamplerInfo info{TextureType::Texture2D, false, is_depth_compare};
- const Sampler& sampler = *GetSampler(instr.sampler, info);
+ SamplerInfo info;
+ info.is_shadow = is_depth_compare;
+ const std::optional<Sampler> sampler = GetSampler(instr.sampler, info);
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
- auto coords_copy = coords;
- MetaTexture meta{sampler, {}, depth_compare, aoffi, {}, {},
- {}, {}, component, element, {}};
- values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
+ MetaTexture meta{*sampler, {}, depth_compare, aoffi, {}, {},
+ {}, {}, component, element, {}};
+ values[element] = Operation(OperationCode::TextureGather, meta, coords);
}
if (instr.tld4s.fp16_flag) {
@@ -165,19 +165,20 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
"AOFFI is not implemented");
const bool is_array = instr.txd.is_array != 0;
- u64 base_reg = instr.gpr8.Value();
const auto derivate_reg = instr.gpr20.Value();
const auto texture_type = instr.txd.texture_type.Value();
const auto coord_count = GetCoordCount(texture_type);
- Node index_var{};
- const Sampler* sampler =
- is_bindless ? GetBindlessSampler(base_reg, index_var, {{texture_type, is_array, false}})
- : GetSampler(instr.sampler, {{texture_type, is_array, false}});
+ u64 base_reg = instr.gpr8.Value();
+ Node index_var;
+ SamplerInfo info;
+ info.type = texture_type;
+ info.is_array = is_array;
+ const std::optional<Sampler> sampler = is_bindless
+ ? GetBindlessSampler(base_reg, info, index_var)
+ : GetSampler(instr.sampler, info);
Node4 values;
- if (sampler == nullptr) {
- for (u32 element = 0; element < values.size(); ++element) {
- values[element] = Immediate(0);
- }
+ if (!sampler) {
+ std::generate(values.begin(), values.end(), [this] { return Immediate(0); });
WriteTexInstructionFloat(bb, instr, values);
break;
}
@@ -215,14 +216,12 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
is_bindless = true;
[[fallthrough]];
case OpCode::Id::TXQ: {
- // TODO: The new commits on the texture refactor, change the way samplers work.
- // Sadly, not all texture instructions specify the type of texture their sampler
- // uses. This must be fixed at a later instance.
- Node index_var{};
- const Sampler* sampler =
- is_bindless ? GetBindlessSampler(instr.gpr8, index_var) : GetSampler(instr.sampler);
-
- if (sampler == nullptr) {
+ Node index_var;
+ const std::optional<Sampler> sampler = is_bindless
+ ? GetBindlessSampler(instr.gpr8, {}, index_var)
+ : GetSampler(instr.sampler, {});
+
+ if (!sampler) {
u32 indexer = 0;
for (u32 element = 0; element < 4; ++element) {
if (!instr.txq.IsComponentEnabled(element)) {
@@ -268,13 +267,17 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
"NDV is not implemented");
- auto texture_type = instr.tmml.texture_type.Value();
+ const auto texture_type = instr.tmml.texture_type.Value();
const bool is_array = instr.tmml.array != 0;
- Node index_var{};
- const Sampler* sampler =
- is_bindless ? GetBindlessSampler(instr.gpr20, index_var) : GetSampler(instr.sampler);
-
- if (sampler == nullptr) {
+ SamplerInfo info;
+ info.type = texture_type;
+ info.is_array = is_array;
+ Node index_var;
+ const std::optional<Sampler> sampler =
+ is_bindless ? GetBindlessSampler(instr.gpr20, info, index_var)
+ : GetSampler(instr.sampler, info);
+
+ if (!sampler) {
u32 indexer = 0;
for (u32 element = 0; element < 2; ++element) {
if (!instr.tmml.IsComponentEnabled(element)) {
@@ -289,34 +292,36 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
break;
}
- std::vector<Node> coords;
-
- // TODO: Add coordinates for different samplers once other texture types are implemented.
- switch (texture_type) {
- case TextureType::Texture1D:
- coords.push_back(GetRegister(instr.gpr8));
- break;
- case TextureType::Texture2D:
- coords.push_back(GetRegister(instr.gpr8.Value() + 0));
- coords.push_back(GetRegister(instr.gpr8.Value() + 1));
- break;
- default:
- UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
+ const u64 base_index = is_array ? 1 : 0;
+ const u64 num_components = [texture_type] {
+ switch (texture_type) {
+ case TextureType::Texture1D:
+ return 1;
+ case TextureType::Texture2D:
+ return 2;
+ case TextureType::TextureCube:
+ return 3;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<int>(texture_type));
+ return 2;
+ }
+ }();
+ // TODO: What's the array component used for?
- // Fallback to interpreting as a 2D texture for now
- coords.push_back(GetRegister(instr.gpr8.Value() + 0));
- coords.push_back(GetRegister(instr.gpr8.Value() + 1));
- texture_type = TextureType::Texture2D;
+ std::vector<Node> coords;
+ coords.reserve(num_components);
+ for (u64 component = 0; component < num_components; ++component) {
+ coords.push_back(GetRegister(instr.gpr8.Value() + base_index + component));
}
+
u32 indexer = 0;
for (u32 element = 0; element < 2; ++element) {
if (!instr.tmml.IsComponentEnabled(element)) {
continue;
}
- auto params = coords;
MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element, index_var};
- const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
- SetTemporary(bb, indexer++, value);
+ Node value = Operation(OperationCode::TextureQueryLod, meta, coords);
+ SetTemporary(bb, indexer++, std::move(value));
}
for (u32 i = 0; i < indexer; ++i) {
SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
@@ -355,98 +360,122 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
return pc;
}
-ShaderIR::SamplerInfo ShaderIR::GetSamplerInfo(std::optional<SamplerInfo> sampler_info, u32 offset,
- std::optional<u32> buffer) {
- if (sampler_info) {
- return *sampler_info;
+ShaderIR::SamplerInfo ShaderIR::GetSamplerInfo(
+ SamplerInfo info, std::optional<Tegra::Engines::SamplerDescriptor> sampler) {
+ if (info.IsComplete()) {
+ return info;
}
- const auto sampler = buffer ? registry.ObtainBindlessSampler(*buffer, offset)
- : registry.ObtainBoundSampler(offset);
if (!sampler) {
LOG_WARNING(HW_GPU, "Unknown sampler info");
- return SamplerInfo{TextureType::Texture2D, false, false, false};
- }
- return SamplerInfo{sampler->texture_type, sampler->is_array != 0, sampler->is_shadow != 0,
- sampler->is_buffer != 0};
+ info.type = info.type.value_or(Tegra::Shader::TextureType::Texture2D);
+ info.is_array = info.is_array.value_or(false);
+ info.is_shadow = info.is_shadow.value_or(false);
+ info.is_buffer = info.is_buffer.value_or(false);
+ return info;
+ }
+ info.type = info.type.value_or(sampler->texture_type);
+ info.is_array = info.is_array.value_or(sampler->is_array != 0);
+ info.is_shadow = info.is_shadow.value_or(sampler->is_shadow != 0);
+ info.is_buffer = info.is_buffer.value_or(sampler->is_buffer != 0);
+ return info;
}
-const Sampler* ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler,
- std::optional<SamplerInfo> sampler_info) {
- const auto offset = static_cast<u32>(sampler.index.Value());
- const auto info = GetSamplerInfo(sampler_info, offset);
+std::optional<Sampler> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler,
+ SamplerInfo sampler_info) {
+ const u32 offset = static_cast<u32>(sampler.index.Value());
+ const auto info = GetSamplerInfo(sampler_info, registry.ObtainBoundSampler(offset));
// If this sampler has already been used, return the existing mapping.
- const auto it =
- std::find_if(used_samplers.begin(), used_samplers.end(),
- [offset](const Sampler& entry) { return entry.GetOffset() == offset; });
+ const auto it = std::find_if(used_samplers.begin(), used_samplers.end(),
+ [offset](const Sampler& entry) { return entry.offset == offset; });
if (it != used_samplers.end()) {
- ASSERT(!it->IsBindless() && it->GetType() == info.type && it->IsArray() == info.is_array &&
- it->IsShadow() == info.is_shadow && it->IsBuffer() == info.is_buffer);
- return &*it;
+ ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array &&
+ it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer);
+ return *it;
}
// Otherwise create a new mapping for this sampler
const auto next_index = static_cast<u32>(used_samplers.size());
- return &used_samplers.emplace_back(next_index, offset, info.type, info.is_array, info.is_shadow,
- info.is_buffer, false);
+ return used_samplers.emplace_back(next_index, offset, *info.type, *info.is_array,
+ *info.is_shadow, *info.is_buffer, false);
}
-const Sampler* ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, Node& index_var,
- std::optional<SamplerInfo> sampler_info) {
+std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info,
+ Node& index_var) {
const Node sampler_register = GetRegister(reg);
const auto [base_node, tracked_sampler_info] =
TrackBindlessSampler(sampler_register, global_code, static_cast<s64>(global_code.size()));
- ASSERT(base_node != nullptr);
- if (base_node == nullptr) {
- return nullptr;
+ if (!base_node) {
+ UNREACHABLE();
+ return std::nullopt;
}
- if (const auto bindless_sampler_info =
- std::get_if<BindlessSamplerNode>(&*tracked_sampler_info)) {
- const u32 buffer = bindless_sampler_info->GetIndex();
- const u32 offset = bindless_sampler_info->GetOffset();
- const auto info = GetSamplerInfo(sampler_info, offset, buffer);
+ if (const auto sampler_info = std::get_if<BindlessSamplerNode>(&*tracked_sampler_info)) {
+ const u32 buffer = sampler_info->index;
+ const u32 offset = sampler_info->offset;
+ info = GetSamplerInfo(info, registry.ObtainBindlessSampler(buffer, offset));
// If this sampler has already been used, return the existing mapping.
- const auto it =
- std::find_if(used_samplers.begin(), used_samplers.end(),
- [buffer = buffer, offset = offset](const Sampler& entry) {
- return entry.GetBuffer() == buffer && entry.GetOffset() == offset;
- });
+ const auto it = std::find_if(used_samplers.begin(), used_samplers.end(),
+ [buffer, offset](const Sampler& entry) {
+ return entry.buffer == buffer && entry.offset == offset;
+ });
if (it != used_samplers.end()) {
- ASSERT(it->IsBindless() && it->GetType() == info.type &&
- it->IsArray() == info.is_array && it->IsShadow() == info.is_shadow);
- return &*it;
+ ASSERT(it->is_bindless && it->type == info.type && it->is_array == info.is_array &&
+ it->is_shadow == info.is_shadow);
+ return *it;
}
// Otherwise create a new mapping for this sampler
const auto next_index = static_cast<u32>(used_samplers.size());
- return &used_samplers.emplace_back(next_index, offset, buffer, info.type, info.is_array,
- info.is_shadow, info.is_buffer, false);
- } else if (const auto array_sampler_info =
- std::get_if<ArraySamplerNode>(&*tracked_sampler_info)) {
- const u32 base_offset = array_sampler_info->GetBaseOffset() / 4;
- index_var = GetCustomVariable(array_sampler_info->GetIndexVar());
- const auto info = GetSamplerInfo(sampler_info, base_offset);
+ return used_samplers.emplace_back(next_index, offset, buffer, *info.type, *info.is_array,
+ *info.is_shadow, *info.is_buffer, false);
+ }
+ if (const auto sampler_info = std::get_if<SeparateSamplerNode>(&*tracked_sampler_info)) {
+ const std::pair indices = sampler_info->indices;
+ const std::pair offsets = sampler_info->offsets;
+ info = GetSamplerInfo(info, registry.ObtainSeparateSampler(indices, offsets));
+
+ // Try to use an already created sampler if it exists
+ const auto it = std::find_if(
+ used_samplers.begin(), used_samplers.end(), [indices, offsets](const Sampler& entry) {
+ return offsets == std::pair{entry.offset, entry.secondary_offset} &&
+ indices == std::pair{entry.buffer, entry.secondary_buffer};
+ });
+ if (it != used_samplers.end()) {
+ ASSERT(it->is_separated && it->type == info.type && it->is_array == info.is_array &&
+ it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer);
+ return *it;
+ }
+
+ // Otherwise create a new mapping for this sampler
+ const u32 next_index = static_cast<u32>(used_samplers.size());
+ return used_samplers.emplace_back(next_index, offsets, indices, *info.type, *info.is_array,
+ *info.is_shadow, *info.is_buffer);
+ }
+ if (const auto sampler_info = std::get_if<ArraySamplerNode>(&*tracked_sampler_info)) {
+ const u32 base_offset = sampler_info->base_offset / 4;
+ index_var = GetCustomVariable(sampler_info->bindless_var);
+ info = GetSamplerInfo(info, registry.ObtainBoundSampler(base_offset));
// If this sampler has already been used, return the existing mapping.
const auto it = std::find_if(
used_samplers.begin(), used_samplers.end(),
- [base_offset](const Sampler& entry) { return entry.GetOffset() == base_offset; });
+ [base_offset](const Sampler& entry) { return entry.offset == base_offset; });
if (it != used_samplers.end()) {
- ASSERT(!it->IsBindless() && it->GetType() == info.type &&
- it->IsArray() == info.is_array && it->IsShadow() == info.is_shadow &&
- it->IsBuffer() == info.is_buffer && it->IsIndexed());
- return &*it;
+ ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array &&
+ it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer &&
+ it->is_indexed);
+ return *it;
}
uses_indexed_samplers = true;
// Otherwise create a new mapping for this sampler
const auto next_index = static_cast<u32>(used_samplers.size());
- return &used_samplers.emplace_back(next_index, base_offset, info.type, info.is_array,
- info.is_shadow, info.is_buffer, true);
+ return used_samplers.emplace_back(next_index, base_offset, *info.type, *info.is_array,
+ *info.is_shadow, *info.is_buffer, true);
}
- return nullptr;
+ return std::nullopt;
}
void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
@@ -527,14 +556,19 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
const bool is_shadow = depth_compare != nullptr;
const bool is_bindless = bindless_reg.has_value();
- UNIMPLEMENTED_IF(texture_type == TextureType::TextureCube && is_array && is_shadow);
ASSERT_MSG(texture_type != TextureType::Texture3D || !is_array || !is_shadow,
"Illegal texture type");
- const SamplerInfo info{texture_type, is_array, is_shadow, false};
+ SamplerInfo info;
+ info.type = texture_type;
+ info.is_array = is_array;
+ info.is_shadow = is_shadow;
+ info.is_buffer = false;
+
Node index_var;
- const Sampler* sampler = is_bindless ? GetBindlessSampler(*bindless_reg, index_var, info)
- : GetSampler(instr.sampler, info);
+ const std::optional<Sampler> sampler = is_bindless
+ ? GetBindlessSampler(*bindless_reg, info, index_var)
+ : GetSampler(instr.sampler, info);
if (!sampler) {
return {Immediate(0), Immediate(0), Immediate(0), Immediate(0)};
}
@@ -593,8 +627,9 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
++parameter_register;
}
- const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
- texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
+ const auto coord_counts = ValidateAndGetCoordinateElement(texture_type, depth_compare, is_array,
+ lod_bias_enabled, 4, 5);
+ const auto coord_count = std::get<0>(coord_counts);
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
@@ -632,8 +667,10 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
const bool lod_bias_enabled =
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
- const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
- texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
+ const auto coord_counts = ValidateAndGetCoordinateElement(texture_type, depth_compare, is_array,
+ lod_bias_enabled, 4, 4);
+ const auto coord_count = std::get<0>(coord_counts);
+
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
@@ -682,12 +719,17 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
u64 parameter_register = instr.gpr20.Value();
- const SamplerInfo info{texture_type, is_array, depth_compare, false};
- Node index_var{};
- const Sampler* sampler = is_bindless ? GetBindlessSampler(parameter_register++, index_var, info)
- : GetSampler(instr.sampler, info);
+ SamplerInfo info;
+ info.type = texture_type;
+ info.is_array = is_array;
+ info.is_shadow = depth_compare;
+
+ Node index_var;
+ const std::optional<Sampler> sampler =
+ is_bindless ? GetBindlessSampler(parameter_register++, info, index_var)
+ : GetSampler(instr.sampler, info);
Node4 values;
- if (sampler == nullptr) {
+ if (!sampler) {
for (u32 element = 0; element < values.size(); ++element) {
values[element] = Immediate(0);
}
@@ -723,7 +765,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
const auto texture_type{instr.tld.texture_type};
- const bool is_array{instr.tld.is_array};
+ const bool is_array{instr.tld.is_array != 0};
const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL};
const std::size_t coord_count{GetCoordCount(texture_type)};
@@ -742,12 +784,12 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
// const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr};
// const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr};
- const auto& sampler = *GetSampler(instr.sampler);
+ const std::optional<Sampler> sampler = GetSampler(instr.sampler, {});
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto coords_copy = coords;
- MetaTexture meta{sampler, array_register, {}, {}, {}, {}, {}, lod, {}, element, {}};
+ MetaTexture meta{*sampler, array_register, {}, {}, {}, {}, {}, lod, {}, element, {}};
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
}
@@ -755,7 +797,11 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
}
Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
- const Sampler& sampler = *GetSampler(instr.sampler);
+ SamplerInfo info;
+ info.type = texture_type;
+ info.is_array = is_array;
+ info.is_shadow = false;
+ const std::optional<Sampler> sampler = GetSampler(instr.sampler, info);
const std::size_t type_coord_count = GetCoordCount(texture_type);
const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
@@ -783,7 +829,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto coords_copy = coords;
- MetaTexture meta{sampler, array, {}, {}, {}, {}, {}, lod, {}, element, {}};
+ MetaTexture meta{*sampler, array, {}, {}, {}, {}, {}, lod, {}, element, {}};
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
}
return values;
diff --git a/src/video_core/shader/decode/video.cpp b/src/video_core/shader/decode/video.cpp
index 64ba60ea2..1c0957277 100644
--- a/src/video_core/shader/decode/video.cpp
+++ b/src/video_core/shader/decode/video.cpp
@@ -91,29 +91,28 @@ u32 ShaderIR::DecodeVideo(NodeBlock& bb, u32 pc) {
return pc;
}
-Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed,
- Tegra::Shader::VideoType type, u64 byte_height) {
+Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed, VideoType type,
+ u64 byte_height) {
if (!is_chunk) {
return BitfieldExtract(op, static_cast<u32>(byte_height * 8), 8);
}
- const Node zero = Immediate(0);
switch (type) {
- case Tegra::Shader::VideoType::Size16_Low:
+ case VideoType::Size16_Low:
return BitfieldExtract(op, 0, 16);
- case Tegra::Shader::VideoType::Size16_High:
+ case VideoType::Size16_High:
return BitfieldExtract(op, 16, 16);
- case Tegra::Shader::VideoType::Size32:
+ case VideoType::Size32:
// TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when this type is used
// (1 * 1 + 0 == 0x5b800000). Until a better explanation is found: abort.
UNIMPLEMENTED();
- return zero;
- case Tegra::Shader::VideoType::Invalid:
+ return Immediate(0);
+ case VideoType::Invalid:
UNREACHABLE_MSG("Invalid instruction encoding");
- return zero;
+ return Immediate(0);
default:
UNREACHABLE();
- return zero;
+ return Immediate(0);
}
}
diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp
index 6191ffba1..233b8fa42 100644
--- a/src/video_core/shader/decode/xmad.cpp
+++ b/src/video_core/shader/decode/xmad.cpp
@@ -81,35 +81,36 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
SetTemporary(bb, 0, product);
product = GetTemporary(0);
- const Node original_c = op_c;
+ Node original_c = op_c;
const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
- op_c = [&]() {
+ op_c = [&] {
switch (set_mode) {
case Tegra::Shader::XmadMode::None:
return original_c;
case Tegra::Shader::XmadMode::CLo:
- return BitfieldExtract(original_c, 0, 16);
+ return BitfieldExtract(std::move(original_c), 0, 16);
case Tegra::Shader::XmadMode::CHi:
- return BitfieldExtract(original_c, 16, 16);
+ return BitfieldExtract(std::move(original_c), 16, 16);
case Tegra::Shader::XmadMode::CBcc: {
- const Node shifted_b = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed_b,
- original_b, Immediate(16));
- return SignedOperation(OperationCode::IAdd, is_signed_c, original_c, shifted_b);
+ Node shifted_b = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed_b,
+ original_b, Immediate(16));
+ return SignedOperation(OperationCode::IAdd, is_signed_c, std::move(original_c),
+ std::move(shifted_b));
}
case Tegra::Shader::XmadMode::CSfu: {
- const Node comp_a = GetPredicateComparisonInteger(PredCondition::Equal, is_signed_a,
- op_a, Immediate(0));
- const Node comp_b = GetPredicateComparisonInteger(PredCondition::Equal, is_signed_b,
- op_b, Immediate(0));
+ const Node comp_a =
+ GetPredicateComparisonInteger(PredCondition::EQ, is_signed_a, op_a, Immediate(0));
+ const Node comp_b =
+ GetPredicateComparisonInteger(PredCondition::EQ, is_signed_b, op_b, Immediate(0));
const Node comp = Operation(OperationCode::LogicalOr, comp_a, comp_b);
const Node comp_minus_a = GetPredicateComparisonInteger(
- PredCondition::NotEqual, is_signed_a,
+ PredCondition::NE, is_signed_a,
SignedOperation(OperationCode::IBitwiseAnd, is_signed_a, op_a,
Immediate(0x80000000)),
Immediate(0));
const Node comp_minus_b = GetPredicateComparisonInteger(
- PredCondition::NotEqual, is_signed_b,
+ PredCondition::NE, is_signed_b,
SignedOperation(OperationCode::IBitwiseAnd, is_signed_b, op_b,
Immediate(0x80000000)),
Immediate(0));
diff --git a/src/video_core/shader/memory_util.cpp b/src/video_core/shader/memory_util.cpp
new file mode 100644
index 000000000..e18ccba8e
--- /dev/null
+++ b/src/video_core/shader/memory_util.cpp
@@ -0,0 +1,76 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstddef>
+
+#include <boost/container_hash/hash.hpp>
+
+#include "common/common_types.h"
+#include "core/core.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
+#include "video_core/shader/memory_util.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace VideoCommon::Shader {
+
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::Maxwell3D::Regs::ShaderProgram program) {
+ const auto& shader_config{maxwell3d.regs.shader_config[static_cast<std::size_t>(program)]};
+ return maxwell3d.regs.code_address.CodeAddress() + shader_config.offset;
+}
+
+bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
+ // Sched instructions appear once every 4 instructions.
+ constexpr std::size_t SchedPeriod = 4;
+ const std::size_t absolute_offset = offset - main_offset;
+ return (absolute_offset % SchedPeriod) == 0;
+}
+
+std::size_t CalculateProgramSize(const ProgramCode& program, bool is_compute) {
+ // This is the encoded version of BRA that jumps to itself. All Nvidia
+ // shaders end with one.
+ static constexpr u64 SELF_JUMPING_BRANCH = 0xE2400FFFFF07000FULL;
+ static constexpr u64 MASK = 0xFFFFFFFFFF7FFFFFULL;
+
+ const std::size_t start_offset = is_compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET;
+ std::size_t offset = start_offset;
+ while (offset < program.size()) {
+ const u64 instruction = program[offset];
+ if (!IsSchedInstruction(offset, start_offset)) {
+ if ((instruction & MASK) == SELF_JUMPING_BRANCH) {
+ // End on Maxwell's "nop" instruction
+ break;
+ }
+ if (instruction == 0) {
+ break;
+ }
+ }
+ ++offset;
+ }
+ // The last instruction is included in the program size
+ return std::min(offset + 1, program.size());
+}
+
+ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, GPUVAddr gpu_addr,
+ const u8* host_ptr, bool is_compute) {
+ ProgramCode code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
+ ASSERT_OR_EXECUTE(host_ptr != nullptr, { return code; });
+ memory_manager.ReadBlockUnsafe(gpu_addr, code.data(), code.size() * sizeof(u64));
+ code.resize(CalculateProgramSize(code, is_compute));
+ return code;
+}
+
+u64 GetUniqueIdentifier(Tegra::Engines::ShaderType shader_type, bool is_a, const ProgramCode& code,
+ const ProgramCode& code_b) {
+ size_t unique_identifier = boost::hash_value(code);
+ if (is_a) {
+ // VertexA programs include two programs
+ boost::hash_combine(unique_identifier, boost::hash_value(code_b));
+ }
+ return static_cast<u64>(unique_identifier);
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/memory_util.h b/src/video_core/shader/memory_util.h
new file mode 100644
index 000000000..4624d38e6
--- /dev/null
+++ b/src/video_core/shader/memory_util.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/shader_type.h"
+
+namespace Tegra {
+class MemoryManager;
+}
+
+namespace VideoCommon::Shader {
+
+using ProgramCode = std::vector<u64>;
+
+constexpr u32 STAGE_MAIN_OFFSET = 10;
+constexpr u32 KERNEL_MAIN_OFFSET = 0;
+
+/// Gets the address for the specified shader stage program
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::Maxwell3D::Regs::ShaderProgram program);
+
+/// Gets if the current instruction offset is a scheduler instruction
+bool IsSchedInstruction(std::size_t offset, std::size_t main_offset);
+
+/// Calculates the size of a program stream
+std::size_t CalculateProgramSize(const ProgramCode& program, bool is_compute);
+
+/// Gets the shader program code from memory for the specified address
+ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, GPUVAddr gpu_addr,
+ const u8* host_ptr, bool is_compute);
+
+/// Hashes one (or two) program streams
+u64 GetUniqueIdentifier(Tegra::Engines::ShaderType shader_type, bool is_a, const ProgramCode& code,
+ const ProgramCode& code_b = {});
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 3eee961f5..8f230d57a 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -110,13 +110,20 @@ enum class OperationCode {
LogicalPick2, /// (bool2 pair, uint index) -> bool
LogicalAnd2, /// (bool2 a) -> bool
- LogicalFLessThan, /// (float a, float b) -> bool
- LogicalFEqual, /// (float a, float b) -> bool
- LogicalFLessEqual, /// (float a, float b) -> bool
- LogicalFGreaterThan, /// (float a, float b) -> bool
- LogicalFNotEqual, /// (float a, float b) -> bool
- LogicalFGreaterEqual, /// (float a, float b) -> bool
- LogicalFIsNan, /// (float a) -> bool
+ LogicalFOrdLessThan, /// (float a, float b) -> bool
+ LogicalFOrdEqual, /// (float a, float b) -> bool
+ LogicalFOrdLessEqual, /// (float a, float b) -> bool
+ LogicalFOrdGreaterThan, /// (float a, float b) -> bool
+ LogicalFOrdNotEqual, /// (float a, float b) -> bool
+ LogicalFOrdGreaterEqual, /// (float a, float b) -> bool
+ LogicalFOrdered, /// (float a, float b) -> bool
+ LogicalFUnordered, /// (float a, float b) -> bool
+ LogicalFUnordLessThan, /// (float a, float b) -> bool
+ LogicalFUnordEqual, /// (float a, float b) -> bool
+ LogicalFUnordLessEqual, /// (float a, float b) -> bool
+ LogicalFUnordGreaterThan, /// (float a, float b) -> bool
+ LogicalFUnordNotEqual, /// (float a, float b) -> bool
+ LogicalFUnordGreaterEqual, /// (float a, float b) -> bool
LogicalILessThan, /// (int a, int b) -> bool
LogicalIEqual, /// (int a, int b) -> bool
@@ -132,6 +139,8 @@ enum class OperationCode {
LogicalUNotEqual, /// (uint a, uint b) -> bool
LogicalUGreaterEqual, /// (uint a, uint b) -> bool
+ LogicalAddCarry, /// (uint a, uint b) -> bool
+
Logical2HLessThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
Logical2HEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
Logical2HLessEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
@@ -217,9 +226,16 @@ enum class OperationCode {
VoteEqual, /// (bool) -> bool
ThreadId, /// () -> uint
+ ThreadEqMask, /// () -> uint
+ ThreadGeMask, /// () -> uint
+ ThreadGtMask, /// () -> uint
+ ThreadLeMask, /// () -> uint
+ ThreadLtMask, /// () -> uint
ShuffleIndexed, /// (uint value, uint index) -> uint
- MemoryBarrierGL, /// () -> void
+ Barrier, /// () -> void
+ MemoryBarrierGroup, /// () -> void
+ MemoryBarrierGlobal, /// () -> void
Amount,
};
@@ -259,133 +275,76 @@ using Node = std::shared_ptr<NodeData>;
using Node4 = std::array<Node, 4>;
using NodeBlock = std::vector<Node>;
-class BindlessSamplerNode;
-class ArraySamplerNode;
+struct ArraySamplerNode;
+struct BindlessSamplerNode;
+struct SeparateSamplerNode;
-using TrackSamplerData = std::variant<BindlessSamplerNode, ArraySamplerNode>;
+using TrackSamplerData = std::variant<BindlessSamplerNode, SeparateSamplerNode, ArraySamplerNode>;
using TrackSampler = std::shared_ptr<TrackSamplerData>;
-class Sampler {
-public:
- /// This constructor is for bound samplers
+struct Sampler {
+ /// Bound samplers constructor
constexpr explicit Sampler(u32 index, u32 offset, Tegra::Shader::TextureType type,
bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
: index{index}, offset{offset}, type{type}, is_array{is_array}, is_shadow{is_shadow},
is_buffer{is_buffer}, is_indexed{is_indexed} {}
- /// This constructor is for bindless samplers
+ /// Separate sampler constructor
+ constexpr explicit Sampler(u32 index, std::pair<u32, u32> offsets, std::pair<u32, u32> buffers,
+ Tegra::Shader::TextureType type, bool is_array, bool is_shadow,
+ bool is_buffer)
+ : index{index}, offset{offsets.first}, secondary_offset{offsets.second},
+ buffer{buffers.first}, secondary_buffer{buffers.second}, type{type}, is_array{is_array},
+ is_shadow{is_shadow}, is_buffer{is_buffer}, is_separated{true} {}
+
+ /// Bindless samplers constructor
constexpr explicit Sampler(u32 index, u32 offset, u32 buffer, Tegra::Shader::TextureType type,
bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
: index{index}, offset{offset}, buffer{buffer}, type{type}, is_array{is_array},
is_shadow{is_shadow}, is_buffer{is_buffer}, is_bindless{true}, is_indexed{is_indexed} {}
- constexpr u32 GetIndex() const {
- return index;
- }
-
- constexpr u32 GetOffset() const {
- return offset;
- }
-
- constexpr u32 GetBuffer() const {
- return buffer;
- }
-
- constexpr Tegra::Shader::TextureType GetType() const {
- return type;
- }
-
- constexpr bool IsArray() const {
- return is_array;
- }
-
- constexpr bool IsShadow() const {
- return is_shadow;
- }
-
- constexpr bool IsBuffer() const {
- return is_buffer;
- }
-
- constexpr bool IsBindless() const {
- return is_bindless;
- }
-
- constexpr bool IsIndexed() const {
- return is_indexed;
- }
-
- constexpr u32 Size() const {
- return size;
- }
-
- constexpr void SetSize(u32 new_size) {
- size = new_size;
- }
-
-private:
- u32 index{}; ///< Emulated index given for the this sampler.
- u32 offset{}; ///< Offset in the const buffer from where the sampler is being read.
- u32 buffer{}; ///< Buffer where the bindless sampler is being read (unused on bound samplers).
- u32 size{1}; ///< Size of the sampler.
+ u32 index = 0; ///< Emulated index given for the this sampler.
+ u32 offset = 0; ///< Offset in the const buffer from where the sampler is being read.
+ u32 secondary_offset = 0; ///< Secondary offset in the const buffer.
+ u32 buffer = 0; ///< Buffer where the bindless sampler is read.
+ u32 secondary_buffer = 0; ///< Secondary buffer where the bindless sampler is read.
+ u32 size = 1; ///< Size of the sampler.
Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
- bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
- bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
- bool is_buffer{}; ///< Whether the texture is a texture buffer without sampler.
- bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
- bool is_indexed{}; ///< Whether this sampler is an indexed array of textures.
+ bool is_array = false; ///< Whether the texture is being sampled as an array texture or not.
+ bool is_shadow = false; ///< Whether the texture is being sampled as a depth texture or not.
+ bool is_buffer = false; ///< Whether the texture is a texture buffer without sampler.
+ bool is_bindless = false; ///< Whether this sampler belongs to a bindless texture or not.
+ bool is_indexed = false; ///< Whether this sampler is an indexed array of textures.
+ bool is_separated = false; ///< Whether the image and sampler is separated or not.
};
/// Represents a tracked bindless sampler into a direct const buffer
-class ArraySamplerNode final {
-public:
- explicit ArraySamplerNode(u32 index, u32 base_offset, u32 bindless_var)
- : index{index}, base_offset{base_offset}, bindless_var{bindless_var} {}
-
- constexpr u32 GetIndex() const {
- return index;
- }
-
- constexpr u32 GetBaseOffset() const {
- return base_offset;
- }
-
- constexpr u32 GetIndexVar() const {
- return bindless_var;
- }
-
-private:
+struct ArraySamplerNode {
u32 index;
u32 base_offset;
u32 bindless_var;
};
-/// Represents a tracked bindless sampler into a direct const buffer
-class BindlessSamplerNode final {
-public:
- explicit BindlessSamplerNode(u32 index, u32 offset) : index{index}, offset{offset} {}
-
- constexpr u32 GetIndex() const {
- return index;
- }
-
- constexpr u32 GetOffset() const {
- return offset;
- }
+/// Represents a tracked separate sampler image pair that was folded statically
+struct SeparateSamplerNode {
+ std::pair<u32, u32> indices;
+ std::pair<u32, u32> offsets;
+};
-private:
+/// Represents a tracked bindless sampler into a direct const buffer
+struct BindlessSamplerNode {
u32 index;
u32 offset;
};
-class Image final {
+struct Image {
public:
- /// This constructor is for bound images
+ /// Bound images constructor
constexpr explicit Image(u32 index, u32 offset, Tegra::Shader::ImageType type)
: index{index}, offset{offset}, type{type} {}
- /// This constructor is for bindless samplers
+ /// Bindless samplers constructor
constexpr explicit Image(u32 index, u32 offset, u32 buffer, Tegra::Shader::ImageType type)
: index{index}, offset{offset}, buffer{buffer}, type{type}, is_bindless{true} {}
@@ -403,53 +362,20 @@ public:
is_atomic = true;
}
- constexpr u32 GetIndex() const {
- return index;
- }
-
- constexpr u32 GetOffset() const {
- return offset;
- }
-
- constexpr u32 GetBuffer() const {
- return buffer;
- }
-
- constexpr Tegra::Shader::ImageType GetType() const {
- return type;
- }
-
- constexpr bool IsBindless() const {
- return is_bindless;
- }
-
- constexpr bool IsWritten() const {
- return is_written;
- }
-
- constexpr bool IsRead() const {
- return is_read;
- }
-
- constexpr bool IsAtomic() const {
- return is_atomic;
- }
-
-private:
- u32 index{};
- u32 offset{};
- u32 buffer{};
+ u32 index = 0;
+ u32 offset = 0;
+ u32 buffer = 0;
Tegra::Shader::ImageType type{};
- bool is_bindless{};
- bool is_written{};
- bool is_read{};
- bool is_atomic{};
+ bool is_bindless = false;
+ bool is_written = false;
+ bool is_read = false;
+ bool is_atomic = false;
};
struct GlobalMemoryBase {
- u32 cbuf_index{};
- u32 cbuf_offset{};
+ u32 cbuf_index = 0;
+ u32 cbuf_offset = 0;
bool operator<(const GlobalMemoryBase& rhs) const {
return std::tie(cbuf_index, cbuf_offset) < std::tie(rhs.cbuf_index, rhs.cbuf_offset);
@@ -463,7 +389,7 @@ struct MetaArithmetic {
/// Parameters describing a texture sampler
struct MetaTexture {
- const Sampler& sampler;
+ Sampler sampler;
Node array;
Node depth_compare;
std::vector<Node> aoffi;
diff --git a/src/video_core/shader/node_helper.h b/src/video_core/shader/node_helper.h
index 11231bbea..1e0886185 100644
--- a/src/video_core/shader/node_helper.h
+++ b/src/video_core/shader/node_helper.h
@@ -48,7 +48,7 @@ Node MakeNode(Args&&... args) {
template <typename T, typename... Args>
TrackSampler MakeTrackSampler(Args&&... args) {
static_assert(std::is_convertible_v<T, TrackSamplerData>);
- return std::make_shared<TrackSamplerData>(T(std::forward<Args>(args)...));
+ return std::make_shared<TrackSamplerData>(T{std::forward<Args>(args)...});
}
template <typename... Args>
diff --git a/src/video_core/shader/registry.cpp b/src/video_core/shader/registry.cpp
index af70b3f35..148d91fcb 100644
--- a/src/video_core/shader/registry.cpp
+++ b/src/video_core/shader/registry.cpp
@@ -24,44 +24,45 @@ GraphicsInfo MakeGraphicsInfo(ShaderType shader_stage, ConstBufferEngineInterfac
if (shader_stage == ShaderType::Compute) {
return {};
}
- auto& graphics = static_cast<Tegra::Engines::Maxwell3D&>(engine);
-
- GraphicsInfo info;
- info.tfb_layouts = graphics.regs.tfb_layouts;
- info.tfb_varying_locs = graphics.regs.tfb_varying_locs;
- info.primitive_topology = graphics.regs.draw.topology;
- info.tessellation_primitive = graphics.regs.tess_mode.prim;
- info.tessellation_spacing = graphics.regs.tess_mode.spacing;
- info.tfb_enabled = graphics.regs.tfb_enabled;
- info.tessellation_clockwise = graphics.regs.tess_mode.cw;
- return info;
+
+ auto& graphics = dynamic_cast<Tegra::Engines::Maxwell3D&>(engine);
+
+ return {
+ .tfb_layouts = graphics.regs.tfb_layouts,
+ .tfb_varying_locs = graphics.regs.tfb_varying_locs,
+ .primitive_topology = graphics.regs.draw.topology,
+ .tessellation_primitive = graphics.regs.tess_mode.prim,
+ .tessellation_spacing = graphics.regs.tess_mode.spacing,
+ .tfb_enabled = graphics.regs.tfb_enabled != 0,
+ .tessellation_clockwise = graphics.regs.tess_mode.cw.Value() != 0,
+ };
}
ComputeInfo MakeComputeInfo(ShaderType shader_stage, ConstBufferEngineInterface& engine) {
if (shader_stage != ShaderType::Compute) {
return {};
}
- auto& compute = static_cast<Tegra::Engines::KeplerCompute&>(engine);
+
+ auto& compute = dynamic_cast<Tegra::Engines::KeplerCompute&>(engine);
const auto& launch = compute.launch_description;
- ComputeInfo info;
- info.workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z};
- info.local_memory_size_in_words = launch.local_pos_alloc;
- info.shared_memory_size_in_words = launch.shared_alloc;
- return info;
+ return {
+ .workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z},
+ .shared_memory_size_in_words = launch.shared_alloc,
+ .local_memory_size_in_words = launch.local_pos_alloc,
+ };
}
} // Anonymous namespace
-Registry::Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info)
+Registry::Registry(ShaderType shader_stage, const SerializedRegistryInfo& info)
: stage{shader_stage}, stored_guest_driver_profile{info.guest_driver_profile},
bound_buffer{info.bound_buffer}, graphics_info{info.graphics}, compute_info{info.compute} {}
-Registry::Registry(Tegra::Engines::ShaderType shader_stage,
- Tegra::Engines::ConstBufferEngineInterface& engine)
- : stage{shader_stage}, engine{&engine}, bound_buffer{engine.GetBoundBuffer()},
- graphics_info{MakeGraphicsInfo(shader_stage, engine)}, compute_info{MakeComputeInfo(
- shader_stage, engine)} {}
+Registry::Registry(ShaderType shader_stage, ConstBufferEngineInterface& engine_)
+ : stage{shader_stage}, engine{&engine_}, bound_buffer{engine_.GetBoundBuffer()},
+ graphics_info{MakeGraphicsInfo(shader_stage, engine_)}, compute_info{MakeComputeInfo(
+ shader_stage, engine_)} {}
Registry::~Registry() = default;
@@ -93,8 +94,27 @@ std::optional<SamplerDescriptor> Registry::ObtainBoundSampler(u32 offset) {
return value;
}
-std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer,
- u32 offset) {
+std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainSeparateSampler(
+ std::pair<u32, u32> buffers, std::pair<u32, u32> offsets) {
+ SeparateSamplerKey key;
+ key.buffers = buffers;
+ key.offsets = offsets;
+ const auto iter = separate_samplers.find(key);
+ if (iter != separate_samplers.end()) {
+ return iter->second;
+ }
+ if (!engine) {
+ return std::nullopt;
+ }
+
+ const u32 handle_1 = engine->AccessConstBuffer32(stage, key.buffers.first, key.offsets.first);
+ const u32 handle_2 = engine->AccessConstBuffer32(stage, key.buffers.second, key.offsets.second);
+ const SamplerDescriptor value = engine->AccessSampler(handle_1 | handle_2);
+ separate_samplers.emplace(key, value);
+ return value;
+}
+
+std::optional<SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer, u32 offset) {
const std::pair key = {buffer, offset};
const auto iter = bindless_samplers.find(key);
if (iter != bindless_samplers.end()) {
diff --git a/src/video_core/shader/registry.h b/src/video_core/shader/registry.h
index 0c80d35fd..4bebefdde 100644
--- a/src/video_core/shader/registry.h
+++ b/src/video_core/shader/registry.h
@@ -19,8 +19,39 @@
namespace VideoCommon::Shader {
+struct SeparateSamplerKey {
+ std::pair<u32, u32> buffers;
+ std::pair<u32, u32> offsets;
+};
+
+} // namespace VideoCommon::Shader
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::Shader::SeparateSamplerKey> {
+ std::size_t operator()(const VideoCommon::Shader::SeparateSamplerKey& key) const noexcept {
+ return std::hash<u32>{}(key.buffers.first ^ key.buffers.second ^ key.offsets.first ^
+ key.offsets.second);
+ }
+};
+
+template <>
+struct equal_to<VideoCommon::Shader::SeparateSamplerKey> {
+ bool operator()(const VideoCommon::Shader::SeparateSamplerKey& lhs,
+ const VideoCommon::Shader::SeparateSamplerKey& rhs) const noexcept {
+ return lhs.buffers == rhs.buffers && lhs.offsets == rhs.offsets;
+ }
+};
+
+} // namespace std
+
+namespace VideoCommon::Shader {
+
using KeyMap = std::unordered_map<std::pair<u32, u32>, u32, Common::PairHash>;
using BoundSamplerMap = std::unordered_map<u32, Tegra::Engines::SamplerDescriptor>;
+using SeparateSamplerMap =
+ std::unordered_map<SeparateSamplerKey, Tegra::Engines::SamplerDescriptor>;
using BindlessSamplerMap =
std::unordered_map<std::pair<u32, u32>, Tegra::Engines::SamplerDescriptor, Common::PairHash>;
@@ -63,7 +94,7 @@ public:
explicit Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info);
explicit Registry(Tegra::Engines::ShaderType shader_stage,
- Tegra::Engines::ConstBufferEngineInterface& engine);
+ Tegra::Engines::ConstBufferEngineInterface& engine_);
~Registry();
@@ -73,6 +104,9 @@ public:
std::optional<Tegra::Engines::SamplerDescriptor> ObtainBoundSampler(u32 offset);
+ std::optional<Tegra::Engines::SamplerDescriptor> ObtainSeparateSampler(
+ std::pair<u32, u32> buffers, std::pair<u32, u32> offsets);
+
std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset);
/// Inserts a key.
@@ -128,6 +162,7 @@ private:
Tegra::Engines::ConstBufferEngineInterface* engine = nullptr;
KeyMap keys;
BoundSamplerMap bound_samplers;
+ SeparateSamplerMap separate_samplers;
BindlessSamplerMap bindless_samplers;
u32 bound_buffer;
GraphicsInfo graphics_info;
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 8852c8a1b..29d794b34 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node.h"
#include "video_core/shader/node_helper.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
@@ -56,8 +57,7 @@ Node ShaderIR::GetConstBuffer(u64 index_, u64 offset_) {
const auto index = static_cast<u32>(index_);
const auto offset = static_cast<u32>(offset_);
- const auto [entry, is_new] = used_cbufs.try_emplace(index);
- entry->second.MarkAsUsed(offset);
+ used_cbufs.try_emplace(index).first->second.MarkAsUsed(offset);
return MakeNode<CbufNode>(index, Immediate(offset));
}
@@ -66,8 +66,7 @@ Node ShaderIR::GetConstBufferIndirect(u64 index_, u64 offset_, Node node) {
const auto index = static_cast<u32>(index_);
const auto offset = static_cast<u32>(offset_);
- const auto [entry, is_new] = used_cbufs.try_emplace(index);
- entry->second.MarkAsUsedIndirect();
+ used_cbufs.try_emplace(index).first->second.MarkAsUsedIndirect();
Node final_offset = [&] {
// Attempt to inline constant buffer without a variable offset. This is done to allow
@@ -113,9 +112,9 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
}
Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const {
- const Node node = MakeNode<InternalFlagNode>(flag);
+ Node node = MakeNode<InternalFlagNode>(flag);
if (negated) {
- return Operation(OperationCode::LogicalNegate, node);
+ return Operation(OperationCode::LogicalNegate, std::move(node));
}
return node;
}
@@ -166,6 +165,7 @@ Node ShaderIR::ConvertIntegerSize(Node value, Register::Size size, bool is_signe
std::move(value), Immediate(16));
value = SignedOperation(OperationCode::IArithmeticShiftRight, is_signed, NO_PRECISE,
std::move(value), Immediate(16));
+ return value;
case Register::Size::Word:
// Default - do nothing
return value;
@@ -244,56 +244,44 @@ Node ShaderIR::GetSaturatedHalfFloat(Node value, bool saturate) {
}
Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, Node op_b) {
+ if (condition == PredCondition::T) {
+ return GetPredicate(true);
+ } else if (condition == PredCondition::F) {
+ return GetPredicate(false);
+ }
+
static constexpr std::array comparison_table{
- std::pair{PredCondition::LessThan, OperationCode::LogicalFLessThan},
- std::pair{PredCondition::Equal, OperationCode::LogicalFEqual},
- std::pair{PredCondition::LessEqual, OperationCode::LogicalFLessEqual},
- std::pair{PredCondition::GreaterThan, OperationCode::LogicalFGreaterThan},
- std::pair{PredCondition::NotEqual, OperationCode::LogicalFNotEqual},
- std::pair{PredCondition::GreaterEqual, OperationCode::LogicalFGreaterEqual},
- std::pair{PredCondition::LessThanWithNan, OperationCode::LogicalFLessThan},
- std::pair{PredCondition::NotEqualWithNan, OperationCode::LogicalFNotEqual},
- std::pair{PredCondition::LessEqualWithNan, OperationCode::LogicalFLessEqual},
- std::pair{PredCondition::GreaterThanWithNan, OperationCode::LogicalFGreaterThan},
- std::pair{PredCondition::GreaterEqualWithNan, OperationCode::LogicalFGreaterEqual},
+ OperationCode(0),
+ OperationCode::LogicalFOrdLessThan, // LT
+ OperationCode::LogicalFOrdEqual, // EQ
+ OperationCode::LogicalFOrdLessEqual, // LE
+ OperationCode::LogicalFOrdGreaterThan, // GT
+ OperationCode::LogicalFOrdNotEqual, // NE
+ OperationCode::LogicalFOrdGreaterEqual, // GE
+ OperationCode::LogicalFOrdered, // NUM
+ OperationCode::LogicalFUnordered, // NAN
+ OperationCode::LogicalFUnordLessThan, // LTU
+ OperationCode::LogicalFUnordEqual, // EQU
+ OperationCode::LogicalFUnordLessEqual, // LEU
+ OperationCode::LogicalFUnordGreaterThan, // GTU
+ OperationCode::LogicalFUnordNotEqual, // NEU
+ OperationCode::LogicalFUnordGreaterEqual, // GEU
};
+ const std::size_t index = static_cast<std::size_t>(condition);
+ ASSERT_MSG(index < std::size(comparison_table), "Invalid condition={}", index);
- const auto comparison =
- std::find_if(comparison_table.cbegin(), comparison_table.cend(),
- [condition](const auto entry) { return condition == entry.first; });
- UNIMPLEMENTED_IF_MSG(comparison == comparison_table.cend(),
- "Unknown predicate comparison operation");
-
- Node predicate = Operation(comparison->second, NO_PRECISE, op_a, op_b);
-
- if (condition == PredCondition::LessThanWithNan ||
- condition == PredCondition::NotEqualWithNan ||
- condition == PredCondition::LessEqualWithNan ||
- condition == PredCondition::GreaterThanWithNan ||
- condition == PredCondition::GreaterEqualWithNan) {
- predicate = Operation(OperationCode::LogicalOr, predicate,
- Operation(OperationCode::LogicalFIsNan, op_a));
- predicate = Operation(OperationCode::LogicalOr, predicate,
- Operation(OperationCode::LogicalFIsNan, op_b));
- }
-
- return predicate;
+ return Operation(comparison_table[index], op_a, op_b);
}
Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_signed, Node op_a,
Node op_b) {
static constexpr std::array comparison_table{
- std::pair{PredCondition::LessThan, OperationCode::LogicalILessThan},
- std::pair{PredCondition::Equal, OperationCode::LogicalIEqual},
- std::pair{PredCondition::LessEqual, OperationCode::LogicalILessEqual},
- std::pair{PredCondition::GreaterThan, OperationCode::LogicalIGreaterThan},
- std::pair{PredCondition::NotEqual, OperationCode::LogicalINotEqual},
- std::pair{PredCondition::GreaterEqual, OperationCode::LogicalIGreaterEqual},
- std::pair{PredCondition::LessThanWithNan, OperationCode::LogicalILessThan},
- std::pair{PredCondition::NotEqualWithNan, OperationCode::LogicalINotEqual},
- std::pair{PredCondition::LessEqualWithNan, OperationCode::LogicalILessEqual},
- std::pair{PredCondition::GreaterThanWithNan, OperationCode::LogicalIGreaterThan},
- std::pair{PredCondition::GreaterEqualWithNan, OperationCode::LogicalIGreaterEqual},
+ std::pair{PredCondition::LT, OperationCode::LogicalILessThan},
+ std::pair{PredCondition::EQ, OperationCode::LogicalIEqual},
+ std::pair{PredCondition::LE, OperationCode::LogicalILessEqual},
+ std::pair{PredCondition::GT, OperationCode::LogicalIGreaterThan},
+ std::pair{PredCondition::NE, OperationCode::LogicalINotEqual},
+ std::pair{PredCondition::GE, OperationCode::LogicalIGreaterEqual},
};
const auto comparison =
@@ -302,32 +290,24 @@ Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_si
UNIMPLEMENTED_IF_MSG(comparison == comparison_table.cend(),
"Unknown predicate comparison operation");
- Node predicate = SignedOperation(comparison->second, is_signed, NO_PRECISE, std::move(op_a),
- std::move(op_b));
-
- UNIMPLEMENTED_IF_MSG(condition == PredCondition::LessThanWithNan ||
- condition == PredCondition::NotEqualWithNan ||
- condition == PredCondition::LessEqualWithNan ||
- condition == PredCondition::GreaterThanWithNan ||
- condition == PredCondition::GreaterEqualWithNan,
- "NaN comparisons for integers are not implemented");
- return predicate;
+ return SignedOperation(comparison->second, is_signed, NO_PRECISE, std::move(op_a),
+ std::move(op_b));
}
Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition, Node op_a,
Node op_b) {
static constexpr std::array comparison_table{
- std::pair{PredCondition::LessThan, OperationCode::Logical2HLessThan},
- std::pair{PredCondition::Equal, OperationCode::Logical2HEqual},
- std::pair{PredCondition::LessEqual, OperationCode::Logical2HLessEqual},
- std::pair{PredCondition::GreaterThan, OperationCode::Logical2HGreaterThan},
- std::pair{PredCondition::NotEqual, OperationCode::Logical2HNotEqual},
- std::pair{PredCondition::GreaterEqual, OperationCode::Logical2HGreaterEqual},
- std::pair{PredCondition::LessThanWithNan, OperationCode::Logical2HLessThanWithNan},
- std::pair{PredCondition::NotEqualWithNan, OperationCode::Logical2HNotEqualWithNan},
- std::pair{PredCondition::LessEqualWithNan, OperationCode::Logical2HLessEqualWithNan},
- std::pair{PredCondition::GreaterThanWithNan, OperationCode::Logical2HGreaterThanWithNan},
- std::pair{PredCondition::GreaterEqualWithNan, OperationCode::Logical2HGreaterEqualWithNan},
+ std::pair{PredCondition::LT, OperationCode::Logical2HLessThan},
+ std::pair{PredCondition::EQ, OperationCode::Logical2HEqual},
+ std::pair{PredCondition::LE, OperationCode::Logical2HLessEqual},
+ std::pair{PredCondition::GT, OperationCode::Logical2HGreaterThan},
+ std::pair{PredCondition::NE, OperationCode::Logical2HNotEqual},
+ std::pair{PredCondition::GE, OperationCode::Logical2HGreaterEqual},
+ std::pair{PredCondition::LTU, OperationCode::Logical2HLessThanWithNan},
+ std::pair{PredCondition::LEU, OperationCode::Logical2HLessEqualWithNan},
+ std::pair{PredCondition::GTU, OperationCode::Logical2HGreaterThanWithNan},
+ std::pair{PredCondition::NEU, OperationCode::Logical2HNotEqualWithNan},
+ std::pair{PredCondition::GEU, OperationCode::Logical2HGreaterEqualWithNan},
};
const auto comparison =
@@ -398,7 +378,7 @@ void ShaderIR::SetInternalFlagsFromFloat(NodeBlock& bb, Node value, bool sets_cc
if (!sets_cc) {
return;
}
- Node zerop = Operation(OperationCode::LogicalFEqual, std::move(value), Immediate(0.0f));
+ Node zerop = Operation(OperationCode::LogicalFOrdEqual, std::move(value), Immediate(0.0f));
SetInternalFlag(bb, InternalFlag::Zero, std::move(zerop));
LOG_WARNING(HW_GPU, "Condition codes implementation is incomplete");
}
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index c6e7bdf50..3a98b2104 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -18,6 +18,7 @@
#include "video_core/engines/shader_header.h"
#include "video_core/shader/ast.h"
#include "video_core/shader/compiler_settings.h"
+#include "video_core/shader/memory_util.h"
#include "video_core/shader/node.h"
#include "video_core/shader/registry.h"
@@ -25,16 +26,13 @@ namespace VideoCommon::Shader {
struct ShaderBlock;
-using ProgramCode = std::vector<u64>;
-
constexpr u32 MAX_PROGRAM_LENGTH = 0x1000;
-class ConstBuffer {
-public:
- explicit ConstBuffer(u32 max_offset, bool is_indirect)
+struct ConstBuffer {
+ constexpr explicit ConstBuffer(u32 max_offset, bool is_indirect)
: max_offset{max_offset}, is_indirect{is_indirect} {}
- ConstBuffer() = default;
+ constexpr ConstBuffer() = default;
void MarkAsUsed(u64 offset) {
max_offset = std::max(max_offset, static_cast<u32>(offset));
@@ -57,8 +55,8 @@ public:
}
private:
- u32 max_offset{};
- bool is_indirect{};
+ u32 max_offset = 0;
+ bool is_indirect = false;
};
struct GlobalMemoryUsage {
@@ -192,10 +190,14 @@ private:
friend class ASTDecoder;
struct SamplerInfo {
- Tegra::Shader::TextureType type;
- bool is_array;
- bool is_shadow;
- bool is_buffer;
+ std::optional<Tegra::Shader::TextureType> type;
+ std::optional<bool> is_array;
+ std::optional<bool> is_shadow;
+ std::optional<bool> is_buffer;
+
+ constexpr bool IsComplete() const noexcept {
+ return type && is_array && is_shadow && is_buffer;
+ }
};
void Decode();
@@ -328,16 +330,15 @@ private:
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
/// Queries the missing sampler info from the execution context.
- SamplerInfo GetSamplerInfo(std::optional<SamplerInfo> sampler_info, u32 offset,
- std::optional<u32> buffer = std::nullopt);
+ SamplerInfo GetSamplerInfo(SamplerInfo info,
+ std::optional<Tegra::Engines::SamplerDescriptor> sampler);
- /// Accesses a texture sampler
- const Sampler* GetSampler(const Tegra::Shader::Sampler& sampler,
- std::optional<SamplerInfo> sampler_info = std::nullopt);
+ /// Accesses a texture sampler.
+ std::optional<Sampler> GetSampler(Tegra::Shader::Sampler sampler, SamplerInfo info);
/// Accesses a texture sampler for a bindless texture.
- const Sampler* GetBindlessSampler(Tegra::Shader::Register reg, Node& index_var,
- std::optional<SamplerInfo> sampler_info = std::nullopt);
+ std::optional<Sampler> GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info,
+ Node& index_var);
/// Accesses an image.
Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
@@ -408,8 +409,14 @@ private:
std::tuple<Node, u32, u32> TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const;
- std::tuple<Node, TrackSampler> TrackBindlessSampler(Node tracked, const NodeBlock& code,
- s64 cursor);
+ std::pair<Node, TrackSampler> TrackBindlessSampler(Node tracked, const NodeBlock& code,
+ s64 cursor);
+
+ std::pair<Node, TrackSampler> HandleBindlessIndirectRead(const CbufNode& cbuf,
+ const OperationNode& operation,
+ Node gpr, Node base_offset,
+ Node tracked, const NodeBlock& code,
+ s64 cursor);
std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const;
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index 10739b37d..6be3ea92b 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -14,6 +14,7 @@
namespace VideoCommon::Shader {
namespace {
+
std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
OperationCode operation_code) {
for (; cursor >= 0; --cursor) {
@@ -27,8 +28,9 @@ std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
const auto& conditional_code = conditional->GetCode();
- auto [found, internal_cursor] = FindOperation(
+ auto result = FindOperation(
conditional_code, static_cast<s64>(conditional_code.size() - 1), operation_code);
+ auto& found = result.first;
if (found) {
return {std::move(found), cursor};
}
@@ -62,7 +64,8 @@ bool AmendNodeCv(std::size_t amend_index, Node node) {
if (const auto operation = std::get_if<OperationNode>(&*node)) {
operation->SetAmendIndex(amend_index);
return true;
- } else if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
+ }
+ if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
conditional->SetAmendIndex(amend_index);
return true;
}
@@ -71,39 +74,27 @@ bool AmendNodeCv(std::size_t amend_index, Node node) {
} // Anonymous namespace
-std::tuple<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, const NodeBlock& code,
- s64 cursor) {
+std::pair<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, const NodeBlock& code,
+ s64 cursor) {
if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) {
+ const u32 cbuf_index = cbuf->GetIndex();
+
// Constant buffer found, test if it's an immediate
- const auto offset = cbuf->GetOffset();
+ const auto& offset = cbuf->GetOffset();
if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
- auto track =
- MakeTrackSampler<BindlessSamplerNode>(cbuf->GetIndex(), immediate->GetValue());
+ auto track = MakeTrackSampler<BindlessSamplerNode>(cbuf_index, immediate->GetValue());
return {tracked, track};
- } else if (const auto operation = std::get_if<OperationNode>(&*offset)) {
+ }
+ if (const auto operation = std::get_if<OperationNode>(&*offset)) {
const u32 bound_buffer = registry.GetBoundBuffer();
- if (bound_buffer != cbuf->GetIndex()) {
+ if (bound_buffer != cbuf_index) {
return {};
}
- const auto pair = DecoupleIndirectRead(*operation);
- if (!pair) {
- return {};
+ if (const std::optional pair = DecoupleIndirectRead(*operation)) {
+ auto [gpr, base_offset] = *pair;
+ return HandleBindlessIndirectRead(*cbuf, *operation, gpr, base_offset, tracked,
+ code, cursor);
}
- auto [gpr, base_offset] = *pair;
- const auto offset_inm = std::get_if<ImmediateNode>(&*base_offset);
- const auto& gpu_driver = registry.AccessGuestDriverProfile();
- const u32 bindless_cv = NewCustomVariable();
- const Node op =
- Operation(OperationCode::UDiv, gpr, Immediate(gpu_driver.GetTextureHandlerSize()));
-
- const Node cv_node = GetCustomVariable(bindless_cv);
- Node amend_op = Operation(OperationCode::Assign, cv_node, std::move(op));
- const std::size_t amend_index = DeclareAmend(amend_op);
- AmendNodeCv(amend_index, code[cursor]);
- // TODO Implement Bindless Index custom variable
- auto track = MakeTrackSampler<ArraySamplerNode>(cbuf->GetIndex(),
- offset_inm->GetValue(), bindless_cv);
- return {tracked, track};
}
return {};
}
@@ -120,10 +111,23 @@ std::tuple<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, cons
return TrackBindlessSampler(source, code, new_cursor);
}
if (const auto operation = std::get_if<OperationNode>(&*tracked)) {
- for (std::size_t i = operation->GetOperandsCount(); i > 0; --i) {
- if (auto found = TrackBindlessSampler((*operation)[i - 1], code, cursor);
- std::get<0>(found)) {
- // Cbuf found in operand.
+ const OperationNode& op = *operation;
+
+ const OperationCode opcode = operation->GetCode();
+ if (opcode == OperationCode::IBitwiseOr || opcode == OperationCode::UBitwiseOr) {
+ ASSERT(op.GetOperandsCount() == 2);
+ auto [node_a, index_a, offset_a] = TrackCbuf(op[0], code, cursor);
+ auto [node_b, index_b, offset_b] = TrackCbuf(op[1], code, cursor);
+ if (node_a && node_b) {
+ auto track = MakeTrackSampler<SeparateSamplerNode>(std::pair{index_a, index_b},
+ std::pair{offset_a, offset_b});
+ return {tracked, std::move(track)};
+ }
+ }
+ std::size_t i = op.GetOperandsCount();
+ while (i--) {
+ if (auto found = TrackBindlessSampler(op[i - 1], code, cursor); std::get<0>(found)) {
+ // Constant buffer found in operand.
return found;
}
}
@@ -137,11 +141,31 @@ std::tuple<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, cons
return {};
}
+std::pair<Node, TrackSampler> ShaderIR::HandleBindlessIndirectRead(
+ const CbufNode& cbuf, const OperationNode& operation, Node gpr, Node base_offset, Node tracked,
+ const NodeBlock& code, s64 cursor) {
+ const auto offset_imm = std::get<ImmediateNode>(*base_offset);
+ const auto& gpu_driver = registry.AccessGuestDriverProfile();
+ const u32 bindless_cv = NewCustomVariable();
+ const u32 texture_handler_size = gpu_driver.GetTextureHandlerSize();
+ Node op = Operation(OperationCode::UDiv, gpr, Immediate(texture_handler_size));
+
+ Node cv_node = GetCustomVariable(bindless_cv);
+ Node amend_op = Operation(OperationCode::Assign, std::move(cv_node), std::move(op));
+ const std::size_t amend_index = DeclareAmend(std::move(amend_op));
+ AmendNodeCv(amend_index, code[cursor]);
+
+ // TODO: Implement bindless index custom variable
+ auto track =
+ MakeTrackSampler<ArraySamplerNode>(cbuf.GetIndex(), offset_imm.GetValue(), bindless_cv);
+ return {tracked, track};
+}
+
std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code,
s64 cursor) const {
if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) {
// Constant buffer found, test if it's an immediate
- const auto offset = cbuf->GetOffset();
+ const auto& offset = cbuf->GetOffset();
if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
return {tracked, cbuf->GetIndex(), immediate->GetValue()};
}
@@ -151,21 +175,13 @@ std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& co
if (gpr->GetIndex() == Tegra::Shader::Register::ZeroIndex) {
return {};
}
- s64 current_cursor = cursor;
- while (current_cursor > 0) {
- // Reduce the cursor in one to avoid infinite loops when the instruction sets the same
- // register that it uses as operand
- const auto [source, new_cursor] = TrackRegister(gpr, code, current_cursor - 1);
- current_cursor = new_cursor;
- if (!source) {
- continue;
- }
- const auto [base_address, index, offset] = TrackCbuf(source, code, current_cursor);
- if (base_address != nullptr) {
- return {base_address, index, offset};
- }
+ // Reduce the cursor in one to avoid infinite loops when the instruction sets the same
+ // register that it uses as operand
+ const auto [source, new_cursor] = TrackRegister(gpr, code, cursor - 1);
+ if (!source) {
+ return {};
}
- return {};
+ return TrackCbuf(source, code, new_cursor);
}
if (const auto operation = std::get_if<OperationNode>(&*tracked)) {
for (std::size_t i = operation->GetOperandsCount(); i > 0; --i) {
@@ -186,15 +202,15 @@ std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& co
std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const {
// Reduce the cursor in one to avoid infinite loops when the instruction sets the same register
// that it uses as operand
- const auto [found, found_cursor] =
- TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
+ const auto result = TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
+ const auto& found = result.first;
if (!found) {
- return {};
+ return std::nullopt;
}
if (const auto immediate = std::get_if<ImmediateNode>(&*found)) {
return immediate->GetValue();
}
- return {};
+ return std::nullopt;
}
std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
new file mode 100644
index 000000000..015a789d6
--- /dev/null
+++ b/src/video_core/shader_cache.h
@@ -0,0 +1,240 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace VideoCommon {
+
+template <class T>
+class ShaderCache {
+ static constexpr u64 PAGE_BITS = 14;
+ static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;
+
+ struct Entry {
+ VAddr addr_start;
+ VAddr addr_end;
+ T* data;
+
+ bool is_memory_marked = true;
+
+ constexpr bool Overlaps(VAddr start, VAddr end) const noexcept {
+ return start < addr_end && addr_start < end;
+ }
+ };
+
+public:
+ virtual ~ShaderCache() = default;
+
+ /// @brief Removes shaders inside a given region
+ /// @note Checks for ranges
+ /// @param addr Start address of the invalidation
+ /// @param size Number of bytes of the invalidation
+ void InvalidateRegion(VAddr addr, std::size_t size) {
+ std::scoped_lock lock{invalidation_mutex};
+ InvalidatePagesInRegion(addr, size);
+ RemovePendingShaders();
+ }
+
+ /// @brief Unmarks a memory region as cached and marks it for removal
+ /// @param addr Start address of the CPU write operation
+ /// @param size Number of bytes of the CPU write operation
+ void OnCPUWrite(VAddr addr, std::size_t size) {
+ std::lock_guard lock{invalidation_mutex};
+ InvalidatePagesInRegion(addr, size);
+ }
+
+ /// @brief Flushes delayed removal operations
+ void SyncGuestHost() {
+ std::scoped_lock lock{invalidation_mutex};
+ RemovePendingShaders();
+ }
+
+ /// @brief Tries to obtain a cached shader starting in a given address
+ /// @note Doesn't check for ranges, the given address has to be the start of the shader
+ /// @param addr Start address of the shader, this doesn't cache for region
+ /// @return Pointer to a valid shader, nullptr when nothing is found
+ T* TryGet(VAddr addr) const {
+ std::scoped_lock lock{lookup_mutex};
+
+ const auto it = lookup_cache.find(addr);
+ if (it == lookup_cache.end()) {
+ return nullptr;
+ }
+ return it->second->data;
+ }
+
+protected:
+ explicit ShaderCache(VideoCore::RasterizerInterface& rasterizer_) : rasterizer{rasterizer_} {}
+
+ /// @brief Register in the cache a given entry
+ /// @param data Shader to store in the cache
+ /// @param addr Start address of the shader that will be registered
+ /// @param size Size in bytes of the shader
+ void Register(std::unique_ptr<T> data, VAddr addr, std::size_t size) {
+ std::scoped_lock lock{invalidation_mutex, lookup_mutex};
+
+ const VAddr addr_end = addr + size;
+ Entry* const entry = NewEntry(addr, addr_end, data.get());
+
+ const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
+ invalidation_cache[page].push_back(entry);
+ }
+
+ storage.push_back(std::move(data));
+
+ rasterizer.UpdatePagesCachedCount(addr, size, 1);
+ }
+
+ /// @brief Called when a shader is going to be removed
+ /// @param shader Shader that will be removed
+ /// @pre invalidation_cache is locked
+ /// @pre lookup_mutex is locked
+ virtual void OnShaderRemoval([[maybe_unused]] T* shader) {}
+
+private:
+ /// @brief Invalidate pages in a given region
+ /// @pre invalidation_mutex is locked
+ void InvalidatePagesInRegion(VAddr addr, std::size_t size) {
+ const VAddr addr_end = addr + size;
+ const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
+ auto it = invalidation_cache.find(page);
+ if (it == invalidation_cache.end()) {
+ continue;
+ }
+ InvalidatePageEntries(it->second, addr, addr_end);
+ }
+ }
+
+ /// @brief Remove shaders marked for deletion
+ /// @pre invalidation_mutex is locked
+ void RemovePendingShaders() {
+ if (marked_for_removal.empty()) {
+ return;
+ }
+ // Remove duplicates
+ std::sort(marked_for_removal.begin(), marked_for_removal.end());
+ marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
+ marked_for_removal.end());
+
+ std::vector<T*> removed_shaders;
+ removed_shaders.reserve(marked_for_removal.size());
+
+ std::scoped_lock lock{lookup_mutex};
+
+ for (Entry* const entry : marked_for_removal) {
+ removed_shaders.push_back(entry->data);
+
+ const auto it = lookup_cache.find(entry->addr_start);
+ ASSERT(it != lookup_cache.end());
+ lookup_cache.erase(it);
+ }
+ marked_for_removal.clear();
+
+ if (!removed_shaders.empty()) {
+ RemoveShadersFromStorage(std::move(removed_shaders));
+ }
+ }
+
+ /// @brief Invalidates entries in a given range for the passed page
+ /// @param entries Vector of entries in the page, it will be modified on overlaps
+ /// @param addr Start address of the invalidation
+ /// @param addr_end Non-inclusive end address of the invalidation
+ /// @pre invalidation_mutex is locked
+ void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
+ std::size_t index = 0;
+ while (index < entries.size()) {
+ Entry* const entry = entries[index];
+ if (!entry->Overlaps(addr, addr_end)) {
+ ++index;
+ continue;
+ }
+
+ UnmarkMemory(entry);
+ RemoveEntryFromInvalidationCache(entry);
+ marked_for_removal.push_back(entry);
+ }
+ }
+
+ /// @brief Removes all references to an entry in the invalidation cache
+ /// @param entry Entry to remove from the invalidation cache
+ /// @pre invalidation_mutex is locked
+ void RemoveEntryFromInvalidationCache(const Entry* entry) {
+ const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
+ const auto entries_it = invalidation_cache.find(page);
+ ASSERT(entries_it != invalidation_cache.end());
+ std::vector<Entry*>& entries = entries_it->second;
+
+ const auto entry_it = std::find(entries.begin(), entries.end(), entry);
+ ASSERT(entry_it != entries.end());
+ entries.erase(entry_it);
+ }
+ }
+
+ /// @brief Unmarks an entry from the rasterizer cache
+ /// @param entry Entry to unmark from memory
+ void UnmarkMemory(Entry* entry) {
+ if (!entry->is_memory_marked) {
+ return;
+ }
+ entry->is_memory_marked = false;
+
+ const VAddr addr = entry->addr_start;
+ const std::size_t size = entry->addr_end - addr;
+ rasterizer.UpdatePagesCachedCount(addr, size, -1);
+ }
+
+ /// @brief Removes a vector of shaders from a list
+ /// @param removed_shaders Shaders to be removed from the storage
+ /// @pre invalidation_mutex is locked
+ /// @pre lookup_mutex is locked
+ void RemoveShadersFromStorage(std::vector<T*> removed_shaders) {
+ // Notify removals
+ for (T* const shader : removed_shaders) {
+ OnShaderRemoval(shader);
+ }
+
+ // Remove them from the cache
+ const auto is_removed = [&removed_shaders](const std::unique_ptr<T>& shader) {
+ return std::find(removed_shaders.begin(), removed_shaders.end(), shader.get()) !=
+ removed_shaders.end();
+ };
+ std::erase_if(storage, is_removed);
+ }
+
+ /// @brief Creates a new entry in the lookup cache and returns its pointer
+ /// @pre lookup_mutex is locked
+ Entry* NewEntry(VAddr addr, VAddr addr_end, T* data) {
+ auto entry = std::make_unique<Entry>(Entry{addr, addr_end, data});
+ Entry* const entry_pointer = entry.get();
+
+ lookup_cache.emplace(addr, std::move(entry));
+ return entry_pointer;
+ }
+
+ VideoCore::RasterizerInterface& rasterizer;
+
+ mutable std::mutex lookup_mutex;
+ std::mutex invalidation_mutex;
+
+ std::unordered_map<u64, std::unique_ptr<Entry>> lookup_cache;
+ std::unordered_map<u64, std::vector<Entry*>> invalidation_cache;
+ std::vector<std::unique_ptr<T>> storage;
+ std::vector<Entry*> marked_for_removal;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/shader_notify.cpp b/src/video_core/shader_notify.cpp
new file mode 100644
index 000000000..c3c71657d
--- /dev/null
+++ b/src/video_core/shader_notify.cpp
@@ -0,0 +1,42 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/shader_notify.h"
+
+using namespace std::chrono_literals;
+
+namespace VideoCore {
+namespace {
+constexpr auto UPDATE_TICK = 32ms;
+}
+
+ShaderNotify::ShaderNotify() = default;
+ShaderNotify::~ShaderNotify() = default;
+
+std::size_t ShaderNotify::GetShadersBuilding() {
+ const auto now = std::chrono::high_resolution_clock::now();
+ const auto diff = now - last_update;
+ if (diff > UPDATE_TICK) {
+ std::shared_lock lock(mutex);
+ last_updated_count = accurate_count;
+ }
+ return last_updated_count;
+}
+
+std::size_t ShaderNotify::GetShadersBuildingAccurate() {
+ std::shared_lock lock{mutex};
+ return accurate_count;
+}
+
+void ShaderNotify::MarkShaderComplete() {
+ std::unique_lock lock{mutex};
+ accurate_count--;
+}
+
+void ShaderNotify::MarkSharderBuilding() {
+ std::unique_lock lock{mutex};
+ accurate_count++;
+}
+
+} // namespace VideoCore
diff --git a/src/video_core/shader_notify.h b/src/video_core/shader_notify.h
new file mode 100644
index 000000000..a9c92d179
--- /dev/null
+++ b/src/video_core/shader_notify.h
@@ -0,0 +1,29 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <shared_mutex>
+#include "common/common_types.h"
+
+namespace VideoCore {
+class ShaderNotify {
+public:
+ ShaderNotify();
+ ~ShaderNotify();
+
+ std::size_t GetShadersBuilding();
+ std::size_t GetShadersBuildingAccurate();
+
+ void MarkShaderComplete();
+ void MarkSharderBuilding();
+
+private:
+ std::size_t last_updated_count{};
+ std::size_t accurate_count{};
+ std::shared_mutex mutex;
+ std::chrono::high_resolution_clock::time_point last_update{};
+};
+} // namespace VideoCore
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index cc7181229..1688267bb 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -74,115 +74,131 @@ bool SurfaceTargetIsArray(SurfaceTarget target) {
PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
switch (format) {
- case Tegra::DepthFormat::S8_Z24_UNORM:
- return PixelFormat::S8Z24;
- case Tegra::DepthFormat::Z24_S8_UNORM:
- return PixelFormat::Z24S8;
- case Tegra::DepthFormat::Z32_FLOAT:
- return PixelFormat::Z32F;
- case Tegra::DepthFormat::Z16_UNORM:
- return PixelFormat::Z16;
- case Tegra::DepthFormat::Z32_S8_X24_FLOAT:
- return PixelFormat::Z32FS8;
+ case Tegra::DepthFormat::S8_UINT_Z24_UNORM:
+ return PixelFormat::S8_UINT_D24_UNORM;
+ case Tegra::DepthFormat::D24S8_UNORM:
+ return PixelFormat::D24_UNORM_S8_UINT;
+ case Tegra::DepthFormat::D32_FLOAT:
+ return PixelFormat::D32_FLOAT;
+ case Tegra::DepthFormat::D16_UNORM:
+ return PixelFormat::D16_UNORM;
+ case Tegra::DepthFormat::D32_FLOAT_S8X24_UINT:
+ return PixelFormat::D32_FLOAT_S8_UINT;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- return PixelFormat::S8Z24;
+ UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
+ return PixelFormat::S8_UINT_D24_UNORM;
}
}
PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
switch (format) {
- case Tegra::RenderTargetFormat::RGBA8_SRGB:
- return PixelFormat::RGBA8_SRGB;
- case Tegra::RenderTargetFormat::RGBA8_UNORM:
- return PixelFormat::ABGR8U;
- case Tegra::RenderTargetFormat::RGBA8_SNORM:
- return PixelFormat::ABGR8S;
- case Tegra::RenderTargetFormat::RGBA8_UINT:
- return PixelFormat::ABGR8UI;
- case Tegra::RenderTargetFormat::BGRA8_SRGB:
- return PixelFormat::BGRA8_SRGB;
- case Tegra::RenderTargetFormat::BGRA8_UNORM:
- return PixelFormat::BGRA8;
- case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
- return PixelFormat::A2B10G10R10U;
- case Tegra::RenderTargetFormat::RGBA16_FLOAT:
- return PixelFormat::RGBA16F;
- case Tegra::RenderTargetFormat::RGBA16_UNORM:
- return PixelFormat::RGBA16U;
- case Tegra::RenderTargetFormat::RGBA16_SNORM:
- return PixelFormat::RGBA16S;
- case Tegra::RenderTargetFormat::RGBA16_UINT:
- return PixelFormat::RGBA16UI;
- case Tegra::RenderTargetFormat::RGBA32_FLOAT:
- return PixelFormat::RGBA32F;
- case Tegra::RenderTargetFormat::RG32_FLOAT:
- return PixelFormat::RG32F;
- case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
- return PixelFormat::R11FG11FB10F;
- case Tegra::RenderTargetFormat::B5G6R5_UNORM:
- return PixelFormat::B5G6R5U;
- case Tegra::RenderTargetFormat::BGR5A1_UNORM:
- return PixelFormat::A1B5G5R5U;
- case Tegra::RenderTargetFormat::RGBA32_UINT:
- return PixelFormat::RGBA32UI;
- case Tegra::RenderTargetFormat::R8_UNORM:
- return PixelFormat::R8U;
- case Tegra::RenderTargetFormat::R8_UINT:
- return PixelFormat::R8UI;
- case Tegra::RenderTargetFormat::RG16_FLOAT:
- return PixelFormat::RG16F;
- case Tegra::RenderTargetFormat::RG16_UINT:
- return PixelFormat::RG16UI;
- case Tegra::RenderTargetFormat::RG16_SINT:
- return PixelFormat::RG16I;
- case Tegra::RenderTargetFormat::RG16_UNORM:
- return PixelFormat::RG16;
- case Tegra::RenderTargetFormat::RG16_SNORM:
- return PixelFormat::RG16S;
- case Tegra::RenderTargetFormat::RG8_UNORM:
- return PixelFormat::RG8U;
- case Tegra::RenderTargetFormat::RG8_SNORM:
- return PixelFormat::RG8S;
- case Tegra::RenderTargetFormat::R16_FLOAT:
- return PixelFormat::R16F;
+ case Tegra::RenderTargetFormat::R32B32G32A32_FLOAT:
+ return PixelFormat::R32G32B32A32_FLOAT;
+ case Tegra::RenderTargetFormat::R32G32B32A32_SINT:
+ return PixelFormat::R32G32B32A32_SINT;
+ case Tegra::RenderTargetFormat::R32G32B32A32_UINT:
+ return PixelFormat::R32G32B32A32_UINT;
+ case Tegra::RenderTargetFormat::R16G16B16A16_UNORM:
+ return PixelFormat::R16G16B16A16_UNORM;
+ case Tegra::RenderTargetFormat::R16G16B16A16_SNORM:
+ return PixelFormat::R16G16B16A16_SNORM;
+ case Tegra::RenderTargetFormat::R16G16B16A16_SINT:
+ return PixelFormat::R16G16B16A16_SINT;
+ case Tegra::RenderTargetFormat::R16G16B16A16_UINT:
+ return PixelFormat::R16G16B16A16_UINT;
+ case Tegra::RenderTargetFormat::R16G16B16A16_FLOAT:
+ return PixelFormat::R16G16B16A16_FLOAT;
+ case Tegra::RenderTargetFormat::R32G32_FLOAT:
+ return PixelFormat::R32G32_FLOAT;
+ case Tegra::RenderTargetFormat::R32G32_SINT:
+ return PixelFormat::R32G32_SINT;
+ case Tegra::RenderTargetFormat::R32G32_UINT:
+ return PixelFormat::R32G32_UINT;
+ case Tegra::RenderTargetFormat::R16G16B16X16_FLOAT:
+ return PixelFormat::R16G16B16X16_FLOAT;
+ case Tegra::RenderTargetFormat::B8G8R8A8_UNORM:
+ return PixelFormat::B8G8R8A8_UNORM;
+ case Tegra::RenderTargetFormat::B8G8R8A8_SRGB:
+ return PixelFormat::B8G8R8A8_SRGB;
+ case Tegra::RenderTargetFormat::A2B10G10R10_UNORM:
+ return PixelFormat::A2B10G10R10_UNORM;
+ case Tegra::RenderTargetFormat::A2B10G10R10_UINT:
+ return PixelFormat::A2B10G10R10_UINT;
+ case Tegra::RenderTargetFormat::A8B8G8R8_UNORM:
+ return PixelFormat::A8B8G8R8_UNORM;
+ case Tegra::RenderTargetFormat::A8B8G8R8_SRGB:
+ return PixelFormat::A8B8G8R8_SRGB;
+ case Tegra::RenderTargetFormat::A8B8G8R8_SNORM:
+ return PixelFormat::A8B8G8R8_SNORM;
+ case Tegra::RenderTargetFormat::A8B8G8R8_SINT:
+ return PixelFormat::A8B8G8R8_SINT;
+ case Tegra::RenderTargetFormat::A8B8G8R8_UINT:
+ return PixelFormat::A8B8G8R8_UINT;
+ case Tegra::RenderTargetFormat::R16G16_UNORM:
+ return PixelFormat::R16G16_UNORM;
+ case Tegra::RenderTargetFormat::R16G16_SNORM:
+ return PixelFormat::R16G16_SNORM;
+ case Tegra::RenderTargetFormat::R16G16_SINT:
+ return PixelFormat::R16G16_SINT;
+ case Tegra::RenderTargetFormat::R16G16_UINT:
+ return PixelFormat::R16G16_UINT;
+ case Tegra::RenderTargetFormat::R16G16_FLOAT:
+ return PixelFormat::R16G16_FLOAT;
+ case Tegra::RenderTargetFormat::B10G11R11_FLOAT:
+ return PixelFormat::B10G11R11_FLOAT;
+ case Tegra::RenderTargetFormat::R32_SINT:
+ return PixelFormat::R32_SINT;
+ case Tegra::RenderTargetFormat::R32_UINT:
+ return PixelFormat::R32_UINT;
+ case Tegra::RenderTargetFormat::R32_FLOAT:
+ return PixelFormat::R32_FLOAT;
+ case Tegra::RenderTargetFormat::R5G6B5_UNORM:
+ return PixelFormat::R5G6B5_UNORM;
+ case Tegra::RenderTargetFormat::A1R5G5B5_UNORM:
+ return PixelFormat::A1R5G5B5_UNORM;
+ case Tegra::RenderTargetFormat::R8G8_UNORM:
+ return PixelFormat::R8G8_UNORM;
+ case Tegra::RenderTargetFormat::R8G8_SNORM:
+ return PixelFormat::R8G8_SNORM;
+ case Tegra::RenderTargetFormat::R8G8_SINT:
+ return PixelFormat::R8G8_SINT;
+ case Tegra::RenderTargetFormat::R8G8_UINT:
+ return PixelFormat::R8G8_UINT;
case Tegra::RenderTargetFormat::R16_UNORM:
- return PixelFormat::R16U;
+ return PixelFormat::R16_UNORM;
case Tegra::RenderTargetFormat::R16_SNORM:
- return PixelFormat::R16S;
- case Tegra::RenderTargetFormat::R16_UINT:
- return PixelFormat::R16UI;
+ return PixelFormat::R16_SNORM;
case Tegra::RenderTargetFormat::R16_SINT:
- return PixelFormat::R16I;
- case Tegra::RenderTargetFormat::R32_FLOAT:
- return PixelFormat::R32F;
- case Tegra::RenderTargetFormat::R32_SINT:
- return PixelFormat::R32I;
- case Tegra::RenderTargetFormat::R32_UINT:
- return PixelFormat::R32UI;
- case Tegra::RenderTargetFormat::RG32_UINT:
- return PixelFormat::RG32UI;
- case Tegra::RenderTargetFormat::RGBX16_FLOAT:
- return PixelFormat::RGBX16F;
+ return PixelFormat::R16_SINT;
+ case Tegra::RenderTargetFormat::R16_UINT:
+ return PixelFormat::R16_UINT;
+ case Tegra::RenderTargetFormat::R16_FLOAT:
+ return PixelFormat::R16_FLOAT;
+ case Tegra::RenderTargetFormat::R8_UNORM:
+ return PixelFormat::R8_UNORM;
+ case Tegra::RenderTargetFormat::R8_SNORM:
+ return PixelFormat::R8_SNORM;
+ case Tegra::RenderTargetFormat::R8_SINT:
+ return PixelFormat::R8_SINT;
+ case Tegra::RenderTargetFormat::R8_UINT:
+ return PixelFormat::R8_UINT;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- return PixelFormat::RGBA8_SRGB;
+ UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<int>(format));
+ return PixelFormat::A8B8G8R8_UNORM;
}
}
PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
switch (format) {
- case Tegra::FramebufferConfig::PixelFormat::ABGR8:
- return PixelFormat::ABGR8U;
- case Tegra::FramebufferConfig::PixelFormat::RGB565:
- return PixelFormat::B5G6R5U;
- case Tegra::FramebufferConfig::PixelFormat::BGRA8:
- return PixelFormat::BGRA8;
+ case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
+ return PixelFormat::A8B8G8R8_UNORM;
+ case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
+ return PixelFormat::R5G6B5_UNORM;
+ case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM:
+ return PixelFormat::B8G8R8A8_UNORM;
default:
UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
- return PixelFormat::ABGR8U;
+ return PixelFormat::A8B8G8R8_UNORM;
}
}
@@ -210,27 +226,27 @@ SurfaceType GetFormatType(PixelFormat pixel_format) {
bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
- case PixelFormat::ASTC_2D_4X4:
- case PixelFormat::ASTC_2D_5X4:
- case PixelFormat::ASTC_2D_5X5:
- case PixelFormat::ASTC_2D_8X8:
- case PixelFormat::ASTC_2D_8X5:
+ case PixelFormat::ASTC_2D_4X4_UNORM:
+ case PixelFormat::ASTC_2D_5X4_UNORM:
+ case PixelFormat::ASTC_2D_5X5_UNORM:
+ case PixelFormat::ASTC_2D_8X8_UNORM:
+ case PixelFormat::ASTC_2D_8X5_UNORM:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_5X5_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
- case PixelFormat::ASTC_2D_10X8:
+ case PixelFormat::ASTC_2D_10X8_UNORM:
case PixelFormat::ASTC_2D_10X8_SRGB:
- case PixelFormat::ASTC_2D_6X6:
+ case PixelFormat::ASTC_2D_6X6_UNORM:
case PixelFormat::ASTC_2D_6X6_SRGB:
- case PixelFormat::ASTC_2D_10X10:
+ case PixelFormat::ASTC_2D_10X10_UNORM:
case PixelFormat::ASTC_2D_10X10_SRGB:
- case PixelFormat::ASTC_2D_12X12:
+ case PixelFormat::ASTC_2D_12X12_UNORM:
case PixelFormat::ASTC_2D_12X12_SRGB:
- case PixelFormat::ASTC_2D_8X6:
+ case PixelFormat::ASTC_2D_8X6_UNORM:
case PixelFormat::ASTC_2D_8X6_SRGB:
- case PixelFormat::ASTC_2D_6X5:
+ case PixelFormat::ASTC_2D_6X5_UNORM:
case PixelFormat::ASTC_2D_6X5_SRGB:
return true;
default:
@@ -240,12 +256,12 @@ bool IsPixelFormatASTC(PixelFormat format) {
bool IsPixelFormatSRGB(PixelFormat format) {
switch (format) {
- case PixelFormat::RGBA8_SRGB:
- case PixelFormat::BGRA8_SRGB:
- case PixelFormat::DXT1_SRGB:
- case PixelFormat::DXT23_SRGB:
- case PixelFormat::DXT45_SRGB:
- case PixelFormat::BC7U_SRGB:
+ case PixelFormat::A8B8G8R8_SRGB:
+ case PixelFormat::B8G8R8A8_SRGB:
+ case PixelFormat::BC1_RGBA_SRGB:
+ case PixelFormat::BC2_SRGB:
+ case PixelFormat::BC3_SRGB:
+ case PixelFormat::BC7_SRGB:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
@@ -267,25 +283,4 @@ std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)};
}
-bool IsFormatBCn(PixelFormat format) {
- switch (format) {
- case PixelFormat::DXT1:
- case PixelFormat::DXT23:
- case PixelFormat::DXT45:
- case PixelFormat::DXN1:
- case PixelFormat::DXN2SNORM:
- case PixelFormat::DXN2UNORM:
- case PixelFormat::BC7U:
- case PixelFormat::BC6H_UF16:
- case PixelFormat::BC6H_SF16:
- case PixelFormat::DXT1_SRGB:
- case PixelFormat::DXT23_SRGB:
- case PixelFormat::DXT45_SRGB:
- case PixelFormat::BC7U_SRGB:
- return true;
- default:
- return false;
- }
-}
-
} // namespace VideoCore::Surface
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index e0acd44d3..cfd12fa61 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -15,93 +15,105 @@
namespace VideoCore::Surface {
enum class PixelFormat {
- ABGR8U = 0,
- ABGR8S = 1,
- ABGR8UI = 2,
- B5G6R5U = 3,
- A2B10G10R10U = 4,
- A1B5G5R5U = 5,
- R8U = 6,
- R8UI = 7,
- RGBA16F = 8,
- RGBA16U = 9,
- RGBA16S = 10,
- RGBA16UI = 11,
- R11FG11FB10F = 12,
- RGBA32UI = 13,
- DXT1 = 14,
- DXT23 = 15,
- DXT45 = 16,
- DXN1 = 17, // This is also known as BC4
- DXN2UNORM = 18,
- DXN2SNORM = 19,
- BC7U = 20,
- BC6H_UF16 = 21,
- BC6H_SF16 = 22,
- ASTC_2D_4X4 = 23,
- BGRA8 = 24,
- RGBA32F = 25,
- RG32F = 26,
- R32F = 27,
- R16F = 28,
- R16U = 29,
- R16S = 30,
- R16UI = 31,
- R16I = 32,
- RG16 = 33,
- RG16F = 34,
- RG16UI = 35,
- RG16I = 36,
- RG16S = 37,
- RGB32F = 38,
- RGBA8_SRGB = 39,
- RG8U = 40,
- RG8S = 41,
- RG32UI = 42,
- RGBX16F = 43,
- R32UI = 44,
- R32I = 45,
- ASTC_2D_8X8 = 46,
- ASTC_2D_8X5 = 47,
- ASTC_2D_5X4 = 48,
- BGRA8_SRGB = 49,
- DXT1_SRGB = 50,
- DXT23_SRGB = 51,
- DXT45_SRGB = 52,
- BC7U_SRGB = 53,
- R4G4B4A4U = 54,
- ASTC_2D_4X4_SRGB = 55,
- ASTC_2D_8X8_SRGB = 56,
- ASTC_2D_8X5_SRGB = 57,
- ASTC_2D_5X4_SRGB = 58,
- ASTC_2D_5X5 = 59,
- ASTC_2D_5X5_SRGB = 60,
- ASTC_2D_10X8 = 61,
- ASTC_2D_10X8_SRGB = 62,
- ASTC_2D_6X6 = 63,
- ASTC_2D_6X6_SRGB = 64,
- ASTC_2D_10X10 = 65,
- ASTC_2D_10X10_SRGB = 66,
- ASTC_2D_12X12 = 67,
- ASTC_2D_12X12_SRGB = 68,
- ASTC_2D_8X6 = 69,
- ASTC_2D_8X6_SRGB = 70,
- ASTC_2D_6X5 = 71,
- ASTC_2D_6X5_SRGB = 72,
- E5B9G9R9F = 73,
+ A8B8G8R8_UNORM,
+ A8B8G8R8_SNORM,
+ A8B8G8R8_SINT,
+ A8B8G8R8_UINT,
+ R5G6B5_UNORM,
+ B5G6R5_UNORM,
+ A1R5G5B5_UNORM,
+ A2B10G10R10_UNORM,
+ A2B10G10R10_UINT,
+ A1B5G5R5_UNORM,
+ R8_UNORM,
+ R8_SNORM,
+ R8_SINT,
+ R8_UINT,
+ R16G16B16A16_FLOAT,
+ R16G16B16A16_UNORM,
+ R16G16B16A16_SNORM,
+ R16G16B16A16_SINT,
+ R16G16B16A16_UINT,
+ B10G11R11_FLOAT,
+ R32G32B32A32_UINT,
+ BC1_RGBA_UNORM,
+ BC2_UNORM,
+ BC3_UNORM,
+ BC4_UNORM,
+ BC4_SNORM,
+ BC5_UNORM,
+ BC5_SNORM,
+ BC7_UNORM,
+ BC6H_UFLOAT,
+ BC6H_SFLOAT,
+ ASTC_2D_4X4_UNORM,
+ B8G8R8A8_UNORM,
+ R32G32B32A32_FLOAT,
+ R32G32B32A32_SINT,
+ R32G32_FLOAT,
+ R32G32_SINT,
+ R32_FLOAT,
+ R16_FLOAT,
+ R16_UNORM,
+ R16_SNORM,
+ R16_UINT,
+ R16_SINT,
+ R16G16_UNORM,
+ R16G16_FLOAT,
+ R16G16_UINT,
+ R16G16_SINT,
+ R16G16_SNORM,
+ R32G32B32_FLOAT,
+ A8B8G8R8_SRGB,
+ R8G8_UNORM,
+ R8G8_SNORM,
+ R8G8_SINT,
+ R8G8_UINT,
+ R32G32_UINT,
+ R16G16B16X16_FLOAT,
+ R32_UINT,
+ R32_SINT,
+ ASTC_2D_8X8_UNORM,
+ ASTC_2D_8X5_UNORM,
+ ASTC_2D_5X4_UNORM,
+ B8G8R8A8_SRGB,
+ BC1_RGBA_SRGB,
+ BC2_SRGB,
+ BC3_SRGB,
+ BC7_SRGB,
+ A4B4G4R4_UNORM,
+ ASTC_2D_4X4_SRGB,
+ ASTC_2D_8X8_SRGB,
+ ASTC_2D_8X5_SRGB,
+ ASTC_2D_5X4_SRGB,
+ ASTC_2D_5X5_UNORM,
+ ASTC_2D_5X5_SRGB,
+ ASTC_2D_10X8_UNORM,
+ ASTC_2D_10X8_SRGB,
+ ASTC_2D_6X6_UNORM,
+ ASTC_2D_6X6_SRGB,
+ ASTC_2D_10X10_UNORM,
+ ASTC_2D_10X10_SRGB,
+ ASTC_2D_12X12_UNORM,
+ ASTC_2D_12X12_SRGB,
+ ASTC_2D_8X6_UNORM,
+ ASTC_2D_8X6_SRGB,
+ ASTC_2D_6X5_UNORM,
+ ASTC_2D_6X5_SRGB,
+ E5B9G9R9_FLOAT,
MaxColorFormat,
// Depth formats
- Z32F = 74,
- Z16 = 75,
+ D32_FLOAT = MaxColorFormat,
+ D16_UNORM,
MaxDepthFormat,
// DepthStencil formats
- Z24S8 = 76,
- S8Z24 = 77,
- Z32FS8 = 78,
+ D24_UNORM_S8_UINT = MaxDepthFormat,
+ S8_UINT_D24_UNORM,
+ D32_FLOAT_S8_UINT,
MaxDepthStencilFormat,
@@ -129,85 +141,97 @@ enum class SurfaceTarget {
};
constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{
- 0, // ABGR8U
- 0, // ABGR8S
- 0, // ABGR8UI
- 0, // B5G6R5U
- 0, // A2B10G10R10U
- 0, // A1B5G5R5U
- 0, // R8U
- 0, // R8UI
- 0, // RGBA16F
- 0, // RGBA16U
- 0, // RGBA16S
- 0, // RGBA16UI
- 0, // R11FG11FB10F
- 0, // RGBA32UI
- 2, // DXT1
- 2, // DXT23
- 2, // DXT45
- 2, // DXN1
- 2, // DXN2UNORM
- 2, // DXN2SNORM
- 2, // BC7U
- 2, // BC6H_UF16
- 2, // BC6H_SF16
- 2, // ASTC_2D_4X4
- 0, // BGRA8
- 0, // RGBA32F
- 0, // RG32F
- 0, // R32F
- 0, // R16F
- 0, // R16U
- 0, // R16S
- 0, // R16UI
- 0, // R16I
- 0, // RG16
- 0, // RG16F
- 0, // RG16UI
- 0, // RG16I
- 0, // RG16S
- 0, // RGB32F
- 0, // RGBA8_SRGB
- 0, // RG8U
- 0, // RG8S
- 0, // RG32UI
- 0, // RGBX16F
- 0, // R32UI
- 0, // R32I
- 2, // ASTC_2D_8X8
- 2, // ASTC_2D_8X5
- 2, // ASTC_2D_5X4
- 0, // BGRA8_SRGB
- 2, // DXT1_SRGB
- 2, // DXT23_SRGB
- 2, // DXT45_SRGB
- 2, // BC7U_SRGB
- 0, // R4G4B4A4U
+ 0, // A8B8G8R8_UNORM
+ 0, // A8B8G8R8_SNORM
+ 0, // A8B8G8R8_SINT
+ 0, // A8B8G8R8_UINT
+ 0, // R5G6B5_UNORM
+ 0, // B5G6R5_UNORM
+ 0, // A1R5G5B5_UNORM
+ 0, // A2B10G10R10_UNORM
+ 0, // A2B10G10R10_UINT
+ 0, // A1B5G5R5_UNORM
+ 0, // R8_UNORM
+ 0, // R8_SNORM
+ 0, // R8_SINT
+ 0, // R8_UINT
+ 0, // R16G16B16A16_FLOAT
+ 0, // R16G16B16A16_UNORM
+ 0, // R16G16B16A16_SNORM
+ 0, // R16G16B16A16_SINT
+ 0, // R16G16B16A16_UINT
+ 0, // B10G11R11_FLOAT
+ 0, // R32G32B32A32_UINT
+ 2, // BC1_RGBA_UNORM
+ 2, // BC2_UNORM
+ 2, // BC3_UNORM
+ 2, // BC4_UNORM
+ 2, // BC4_SNORM
+ 2, // BC5_UNORM
+ 2, // BC5_SNORM
+ 2, // BC7_UNORM
+ 2, // BC6H_UFLOAT
+ 2, // BC6H_SFLOAT
+ 2, // ASTC_2D_4X4_UNORM
+ 0, // B8G8R8A8_UNORM
+ 0, // R32G32B32A32_FLOAT
+ 0, // R32G32B32A32_SINT
+ 0, // R32G32_FLOAT
+ 0, // R32G32_SINT
+ 0, // R32_FLOAT
+ 0, // R16_FLOAT
+ 0, // R16_UNORM
+ 0, // R16_SNORM
+ 0, // R16_UINT
+ 0, // R16_SINT
+ 0, // R16G16_UNORM
+ 0, // R16G16_FLOAT
+ 0, // R16G16_UINT
+ 0, // R16G16_SINT
+ 0, // R16G16_SNORM
+ 0, // R32G32B32_FLOAT
+ 0, // A8B8G8R8_SRGB
+ 0, // R8G8_UNORM
+ 0, // R8G8_SNORM
+ 0, // R8G8_SINT
+ 0, // R8G8_UINT
+ 0, // R32G32_UINT
+ 0, // R16G16B16X16_FLOAT
+ 0, // R32_UINT
+ 0, // R32_SINT
+ 2, // ASTC_2D_8X8_UNORM
+ 2, // ASTC_2D_8X5_UNORM
+ 2, // ASTC_2D_5X4_UNORM
+ 0, // B8G8R8A8_SRGB
+ 2, // BC1_RGBA_SRGB
+ 2, // BC2_SRGB
+ 2, // BC3_SRGB
+ 2, // BC7_SRGB
+ 0, // A4B4G4R4_UNORM
2, // ASTC_2D_4X4_SRGB
2, // ASTC_2D_8X8_SRGB
2, // ASTC_2D_8X5_SRGB
2, // ASTC_2D_5X4_SRGB
- 2, // ASTC_2D_5X5
+ 2, // ASTC_2D_5X5_UNORM
2, // ASTC_2D_5X5_SRGB
- 2, // ASTC_2D_10X8
+ 2, // ASTC_2D_10X8_UNORM
2, // ASTC_2D_10X8_SRGB
- 2, // ASTC_2D_6X6
+ 2, // ASTC_2D_6X6_UNORM
2, // ASTC_2D_6X6_SRGB
- 2, // ASTC_2D_10X10
+ 2, // ASTC_2D_10X10_UNORM
2, // ASTC_2D_10X10_SRGB
- 2, // ASTC_2D_12X12
+ 2, // ASTC_2D_12X12_UNORM
2, // ASTC_2D_12X12_SRGB
- 2, // ASTC_2D_8X6
+ 2, // ASTC_2D_8X6_UNORM
2, // ASTC_2D_8X6_SRGB
- 2, // ASTC_2D_6X5
+ 2, // ASTC_2D_6X5_UNORM
2, // ASTC_2D_6X5_SRGB
- 0, // E5B9G9R9F
- 0, // Z32F
- 0, // Z16
- 0, // Z24S8
- 0, // S8Z24
- 0, // Z32FS8
+ 0, // E5B9G9R9_FLOAT
+ 0, // D32_FLOAT
+ 0, // D16_UNORM
+ 0, // D24_UNORM_S8_UINT
+ 0, // S8_UINT_D24_UNORM
+ 0, // D32_FLOAT_S8_UINT
}};
/**
@@ -227,85 +251,97 @@ inline constexpr u32 GetCompressionFactor(PixelFormat format) {
}
constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16S
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // RGBA8_SRGB
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // RGBX16F
- 1, // R32UI
- 1, // R32I
- 8, // ASTC_2D_8X8
- 8, // ASTC_2D_8X5
- 5, // ASTC_2D_5X4
- 1, // BGRA8_SRGB
- 4, // DXT1_SRGB
- 4, // DXT23_SRGB
- 4, // DXT45_SRGB
- 4, // BC7U_SRGB
- 1, // R4G4B4A4U
+ 1, // A8B8G8R8_UNORM
+ 1, // A8B8G8R8_SNORM
+ 1, // A8B8G8R8_SINT
+ 1, // A8B8G8R8_UINT
+ 1, // R5G6B5_UNORM
+ 1, // B5G6R5_UNORM
+ 1, // A1R5G5B5_UNORM
+ 1, // A2B10G10R10_UNORM
+ 1, // A2B10G10R10_UINT
+ 1, // A1B5G5R5_UNORM
+ 1, // R8_UNORM
+ 1, // R8_SNORM
+ 1, // R8_SINT
+ 1, // R8_UINT
+ 1, // R16G16B16A16_FLOAT
+ 1, // R16G16B16A16_UNORM
+ 1, // R16G16B16A16_SNORM
+ 1, // R16G16B16A16_SINT
+ 1, // R16G16B16A16_UINT
+ 1, // B10G11R11_FLOAT
+ 1, // R32G32B32A32_UINT
+ 4, // BC1_RGBA_UNORM
+ 4, // BC2_UNORM
+ 4, // BC3_UNORM
+ 4, // BC4_UNORM
+ 4, // BC4_SNORM
+ 4, // BC5_UNORM
+ 4, // BC5_SNORM
+ 4, // BC7_UNORM
+ 4, // BC6H_UFLOAT
+ 4, // BC6H_SFLOAT
+ 4, // ASTC_2D_4X4_UNORM
+ 1, // B8G8R8A8_UNORM
+ 1, // R32G32B32A32_FLOAT
+ 1, // R32G32B32A32_SINT
+ 1, // R32G32_FLOAT
+ 1, // R32G32_SINT
+ 1, // R32_FLOAT
+ 1, // R16_FLOAT
+ 1, // R16_UNORM
+ 1, // R16_SNORM
+ 1, // R16_UINT
+ 1, // R16_SINT
+ 1, // R16G16_UNORM
+ 1, // R16G16_FLOAT
+ 1, // R16G16_UINT
+ 1, // R16G16_SINT
+ 1, // R16G16_SNORM
+ 1, // R32G32B32_FLOAT
+ 1, // A8B8G8R8_SRGB
+ 1, // R8G8_UNORM
+ 1, // R8G8_SNORM
+ 1, // R8G8_SINT
+ 1, // R8G8_UINT
+ 1, // R32G32_UINT
+ 1, // R16G16B16X16_FLOAT
+ 1, // R32_UINT
+ 1, // R32_SINT
+ 8, // ASTC_2D_8X8_UNORM
+ 8, // ASTC_2D_8X5_UNORM
+ 5, // ASTC_2D_5X4_UNORM
+ 1, // B8G8R8A8_SRGB
+ 4, // BC1_RGBA_SRGB
+ 4, // BC2_SRGB
+ 4, // BC3_SRGB
+ 4, // BC7_SRGB
+ 1, // A4B4G4R4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
8, // ASTC_2D_8X5_SRGB
5, // ASTC_2D_5X4_SRGB
- 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_UNORM
5, // ASTC_2D_5X5_SRGB
- 10, // ASTC_2D_10X8
+ 10, // ASTC_2D_10X8_UNORM
10, // ASTC_2D_10X8_SRGB
- 6, // ASTC_2D_6X6
+ 6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
- 10, // ASTC_2D_10X10
+ 10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
- 12, // ASTC_2D_12X12
+ 12, // ASTC_2D_12X12_UNORM
12, // ASTC_2D_12X12_SRGB
- 8, // ASTC_2D_8X6
+ 8, // ASTC_2D_8X6_UNORM
8, // ASTC_2D_8X6_SRGB
- 6, // ASTC_2D_6X5
+ 6, // ASTC_2D_6X5_UNORM
6, // ASTC_2D_6X5_SRGB
- 1, // E5B9G9R9F
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
+ 1, // E5B9G9R9_FLOAT
+ 1, // D32_FLOAT
+ 1, // D16_UNORM
+ 1, // D24_UNORM_S8_UINT
+ 1, // S8_UINT_D24_UNORM
+ 1, // D32_FLOAT_S8_UINT
}};
static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
@@ -317,85 +353,97 @@ static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
}
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16S
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // RGBA8_SRGB
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // RGBX16F
- 1, // R32UI
- 1, // R32I
- 8, // ASTC_2D_8X8
- 5, // ASTC_2D_8X5
- 4, // ASTC_2D_5X4
- 1, // BGRA8_SRGB
- 4, // DXT1_SRGB
- 4, // DXT23_SRGB
- 4, // DXT45_SRGB
- 4, // BC7U_SRGB
- 1, // R4G4B4A4U
+ 1, // A8B8G8R8_UNORM
+ 1, // A8B8G8R8_SNORM
+ 1, // A8B8G8R8_SINT
+ 1, // A8B8G8R8_UINT
+ 1, // R5G6B5_UNORM
+ 1, // B5G6R5_UNORM
+ 1, // A1R5G5B5_UNORM
+ 1, // A2B10G10R10_UNORM
+ 1, // A2B10G10R10_UINT
+ 1, // A1B5G5R5_UNORM
+ 1, // R8_UNORM
+ 1, // R8_SNORM
+ 1, // R8_SINT
+ 1, // R8_UINT
+ 1, // R16G16B16A16_FLOAT
+ 1, // R16G16B16A16_UNORM
+ 1, // R16G16B16A16_SNORM
+ 1, // R16G16B16A16_SINT
+ 1, // R16G16B16A16_UINT
+ 1, // B10G11R11_FLOAT
+ 1, // R32G32B32A32_UINT
+ 4, // BC1_RGBA_UNORM
+ 4, // BC2_UNORM
+ 4, // BC3_UNORM
+ 4, // BC4_UNORM
+ 4, // BC4_SNORM
+ 4, // BC5_UNORM
+ 4, // BC5_SNORM
+ 4, // BC7_UNORM
+ 4, // BC6H_UFLOAT
+ 4, // BC6H_SFLOAT
+ 4, // ASTC_2D_4X4_UNORM
+ 1, // B8G8R8A8_UNORM
+ 1, // R32G32B32A32_FLOAT
+ 1, // R32G32B32A32_SINT
+ 1, // R32G32_FLOAT
+ 1, // R32G32_SINT
+ 1, // R32_FLOAT
+ 1, // R16_FLOAT
+ 1, // R16_UNORM
+ 1, // R16_SNORM
+ 1, // R16_UINT
+ 1, // R16_SINT
+ 1, // R16G16_UNORM
+ 1, // R16G16_FLOAT
+ 1, // R16G16_UINT
+ 1, // R16G16_SINT
+ 1, // R16G16_SNORM
+ 1, // R32G32B32_FLOAT
+ 1, // A8B8G8R8_SRGB
+ 1, // R8G8_UNORM
+ 1, // R8G8_SNORM
+ 1, // R8G8_SINT
+ 1, // R8G8_UINT
+ 1, // R32G32_UINT
+ 1, // R16G16B16X16_FLOAT
+ 1, // R32_UINT
+ 1, // R32_SINT
+ 8, // ASTC_2D_8X8_UNORM
+ 5, // ASTC_2D_8X5_UNORM
+ 4, // ASTC_2D_5X4_UNORM
+ 1, // B8G8R8A8_SRGB
+ 4, // BC1_RGBA_SRGB
+ 4, // BC2_SRGB
+ 4, // BC3_SRGB
+ 4, // BC7_SRGB
+ 1, // A4B4G4R4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
4, // ASTC_2D_5X4_SRGB
- 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_UNORM
5, // ASTC_2D_5X5_SRGB
- 8, // ASTC_2D_10X8
+ 8, // ASTC_2D_10X8_UNORM
8, // ASTC_2D_10X8_SRGB
- 6, // ASTC_2D_6X6
+ 6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
- 10, // ASTC_2D_10X10
+ 10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
- 12, // ASTC_2D_12X12
+ 12, // ASTC_2D_12X12_UNORM
12, // ASTC_2D_12X12_SRGB
- 6, // ASTC_2D_8X6
+ 6, // ASTC_2D_8X6_UNORM
6, // ASTC_2D_8X6_SRGB
- 5, // ASTC_2D_6X5
+ 5, // ASTC_2D_6X5_UNORM
5, // ASTC_2D_6X5_SRGB
- 1, // E5B9G9R9F
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
+ 1, // E5B9G9R9_FLOAT
+ 1, // D32_FLOAT
+ 1, // D16_UNORM
+ 1, // D24_UNORM_S8_UINT
+ 1, // S8_UINT_D24_UNORM
+ 1, // D32_FLOAT_S8_UINT
}};
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
@@ -407,85 +455,97 @@ static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
}
constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
- 32, // ABGR8U
- 32, // ABGR8S
- 32, // ABGR8UI
- 16, // B5G6R5U
- 32, // A2B10G10R10U
- 16, // A1B5G5R5U
- 8, // R8U
- 8, // R8UI
- 64, // RGBA16F
- 64, // RGBA16U
- 64, // RGBA16S
- 64, // RGBA16UI
- 32, // R11FG11FB10F
- 128, // RGBA32UI
- 64, // DXT1
- 128, // DXT23
- 128, // DXT45
- 64, // DXN1
- 128, // DXN2UNORM
- 128, // DXN2SNORM
- 128, // BC7U
- 128, // BC6H_UF16
- 128, // BC6H_SF16
- 128, // ASTC_2D_4X4
- 32, // BGRA8
- 128, // RGBA32F
- 64, // RG32F
- 32, // R32F
- 16, // R16F
- 16, // R16U
- 16, // R16S
- 16, // R16UI
- 16, // R16I
- 32, // RG16
- 32, // RG16F
- 32, // RG16UI
- 32, // RG16I
- 32, // RG16S
- 96, // RGB32F
- 32, // RGBA8_SRGB
- 16, // RG8U
- 16, // RG8S
- 64, // RG32UI
- 64, // RGBX16F
- 32, // R32UI
- 32, // R32I
- 128, // ASTC_2D_8X8
- 128, // ASTC_2D_8X5
- 128, // ASTC_2D_5X4
- 32, // BGRA8_SRGB
- 64, // DXT1_SRGB
- 128, // DXT23_SRGB
- 128, // DXT45_SRGB
- 128, // BC7U
- 16, // R4G4B4A4U
+ 32, // A8B8G8R8_UNORM
+ 32, // A8B8G8R8_SNORM
+ 32, // A8B8G8R8_SINT
+ 32, // A8B8G8R8_UINT
+ 16, // R5G6B5_UNORM
+ 16, // B5G6R5_UNORM
+ 16, // A1R5G5B5_UNORM
+ 32, // A2B10G10R10_UNORM
+ 32, // A2B10G10R10_UINT
+ 16, // A1B5G5R5_UNORM
+ 8, // R8_UNORM
+ 8, // R8_SNORM
+ 8, // R8_SINT
+ 8, // R8_UINT
+ 64, // R16G16B16A16_FLOAT
+ 64, // R16G16B16A16_UNORM
+ 64, // R16G16B16A16_SNORM
+ 64, // R16G16B16A16_SINT
+ 64, // R16G16B16A16_UINT
+ 32, // B10G11R11_FLOAT
+ 128, // R32G32B32A32_UINT
+ 64, // BC1_RGBA_UNORM
+ 128, // BC2_UNORM
+ 128, // BC3_UNORM
+ 64, // BC4_UNORM
+ 64, // BC4_SNORM
+ 128, // BC5_UNORM
+ 128, // BC5_SNORM
+ 128, // BC7_UNORM
+ 128, // BC6H_UFLOAT
+ 128, // BC6H_SFLOAT
+ 128, // ASTC_2D_4X4_UNORM
+ 32, // B8G8R8A8_UNORM
+ 128, // R32G32B32A32_FLOAT
+ 128, // R32G32B32A32_SINT
+ 64, // R32G32_FLOAT
+ 64, // R32G32_SINT
+ 32, // R32_FLOAT
+ 16, // R16_FLOAT
+ 16, // R16_UNORM
+ 16, // R16_SNORM
+ 16, // R16_UINT
+ 16, // R16_SINT
+ 32, // R16G16_UNORM
+ 32, // R16G16_FLOAT
+ 32, // R16G16_UINT
+ 32, // R16G16_SINT
+ 32, // R16G16_SNORM
+ 96, // R32G32B32_FLOAT
+ 32, // A8B8G8R8_SRGB
+ 16, // R8G8_UNORM
+ 16, // R8G8_SNORM
+ 16, // R8G8_SINT
+ 16, // R8G8_UINT
+ 64, // R32G32_UINT
+ 64, // R16G16B16X16_FLOAT
+ 32, // R32_UINT
+ 32, // R32_SINT
+ 128, // ASTC_2D_8X8_UNORM
+ 128, // ASTC_2D_8X5_UNORM
+ 128, // ASTC_2D_5X4_UNORM
+ 32, // B8G8R8A8_SRGB
+ 64, // BC1_RGBA_SRGB
+ 128, // BC2_SRGB
+ 128, // BC3_SRGB
+ 128, // BC7_UNORM
+ 16, // A4B4G4R4_UNORM
128, // ASTC_2D_4X4_SRGB
128, // ASTC_2D_8X8_SRGB
128, // ASTC_2D_8X5_SRGB
128, // ASTC_2D_5X4_SRGB
- 128, // ASTC_2D_5X5
+ 128, // ASTC_2D_5X5_UNORM
128, // ASTC_2D_5X5_SRGB
- 128, // ASTC_2D_10X8
+ 128, // ASTC_2D_10X8_UNORM
128, // ASTC_2D_10X8_SRGB
- 128, // ASTC_2D_6X6
+ 128, // ASTC_2D_6X6_UNORM
128, // ASTC_2D_6X6_SRGB
- 128, // ASTC_2D_10X10
+ 128, // ASTC_2D_10X10_UNORM
128, // ASTC_2D_10X10_SRGB
- 128, // ASTC_2D_12X12
+ 128, // ASTC_2D_12X12_UNORM
128, // ASTC_2D_12X12_SRGB
- 128, // ASTC_2D_8X6
+ 128, // ASTC_2D_8X6_UNORM
128, // ASTC_2D_8X6_SRGB
- 128, // ASTC_2D_6X5
+ 128, // ASTC_2D_6X5_UNORM
128, // ASTC_2D_6X5_SRGB
- 32, // E5B9G9R9F
- 32, // Z32F
- 16, // Z16
- 32, // Z24S8
- 32, // S8Z24
- 64, // Z32FS8
+ 32, // E5B9G9R9_FLOAT
+ 32, // D32_FLOAT
+ 16, // D16_UNORM
+ 32, // D24_UNORM_S8_UINT
+ 32, // S8_UINT_D24_UNORM
+ 64, // D32_FLOAT_S8_UINT
}};
static constexpr u32 GetFormatBpp(PixelFormat format) {
@@ -524,7 +584,4 @@ bool IsPixelFormatSRGB(PixelFormat format);
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format);
-/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN
-bool IsFormatBCn(PixelFormat format);
-
} // namespace VideoCore::Surface
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index e151c26c4..7d5a75648 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -19,8 +19,6 @@ constexpr auto SNORM = ComponentType::SNORM;
constexpr auto UNORM = ComponentType::UNORM;
constexpr auto SINT = ComponentType::SINT;
constexpr auto UINT = ComponentType::UINT;
-constexpr auto SNORM_FORCE_FP16 = ComponentType::SNORM_FORCE_FP16;
-constexpr auto UNORM_FORCE_FP16 = ComponentType::UNORM_FORCE_FP16;
constexpr auto FLOAT = ComponentType::FLOAT;
constexpr bool C = false; // Normal color
constexpr bool S = true; // Srgb
@@ -41,117 +39,126 @@ struct Table {
ComponentType alpha_component;
bool is_srgb;
};
-constexpr std::array<Table, 76> DefinitionTable = {{
- {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ABGR8U},
- {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::ABGR8S},
- {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::ABGR8UI},
- {TextureFormat::A8R8G8B8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::RGBA8_SRGB},
+constexpr std::array<Table, 86> DefinitionTable = {{
+ {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_UNORM},
+ {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::A8B8G8R8_SNORM},
+ {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::A8B8G8R8_UINT},
+ {TextureFormat::A8R8G8B8, C, SINT, SINT, SINT, SINT, PixelFormat::A8B8G8R8_SINT},
+ {TextureFormat::A8R8G8B8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_SRGB},
- {TextureFormat::B5G6R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::B5G6R5U},
+ {TextureFormat::B5G6R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::B5G6R5_UNORM},
- {TextureFormat::A2B10G10R10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A2B10G10R10U},
+ {TextureFormat::A2B10G10R10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A2B10G10R10_UNORM},
+ {TextureFormat::A2B10G10R10, C, UINT, UINT, UINT, UINT, PixelFormat::A2B10G10R10_UINT},
- {TextureFormat::A1B5G5R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A1B5G5R5U},
+ {TextureFormat::A1B5G5R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A1B5G5R5_UNORM},
- {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R4G4B4A4U},
+ {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A4B4G4R4_UNORM},
- {TextureFormat::R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8U},
- {TextureFormat::R8, C, UINT, UINT, UINT, UINT, PixelFormat::R8UI},
+ {TextureFormat::R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8_UNORM},
+ {TextureFormat::R8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8_SNORM},
+ {TextureFormat::R8, C, UINT, UINT, UINT, UINT, PixelFormat::R8_UINT},
+ {TextureFormat::R8, C, SINT, SINT, SINT, SINT, PixelFormat::R8_SINT},
- {TextureFormat::G8R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RG8U},
- {TextureFormat::G8R8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RG8S},
+ {TextureFormat::R8G8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8G8_UNORM},
+ {TextureFormat::R8G8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8G8_SNORM},
+ {TextureFormat::R8G8, C, UINT, UINT, UINT, UINT, PixelFormat::R8G8_UINT},
+ {TextureFormat::R8G8, C, SINT, SINT, SINT, SINT, PixelFormat::R8G8_SINT},
- {TextureFormat::R16_G16_B16_A16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RGBA16S},
- {TextureFormat::R16_G16_B16_A16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RGBA16U},
- {TextureFormat::R16_G16_B16_A16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGBA16F},
- {TextureFormat::R16_G16_B16_A16, C, UINT, UINT, UINT, UINT, PixelFormat::RGBA16UI},
+ {TextureFormat::R16G16B16A16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16B16A16_SNORM},
+ {TextureFormat::R16G16B16A16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16B16A16_UNORM},
+ {TextureFormat::R16G16B16A16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16B16A16_FLOAT},
+ {TextureFormat::R16G16B16A16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16B16A16_UINT},
+ {TextureFormat::R16G16B16A16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16B16A16_SINT},
- {TextureFormat::R16_G16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RG16F},
- {TextureFormat::R16_G16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RG16},
- {TextureFormat::R16_G16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RG16S},
- {TextureFormat::R16_G16, C, UINT, UINT, UINT, UINT, PixelFormat::RG16UI},
- {TextureFormat::R16_G16, C, SINT, SINT, SINT, SINT, PixelFormat::RG16I},
+ {TextureFormat::R16G16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16_FLOAT},
+ {TextureFormat::R16G16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16_UNORM},
+ {TextureFormat::R16G16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16_SNORM},
+ {TextureFormat::R16G16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16_UINT},
+ {TextureFormat::R16G16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16_SINT},
- {TextureFormat::R16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16F},
- {TextureFormat::R16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16U},
- {TextureFormat::R16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16S},
- {TextureFormat::R16, C, UINT, UINT, UINT, UINT, PixelFormat::R16UI},
- {TextureFormat::R16, C, SINT, SINT, SINT, SINT, PixelFormat::R16I},
+ {TextureFormat::R16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16_FLOAT},
+ {TextureFormat::R16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16_UNORM},
+ {TextureFormat::R16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16_SNORM},
+ {TextureFormat::R16, C, UINT, UINT, UINT, UINT, PixelFormat::R16_UINT},
+ {TextureFormat::R16, C, SINT, SINT, SINT, SINT, PixelFormat::R16_SINT},
- {TextureFormat::BF10GF11RF11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R11FG11FB10F},
+ {TextureFormat::B10G11R11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::B10G11R11_FLOAT},
- {TextureFormat::R32_G32_B32_A32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGBA32F},
- {TextureFormat::R32_G32_B32_A32, C, UINT, UINT, UINT, UINT, PixelFormat::RGBA32UI},
+ {TextureFormat::R32G32B32A32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32A32_FLOAT},
+ {TextureFormat::R32G32B32A32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32B32A32_UINT},
+ {TextureFormat::R32G32B32A32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32B32A32_SINT},
- {TextureFormat::R32_G32_B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGB32F},
+ {TextureFormat::R32G32B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32_FLOAT},
- {TextureFormat::R32_G32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RG32F},
- {TextureFormat::R32_G32, C, UINT, UINT, UINT, UINT, PixelFormat::RG32UI},
+ {TextureFormat::R32G32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32_FLOAT},
+ {TextureFormat::R32G32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32_UINT},
+ {TextureFormat::R32G32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32_SINT},
- {TextureFormat::R32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32F},
- {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32UI},
- {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32I},
+ {TextureFormat::R32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32_FLOAT},
+ {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32_UINT},
+ {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32_SINT},
- {TextureFormat::E5B9G9R9_SHAREDEXP, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9F},
+ {TextureFormat::E5B9G9R9, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9_FLOAT},
- {TextureFormat::ZF32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::Z32F},
- {TextureFormat::Z16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::Z16},
- {TextureFormat::S8Z24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8Z24},
- {TextureFormat::ZF32_X24S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::Z32FS8},
+ {TextureFormat::D32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::D32_FLOAT},
+ {TextureFormat::D16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::D16_UNORM},
+ {TextureFormat::S8D24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM},
+ {TextureFormat::R8G24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM},
+ {TextureFormat::D32S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::D32_FLOAT_S8_UINT},
- {TextureFormat::DXT1, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1},
- {TextureFormat::DXT1, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1_SRGB},
+ {TextureFormat::BC1_RGBA, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_UNORM},
+ {TextureFormat::BC1_RGBA, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_SRGB},
- {TextureFormat::DXT23, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT23},
- {TextureFormat::DXT23, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT23_SRGB},
+ {TextureFormat::BC2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_UNORM},
+ {TextureFormat::BC2, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_SRGB},
- {TextureFormat::DXT45, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT45},
- {TextureFormat::DXT45, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT45_SRGB},
+ {TextureFormat::BC3, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_UNORM},
+ {TextureFormat::BC3, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_SRGB},
- // TODO: Use a different pixel format for SNORM
- {TextureFormat::DXN1, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXN1},
- {TextureFormat::DXN1, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::DXN1},
+ {TextureFormat::BC4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC4_UNORM},
+ {TextureFormat::BC4, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC4_SNORM},
- {TextureFormat::DXN2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXN2UNORM},
- {TextureFormat::DXN2, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::DXN2SNORM},
+ {TextureFormat::BC5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC5_UNORM},
+ {TextureFormat::BC5, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC5_SNORM},
- {TextureFormat::BC7U, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7U},
- {TextureFormat::BC7U, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7U_SRGB},
+ {TextureFormat::BC7, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_UNORM},
+ {TextureFormat::BC7, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_SRGB},
- {TextureFormat::BC6H_SF16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_SF16},
- {TextureFormat::BC6H_UF16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_UF16},
+ {TextureFormat::BC6H_SFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_SFLOAT},
+ {TextureFormat::BC6H_UFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_UFLOAT},
- {TextureFormat::ASTC_2D_4X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4},
+ {TextureFormat::ASTC_2D_4X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_UNORM},
{TextureFormat::ASTC_2D_4X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_SRGB},
- {TextureFormat::ASTC_2D_5X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4},
+ {TextureFormat::ASTC_2D_5X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_UNORM},
{TextureFormat::ASTC_2D_5X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_SRGB},
- {TextureFormat::ASTC_2D_5X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5},
+ {TextureFormat::ASTC_2D_5X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_UNORM},
{TextureFormat::ASTC_2D_5X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_SRGB},
- {TextureFormat::ASTC_2D_8X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8},
+ {TextureFormat::ASTC_2D_8X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_UNORM},
{TextureFormat::ASTC_2D_8X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_SRGB},
- {TextureFormat::ASTC_2D_8X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5},
+ {TextureFormat::ASTC_2D_8X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_UNORM},
{TextureFormat::ASTC_2D_8X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_SRGB},
- {TextureFormat::ASTC_2D_10X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8},
+ {TextureFormat::ASTC_2D_10X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_UNORM},
{TextureFormat::ASTC_2D_10X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_SRGB},
- {TextureFormat::ASTC_2D_6X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6},
+ {TextureFormat::ASTC_2D_6X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_UNORM},
{TextureFormat::ASTC_2D_6X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_SRGB},
- {TextureFormat::ASTC_2D_10X10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10},
+ {TextureFormat::ASTC_2D_10X10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_UNORM},
{TextureFormat::ASTC_2D_10X10, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_SRGB},
- {TextureFormat::ASTC_2D_12X12, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12},
+ {TextureFormat::ASTC_2D_12X12, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_UNORM},
{TextureFormat::ASTC_2D_12X12, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_SRGB},
- {TextureFormat::ASTC_2D_8X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6},
+ {TextureFormat::ASTC_2D_8X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_UNORM},
{TextureFormat::ASTC_2D_8X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_SRGB},
- {TextureFormat::ASTC_2D_6X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5},
+ {TextureFormat::ASTC_2D_6X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_UNORM},
{TextureFormat::ASTC_2D_6X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_SRGB},
}};
@@ -182,7 +189,7 @@ PixelFormat FormatLookupTable::GetPixelFormat(TextureFormat format, bool is_srgb
static_cast<int>(format), is_srgb, static_cast<int>(red_component),
static_cast<int>(green_component), static_cast<int>(blue_component),
static_cast<int>(alpha_component));
- return PixelFormat::ABGR8U;
+ return PixelFormat::A8B8G8R8_UNORM;
}
void FormatLookupTable::Set(TextureFormat format, bool is_srgb, ComponentType red_component,
@@ -196,9 +203,9 @@ std::size_t FormatLookupTable::CalculateIndex(TextureFormat format, bool is_srgb
ComponentType alpha_component) noexcept {
const auto format_index = static_cast<std::size_t>(format);
const auto red_index = static_cast<std::size_t>(red_component);
- const auto green_index = static_cast<std::size_t>(red_component);
- const auto blue_index = static_cast<std::size_t>(red_component);
- const auto alpha_index = static_cast<std::size_t>(red_component);
+ const auto green_index = static_cast<std::size_t>(green_component);
+ const auto blue_index = static_cast<std::size_t>(blue_component);
+ const auto alpha_index = static_cast<std::size_t>(alpha_component);
const std::size_t srgb_index = is_srgb ? 1 : 0;
return format_index * PerFormat +
diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp
index 7af0e792c..b44c09d71 100644
--- a/src/video_core/texture_cache/surface_base.cpp
+++ b/src/video_core/texture_cache/surface_base.cpp
@@ -115,17 +115,24 @@ std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap(
if (gpu_addr == candidate_gpu_addr) {
return {{0, 0}};
}
+
if (candidate_gpu_addr < gpu_addr) {
- return {};
+ return std::nullopt;
}
+
const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)};
const auto layer{static_cast<u32>(relative_address / layer_size)};
+ if (layer >= params.depth) {
+ return std::nullopt;
+ }
+
const GPUVAddr mipmap_address = relative_address - layer_size * layer;
const auto mipmap_it =
Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address);
if (mipmap_it == mipmap_offsets.end()) {
- return {};
+ return std::nullopt;
}
+
const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))};
return std::make_pair(layer, level);
}
@@ -225,7 +232,7 @@ void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager,
}
}
- if (!is_converted && params.pixel_format != PixelFormat::S8Z24) {
+ if (!is_converted && params.pixel_format != PixelFormat::S8_UINT_D24_UNORM) {
return;
}
@@ -251,6 +258,11 @@ void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager,
tmp_buffer.resize(guest_memory_size);
host_ptr = tmp_buffer.data();
+ if (params.target == SurfaceTarget::Texture3D) {
+ // Special case for 3D texture segments
+ memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size);
+ }
+
if (params.is_tiled) {
ASSERT_MSG(params.block_width == 0, "Block width is defined as {}", params.block_width);
for (u32 level = 0; level < params.num_levels; ++level) {
diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h
index a39a8661b..173f2edba 100644
--- a/src/video_core/texture_cache/surface_base.h
+++ b/src/video_core/texture_cache/surface_base.h
@@ -72,9 +72,9 @@ public:
return (cpu_addr < end) && (cpu_addr_end > start);
}
- bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) {
+ bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) const {
const GPUVAddr gpu_addr_end = gpu_addr + guest_memory_size;
- return (gpu_addr <= other_start && other_end <= gpu_addr_end);
+ return gpu_addr <= other_start && other_end <= gpu_addr_end;
}
// Use only when recycling a surface
@@ -192,6 +192,22 @@ public:
index = index_;
}
+ void SetMemoryMarked(bool is_memory_marked_) {
+ is_memory_marked = is_memory_marked_;
+ }
+
+ bool IsMemoryMarked() const {
+ return is_memory_marked;
+ }
+
+ void SetSyncPending(bool is_sync_pending_) {
+ is_sync_pending = is_sync_pending_;
+ }
+
+ bool IsSyncPending() const {
+ return is_sync_pending;
+ }
+
void MarkAsPicked(bool is_picked_) {
is_picked = is_picked_;
}
@@ -201,8 +217,8 @@ public:
}
bool IsProtected() const {
- // Only 3D Slices are to be protected
- return is_target && params.block_depth > 0;
+ // Only 3D slices are to be protected
+ return is_target && params.target == SurfaceTarget::Texture3D;
}
bool IsRenderTarget() const {
@@ -234,6 +250,11 @@ public:
return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels));
}
+ TView Emplace3DView(u32 slice, u32 depth, u32 base_level, u32 num_levels) {
+ return GetView(ViewParams(VideoCore::Surface::SurfaceTarget::Texture3D, slice, depth,
+ base_level, num_levels));
+ }
+
std::optional<TView> EmplaceIrregularView(const SurfaceParams& view_params,
const GPUVAddr view_addr,
const std::size_t candidate_size, const u32 mipmap,
@@ -256,8 +277,8 @@ public:
std::optional<TView> EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr,
const std::size_t candidate_size) {
if (params.target == SurfaceTarget::Texture3D ||
- (params.num_levels == 1 && !params.is_layered) ||
- view_params.target == SurfaceTarget::Texture3D) {
+ view_params.target == SurfaceTarget::Texture3D ||
+ (params.num_levels == 1 && !params.is_layered)) {
return {};
}
const auto layer_mipmap{GetLayerMipmap(view_addr)};
@@ -303,6 +324,8 @@ private:
bool is_target{};
bool is_registered{};
bool is_picked{};
+ bool is_memory_marked{};
+ bool is_sync_pending{};
u32 index{NO_RT};
u64 modification_tick{};
};
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index 6f3ef45be..13dd16356 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -74,21 +74,21 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta
SurfaceParams params;
params.is_tiled = tic.IsTiled();
params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+ params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
+ params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
+ params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
params.pixel_format = lookup_table.GetPixelFormat(
tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
params.type = GetFormatType(params.pixel_format);
- if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) {
+ if (entry.is_shadow && params.type == SurfaceType::ColorTexture) {
switch (params.pixel_format) {
- case PixelFormat::R16U:
- case PixelFormat::R16F:
- params.pixel_format = PixelFormat::Z16;
+ case PixelFormat::R16_UNORM:
+ case PixelFormat::R16_FLOAT:
+ params.pixel_format = PixelFormat::D16_UNORM;
break;
- case PixelFormat::R32F:
- params.pixel_format = PixelFormat::Z32F;
+ case PixelFormat::R32_FLOAT:
+ params.pixel_format = PixelFormat::D32_FLOAT;
break;
default:
UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}",
@@ -96,7 +96,6 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta
}
params.type = GetFormatType(params.pixel_format);
}
- params.type = GetFormatType(params.pixel_format);
// TODO: on 1DBuffer we should use the tic info.
if (tic.IsBuffer()) {
params.target = SurfaceTarget::TextureBuffer;
@@ -108,7 +107,7 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta
params.emulated_levels = 1;
params.is_layered = false;
} else {
- params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
+ params.target = TextureTypeToSurfaceTarget(entry.type, entry.is_array);
params.width = tic.Width();
params.height = tic.Height();
params.depth = tic.Depth();
@@ -130,15 +129,14 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl
SurfaceParams params;
params.is_tiled = tic.IsTiled();
params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+ params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
+ params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
+ params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
params.pixel_format = lookup_table.GetPixelFormat(
tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
params.type = GetFormatType(params.pixel_format);
- params.type = GetFormatType(params.pixel_format);
- params.target = ImageTypeToSurfaceTarget(entry.GetType());
+ params.target = ImageTypeToSurfaceTarget(entry.type);
// TODO: on 1DBuffer we should use the tic info.
if (tic.IsBuffer()) {
params.target = SurfaceTarget::TextureBuffer;
@@ -165,39 +163,40 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl
return params;
}
-SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) {
- const auto& regs = system.GPU().Maxwell3D().regs;
- regs.zeta_width, regs.zeta_height, regs.zeta.format, regs.zeta.memory_layout.type;
- SurfaceParams params;
- params.is_tiled = regs.zeta.memory_layout.type ==
- Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
- params.srgb_conversion = false;
- params.block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U);
- params.block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U);
- params.block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
- params.tile_width_spacing = 1;
- params.pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
- params.type = GetFormatType(params.pixel_format);
- params.width = regs.zeta_width;
- params.height = regs.zeta_height;
- params.pitch = 0;
- params.num_levels = 1;
- params.emulated_levels = 1;
-
- const bool is_layered = regs.zeta_layers > 1 && params.block_depth == 0;
- params.is_layered = is_layered;
- params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
- params.depth = is_layered ? regs.zeta_layers.Value() : 1U;
- return params;
+SurfaceParams SurfaceParams::CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d) {
+ const auto& regs = maxwell3d.regs;
+ const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
+ const bool is_layered = regs.zeta_layers > 1 && block_depth == 0;
+ const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
+ return {
+ .is_tiled = regs.zeta.memory_layout.type ==
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear,
+ .srgb_conversion = false,
+ .is_layered = is_layered,
+ .block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U),
+ .block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U),
+ .block_depth = block_depth,
+ .tile_width_spacing = 1,
+ .width = regs.zeta_width,
+ .height = regs.zeta_height,
+ .depth = is_layered ? regs.zeta_layers.Value() : 1U,
+ .pitch = 0,
+ .num_levels = 1,
+ .emulated_levels = 1,
+ .pixel_format = pixel_format,
+ .type = GetFormatType(pixel_format),
+ .target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D,
+ };
}
-SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
- const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
+SurfaceParams SurfaceParams::CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
+ std::size_t index) {
+ const auto& config{maxwell3d.regs.rt[index]};
SurfaceParams params;
params.is_tiled =
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
- params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
- config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
+ params.srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
+ config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB;
params.block_width = config.memory_layout.block_width;
params.block_height = config.memory_layout.block_height;
params.block_depth = config.memory_layout.block_depth;
@@ -216,45 +215,60 @@ SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::siz
params.num_levels = 1;
params.emulated_levels = 1;
- const bool is_layered = config.layers > 1 && params.block_depth == 0;
- params.is_layered = is_layered;
- params.depth = is_layered ? config.layers.Value() : 1;
- params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
+ if (config.memory_layout.is_3d != 0) {
+ params.depth = config.layers.Value();
+ params.is_layered = false;
+ params.target = SurfaceTarget::Texture3D;
+ } else if (config.layers > 1) {
+ params.depth = config.layers.Value();
+ params.is_layered = true;
+ params.target = SurfaceTarget::Texture2DArray;
+ } else {
+ params.depth = 1;
+ params.is_layered = false;
+ params.target = SurfaceTarget::Texture2D;
+ }
return params;
}
SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
- SurfaceParams params{};
- params.is_tiled = !config.linear;
- params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
- config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
- params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 5U) : 0,
- params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 5U) : 0,
- params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 5U) : 0,
- params.tile_width_spacing = 1;
- params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
- params.type = GetFormatType(params.pixel_format);
- params.width = config.width;
- params.height = config.height;
- params.pitch = config.pitch;
- // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
- params.target = SurfaceTarget::Texture2D;
- params.depth = 1;
- params.num_levels = 1;
- params.emulated_levels = 1;
+ const bool is_tiled = !config.linear;
+ const auto pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+
+ SurfaceParams params{
+ .is_tiled = is_tiled,
+ .srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
+ config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB,
+ .is_layered = false,
+ .block_width = is_tiled ? std::min(config.BlockWidth(), 5U) : 0U,
+ .block_height = is_tiled ? std::min(config.BlockHeight(), 5U) : 0U,
+ .block_depth = is_tiled ? std::min(config.BlockDepth(), 5U) : 0U,
+ .tile_width_spacing = 1,
+ .width = config.width,
+ .height = config.height,
+ .depth = 1,
+ .pitch = config.pitch,
+ .num_levels = 1,
+ .emulated_levels = 1,
+ .pixel_format = pixel_format,
+ .type = GetFormatType(pixel_format),
+ // TODO(Rodrigo): Try to guess texture arrays from parameters
+ .target = SurfaceTarget::Texture2D,
+ };
+
params.is_layered = params.IsLayered();
return params;
}
VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
const VideoCommon::Shader::Sampler& entry) {
- return TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
+ return TextureTypeToSurfaceTarget(entry.type, entry.is_array);
}
VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
const VideoCommon::Shader::Image& entry) {
- return ImageTypeToSurfaceTarget(entry.GetType());
+ return ImageTypeToSurfaceTarget(entry.type);
}
bool SurfaceParams::IsLayered() const {
@@ -335,8 +349,7 @@ std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) co
size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed);
}
if (is_tiled && is_layered) {
- return Common::AlignBits(size,
- Tegra::Texture::GetGOBSizeShift() + block_height + block_depth);
+ return Common::AlignBits(size, Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth);
}
return size;
}
@@ -410,7 +423,7 @@ std::tuple<u32, u32, u32> SurfaceParams::GetBlockOffsetXYZ(u32 offset) const {
const u32 block_size = GetBlockSize();
const u32 block_index = offset / block_size;
const u32 gob_offset = offset % block_size;
- const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GetGOBSize());
+ const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GOB_SIZE);
const u32 x_gob_pixels = 64U / GetBytesPerPixel();
const u32 x_block_pixels = x_gob_pixels << block_width;
const u32 y_block_pixels = 8U << block_height;
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index 24957df8d..4466c3c34 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -33,10 +33,11 @@ public:
const VideoCommon::Shader::Image& entry);
/// Creates SurfaceCachedParams for a depth buffer configuration.
- static SurfaceParams CreateForDepthBuffer(Core::System& system);
+ static SurfaceParams CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d);
/// Creates SurfaceCachedParams from a framebuffer configuration.
- static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
+ static SurfaceParams CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
+ std::size_t index);
/// Creates SurfaceCachedParams from a Fermi2D surface configuration.
static SurfaceParams CreateForFermiCopySurface(
@@ -204,7 +205,7 @@ public:
static std::size_t AlignLayered(const std::size_t out_size, const u32 block_height,
const u32 block_depth) {
return Common::AlignBits(out_size,
- Tegra::Texture::GetGOBSizeShift() + block_height + block_depth);
+ Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth);
}
/// Converts a width from a type of surface into another. This helps represent the
diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp
index 57a1f5803..6b5f5984b 100644
--- a/src/video_core/texture_cache/surface_view.cpp
+++ b/src/video_core/texture_cache/surface_view.cpp
@@ -20,4 +20,8 @@ bool ViewParams::operator==(const ViewParams& rhs) const {
std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels, rhs.target);
}
+bool ViewParams::operator!=(const ViewParams& rhs) const {
+ return !operator==(rhs);
+}
+
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h
index b17fd11a9..90a8bb0ae 100644
--- a/src/video_core/texture_cache/surface_view.h
+++ b/src/video_core/texture_cache/surface_view.h
@@ -21,6 +21,7 @@ struct ViewParams {
std::size_t Hash() const;
bool operator==(const ViewParams& rhs) const;
+ bool operator!=(const ViewParams& rhs) const;
bool IsLayered() const {
switch (target) {
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4edd4313b..ea835c59f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
+#include <list>
#include <memory>
#include <mutex>
#include <set>
@@ -13,6 +14,7 @@
#include <unordered_map>
#include <vector>
+#include <boost/container/small_vector.hpp>
#include <boost/icl/interval_map.hpp>
#include <boost/range/iterator_range.hpp>
@@ -22,6 +24,7 @@
#include "core/core.h"
#include "core/memory.h"
#include "core/settings.h"
+#include "video_core/compatible_formats.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/maxwell_3d.h"
@@ -45,13 +48,14 @@ class RasterizerInterface;
namespace VideoCommon {
+using VideoCore::Surface::FormatCompatibility;
using VideoCore::Surface::PixelFormat;
-
using VideoCore::Surface::SurfaceTarget;
using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig;
template <typename TSurface, typename TView>
class TextureCache {
+ using VectorSurface = boost::container::small_vector<TSurface, 1>;
public:
void InvalidateRegion(VAddr addr, std::size_t size) {
@@ -62,6 +66,30 @@ public:
}
}
+ void OnCPUWrite(VAddr addr, std::size_t size) {
+ std::lock_guard lock{mutex};
+
+ for (const auto& surface : GetSurfacesInRegion(addr, size)) {
+ if (surface->IsMemoryMarked()) {
+ UnmarkMemory(surface);
+ surface->SetSyncPending(true);
+ marked_for_unregister.emplace_back(surface);
+ }
+ }
+ }
+
+ void SyncGuestHost() {
+ std::lock_guard lock{mutex};
+
+ for (const auto& surface : marked_for_unregister) {
+ if (surface->IsRegistered()) {
+ surface->SetSyncPending(false);
+ Unregister(surface);
+ }
+ }
+ marked_for_unregister.clear();
+ }
+
/**
* Guarantees that rendertargets don't unregister themselves if the
* collide. Protection is currently only done on 3D slices.
@@ -85,10 +113,20 @@ public:
return a->GetModificationTick() < b->GetModificationTick();
});
for (const auto& surface : surfaces) {
+ mutex.unlock();
FlushSurface(surface);
+ mutex.lock();
}
}
+ bool MustFlushRegion(VAddr addr, std::size_t size) {
+ std::lock_guard lock{mutex};
+
+ const auto surfaces = GetSurfacesInRegion(addr, size);
+ return std::any_of(surfaces.cbegin(), surfaces.cend(),
+ [](const TSurface& surface) { return surface->IsModified(); });
+ }
+
TView GetTextureSurface(const Tegra::Texture::TICEntry& tic,
const VideoCommon::Shader::Sampler& entry) {
std::lock_guard lock{mutex};
@@ -97,8 +135,7 @@ public:
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
@@ -108,7 +145,7 @@ public:
}
const auto params{SurfaceParams::CreateForTexture(format_lookup_table, tic, entry)};
- const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, false);
+ const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false);
if (guard_samplers) {
sampled_textures.push_back(surface);
}
@@ -122,13 +159,12 @@ public:
if (!gpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
const auto params{SurfaceParams::CreateForImage(format_lookup_table, tic, entry)};
- const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, false);
+ const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false);
if (guard_samplers) {
sampled_textures.push_back(surface);
}
@@ -143,13 +179,13 @@ public:
return any_rt;
}
- TView GetDepthBufferSurface() {
+ TView GetDepthBufferSurface(bool preserve_contents) {
std::lock_guard lock{mutex};
- auto& maxwell3d = system.GPU().Maxwell3D();
- if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
+ auto& dirty = maxwell3d.dirty;
+ if (!dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
return depth_buffer.view;
}
- maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
+ dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
const auto& regs{maxwell3d.regs};
const auto gpu_addr{regs.zeta.Address()};
@@ -157,14 +193,13 @@ public:
SetEmptyDepthBuffer();
return {};
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
SetEmptyDepthBuffer();
return {};
}
- const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)};
- auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, true);
+ const auto depth_params{SurfaceParams::CreateForDepthBuffer(maxwell3d)};
+ auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true);
if (depth_buffer.target)
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
depth_buffer.target = surface_view.first;
@@ -174,10 +209,9 @@ public:
return surface_view.second;
}
- TView GetColorBufferSurface(std::size_t index) {
+ TView GetColorBufferSurface(std::size_t index, bool preserve_contents) {
std::lock_guard lock{mutex};
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
- auto& maxwell3d = system.GPU().Maxwell3D();
if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index]) {
return render_targets[index].view;
}
@@ -197,17 +231,23 @@ public:
return {};
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
SetEmptyColorBuffer(index);
return {};
}
- auto surface_view = GetSurface(gpu_addr, *cpu_addr,
- SurfaceParams::CreateForFramebuffer(system, index), true);
- if (render_targets[index].target)
- render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
+ auto surface_view =
+ GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(maxwell3d, index),
+ preserve_contents, true);
+ if (render_targets[index].target) {
+ auto& surface = render_targets[index].target;
+ surface->MarkAsRenderTarget(false, NO_RT);
+ const auto& cr_params = surface->GetSurfaceParams();
+ if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ AsyncFlushSurface(surface);
+ }
+ }
render_targets[index].target = surface_view.first;
render_targets[index].view = surface_view.second;
if (render_targets[index].target)
@@ -254,40 +294,69 @@ public:
const GPUVAddr src_gpu_addr = src_config.Address();
const GPUVAddr dst_gpu_addr = dst_config.Address();
DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
- const std::optional<VAddr> dst_cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(dst_gpu_addr);
- const std::optional<VAddr> src_cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(src_gpu_addr);
- std::pair<TSurface, TView> dst_surface =
- GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, false);
- std::pair<TSurface, TView> src_surface =
- GetSurface(src_gpu_addr, *src_cpu_addr, src_params, false);
- ImageBlit(src_surface.second, dst_surface.second, copy_config);
+
+ const std::optional<VAddr> dst_cpu_addr = gpu_memory.GpuToCpuAddress(dst_gpu_addr);
+ const std::optional<VAddr> src_cpu_addr = gpu_memory.GpuToCpuAddress(src_gpu_addr);
+ std::pair dst_surface = GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, true, false);
+ TView src_surface = GetSurface(src_gpu_addr, *src_cpu_addr, src_params, true, false).second;
+ ImageBlit(src_surface, dst_surface.second, copy_config);
dst_surface.first->MarkAsModified(true, Tick());
}
- TSurface TryFindFramebufferSurface(VAddr addr) {
+ TSurface TryFindFramebufferSurface(VAddr addr) const {
if (!addr) {
return nullptr;
}
const VAddr page = addr >> registry_page_bits;
- std::vector<TSurface>& list = registry[page];
- for (auto& surface : list) {
- if (surface->GetCpuAddr() == addr) {
- return surface;
- }
+ const auto it = registry.find(page);
+ if (it == registry.end()) {
+ return nullptr;
}
- return nullptr;
+ const auto& list = it->second;
+ const auto found = std::find_if(list.begin(), list.end(), [addr](const auto& surface) {
+ return surface->GetCpuAddr() == addr;
+ });
+ return found != list.end() ? *found : nullptr;
}
u64 Tick() {
return ++ticks;
}
+ void CommitAsyncFlushes() {
+ committed_flushes.push_back(uncommitted_flushes);
+ uncommitted_flushes.reset();
+ }
+
+ bool HasUncommittedFlushes() const {
+ return uncommitted_flushes != nullptr;
+ }
+
+ bool ShouldWaitAsyncFlushes() const {
+ return !committed_flushes.empty() && committed_flushes.front() != nullptr;
+ }
+
+ void PopAsyncFlushes() {
+ if (committed_flushes.empty()) {
+ return;
+ }
+ auto& flush_list = committed_flushes.front();
+ if (!flush_list) {
+ committed_flushes.pop_front();
+ return;
+ }
+ for (TSurface& surface : *flush_list) {
+ FlushSurface(surface);
+ }
+ committed_flushes.pop_front();
+ }
+
protected:
- explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- bool is_astc_supported)
- : system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} {
+ explicit TextureCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
+ bool is_astc_supported_)
+ : is_astc_supported{is_astc_supported_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ gpu_memory{gpu_memory_} {
for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
SetEmptyColorBuffer(i);
}
@@ -300,9 +369,9 @@ protected:
siblings_table[static_cast<std::size_t>(b)] = a;
};
std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid);
- make_siblings(PixelFormat::Z16, PixelFormat::R16U);
- make_siblings(PixelFormat::Z32F, PixelFormat::R32F);
- make_siblings(PixelFormat::Z32FS8, PixelFormat::RG32F);
+ make_siblings(PixelFormat::D16_UNORM, PixelFormat::R16_UNORM);
+ make_siblings(PixelFormat::D32_FLOAT, PixelFormat::R32_FLOAT);
+ make_siblings(PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::R32G32_FLOAT);
sampled_textures.reserve(64);
}
@@ -322,7 +391,7 @@ protected:
virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0;
void ManageRenderTargetUnregister(TSurface& surface) {
- auto& dirty = system.GPU().Maxwell3D().dirty;
+ auto& dirty = maxwell3d.dirty;
const u32 index = surface->GetRenderTarget();
if (index == DEPTH_RT) {
dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true;
@@ -335,8 +404,7 @@ protected:
void Register(TSurface surface) {
const GPUVAddr gpu_addr = surface->GetGpuAddr();
const std::size_t size = surface->GetSizeInBytes();
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}",
gpu_addr);
@@ -345,9 +413,20 @@ protected:
surface->SetCpuAddr(*cpu_addr);
RegisterInnerCache(surface);
surface->MarkAsRegistered(true);
+ surface->SetMemoryMarked(true);
rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1);
}
+ void UnmarkMemory(TSurface surface) {
+ if (!surface->IsMemoryMarked()) {
+ return;
+ }
+ const std::size_t size = surface->GetSizeInBytes();
+ const VAddr cpu_addr = surface->GetCpuAddr();
+ rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
+ surface->SetMemoryMarked(false);
+ }
+
void Unregister(TSurface surface) {
if (guard_render_targets && surface->IsProtected()) {
return;
@@ -355,9 +434,11 @@ protected:
if (!guard_render_targets && surface->IsRenderTarget()) {
ManageRenderTargetUnregister(surface);
}
- const std::size_t size = surface->GetSizeInBytes();
- const VAddr cpu_addr = surface->GetCpuAddr();
- rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
+ UnmarkMemory(surface);
+ if (surface->IsSyncPending()) {
+ marked_for_unregister.remove(surface);
+ surface->SetSyncPending(false);
+ }
UnregisterInnerCache(surface);
surface->MarkAsRegistered(false);
ReserveSurface(surface->GetSurfaceParams(), surface);
@@ -373,7 +454,6 @@ protected:
return new_surface;
}
- Core::System& system;
const bool is_astc_supported;
private:
@@ -415,18 +495,18 @@ private:
* @param untopological Indicates to the recycler that the texture has no way
* to match the overlaps due to topological reasons.
**/
- RecycleStrategy PickStrategy(std::vector<TSurface>& overlaps, const SurfaceParams& params,
+ RecycleStrategy PickStrategy(VectorSurface& overlaps, const SurfaceParams& params,
const GPUVAddr gpu_addr, const MatchTopologyResult untopological) {
- if (Settings::values.use_accurate_gpu_emulation) {
+ if (Settings::IsGPULevelExtreme()) {
return RecycleStrategy::Flush;
}
// 3D Textures decision
- if (params.block_depth > 1 || params.target == SurfaceTarget::Texture3D) {
+ if (params.target == SurfaceTarget::Texture3D) {
return RecycleStrategy::Flush;
}
for (const auto& s : overlaps) {
const auto& s_params = s->GetSurfaceParams();
- if (s_params.block_depth > 1 || s_params.target == SurfaceTarget::Texture3D) {
+ if (s_params.target == SurfaceTarget::Texture3D) {
return RecycleStrategy::Flush;
}
}
@@ -450,18 +530,21 @@ private:
* @param overlaps The overlapping surfaces registered in the cache.
* @param params The parameters for the new surface.
* @param gpu_addr The starting address of the new surface.
+ * @param preserve_contents Indicates that the new surface should be loaded from memory or left
+ * blank.
* @param untopological Indicates to the recycler that the texture has no way to match the
* overlaps due to topological reasons.
**/
- std::pair<TSurface, TView> RecycleSurface(std::vector<TSurface>& overlaps,
- const SurfaceParams& params, const GPUVAddr gpu_addr,
+ std::pair<TSurface, TView> RecycleSurface(VectorSurface& overlaps, const SurfaceParams& params,
+ const GPUVAddr gpu_addr, const bool preserve_contents,
const MatchTopologyResult untopological) {
+ const bool do_load = preserve_contents && Settings::IsGPULevelExtreme();
for (auto& surface : overlaps) {
Unregister(surface);
}
switch (PickStrategy(overlaps, params, gpu_addr, untopological)) {
case RecycleStrategy::Ignore: {
- return InitializeSurface(gpu_addr, params, Settings::values.use_accurate_gpu_emulation);
+ return InitializeSurface(gpu_addr, params, do_load);
}
case RecycleStrategy::Flush: {
std::sort(overlaps.begin(), overlaps.end(),
@@ -471,7 +554,7 @@ private:
for (auto& surface : overlaps) {
FlushSurface(surface);
}
- return InitializeSurface(gpu_addr, params);
+ return InitializeSurface(gpu_addr, params, preserve_contents);
}
case RecycleStrategy::BufferCopy: {
auto new_surface = GetUncachedSurface(gpu_addr, params);
@@ -480,7 +563,7 @@ private:
}
default: {
UNIMPLEMENTED_MSG("Unimplemented Texture Cache Recycling Strategy!");
- return InitializeSurface(gpu_addr, params);
+ return InitializeSurface(gpu_addr, params, do_load);
}
}
}
@@ -507,15 +590,15 @@ private:
} else {
new_surface = GetUncachedSurface(gpu_addr, params);
}
- const auto& final_params = new_surface->GetSurfaceParams();
+ const SurfaceParams& final_params = new_surface->GetSurfaceParams();
if (cr_params.type != final_params.type) {
- if (Settings::values.use_accurate_gpu_emulation) {
+ if (Settings::IsGPULevelExtreme()) {
BufferCopy(current_surface, new_surface);
}
} else {
std::vector<CopyParams> bricks = current_surface->BreakDown(final_params);
for (auto& brick : bricks) {
- ImageCopy(current_surface, new_surface, brick);
+ TryCopyImage(current_surface, new_surface, brick);
}
}
Unregister(current_surface);
@@ -563,47 +646,65 @@ private:
* @param params The parameters on the new surface.
* @param gpu_addr The starting address of the new surface.
**/
- std::optional<std::pair<TSurface, TView>> TryReconstructSurface(std::vector<TSurface>& overlaps,
+ std::optional<std::pair<TSurface, TView>> TryReconstructSurface(VectorSurface& overlaps,
const SurfaceParams& params,
- const GPUVAddr gpu_addr) {
+ GPUVAddr gpu_addr) {
if (params.target == SurfaceTarget::Texture3D) {
- return {};
+ return std::nullopt;
}
- bool modified = false;
+ const auto test_modified = [](TSurface& surface) { return surface->IsModified(); };
TSurface new_surface = GetUncachedSurface(gpu_addr, params);
- u32 passed_tests = 0;
+
+ if (std::none_of(overlaps.begin(), overlaps.end(), test_modified)) {
+ LoadSurface(new_surface);
+ for (const auto& surface : overlaps) {
+ Unregister(surface);
+ }
+ Register(new_surface);
+ return {{new_surface, new_surface->GetMainView()}};
+ }
+
+ std::size_t passed_tests = 0;
for (auto& surface : overlaps) {
const SurfaceParams& src_params = surface->GetSurfaceParams();
- if (src_params.is_layered || src_params.num_levels > 1) {
- // We send this cases to recycle as they are more complex to handle
- return {};
- }
- const std::size_t candidate_size = surface->GetSizeInBytes();
- auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
+ const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
if (!mipmap_layer) {
continue;
}
- const auto [layer, mipmap] = *mipmap_layer;
- if (new_surface->GetMipmapSize(mipmap) != candidate_size) {
+ const auto [base_layer, base_mipmap] = *mipmap_layer;
+ if (new_surface->GetMipmapSize(base_mipmap) != surface->GetMipmapSize(0)) {
continue;
}
- modified |= surface->IsModified();
- // Now we got all the data set up
- const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap);
- const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap);
- const CopyParams copy_params(0, 0, 0, 0, 0, layer, 0, mipmap, width, height, 1);
- passed_tests++;
- ImageCopy(surface, new_surface, copy_params);
+ ++passed_tests;
+
+ // Copy all mipmaps and layers
+ const u32 block_width = params.GetDefaultBlockWidth();
+ const u32 block_height = params.GetDefaultBlockHeight();
+ for (u32 mipmap = base_mipmap; mipmap < base_mipmap + src_params.num_levels; ++mipmap) {
+ const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap);
+ const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap);
+ if (width < block_width || height < block_height) {
+ // Current APIs forbid copying small compressed textures, avoid errors
+ break;
+ }
+ const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height,
+ src_params.depth);
+ TryCopyImage(surface, new_surface, copy_params);
+ }
}
if (passed_tests == 0) {
- return {};
+ return std::nullopt;
+ }
+ if (Settings::IsGPULevelExtreme() && passed_tests != overlaps.size()) {
// In Accurate GPU all tests should pass, else we recycle
- } else if (Settings::values.use_accurate_gpu_emulation && passed_tests != overlaps.size()) {
- return {};
+ return std::nullopt;
}
+
+ const bool modified = std::any_of(overlaps.begin(), overlaps.end(), test_modified);
for (const auto& surface : overlaps) {
Unregister(surface);
}
+
new_surface->MarkAsModified(modified, Tick());
Register(new_surface);
return {{new_surface, new_surface->GetMainView()}};
@@ -614,64 +715,26 @@ private:
* textures within the GPU if possible. Falls back to LLE when it isn't possible to use any of
* the HLE methods.
*
- * @param overlaps The overlapping surfaces registered in the cache.
- * @param params The parameters on the new surface.
- * @param gpu_addr The starting address of the new surface.
- * @param cache_addr The starting address of the new surface on physical memory.
+ * @param overlaps The overlapping surfaces registered in the cache.
+ * @param params The parameters on the new surface.
+ * @param gpu_addr The starting address of the new surface.
+ * @param cpu_addr The starting address of the new surface on physical memory.
+ * @param preserve_contents Indicates that the new surface should be loaded from memory or
+ * left blank.
*/
- std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(std::vector<TSurface>& overlaps,
+ std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(VectorSurface& overlaps,
const SurfaceParams& params,
- const GPUVAddr gpu_addr,
- const VAddr cpu_addr) {
- if (params.target == SurfaceTarget::Texture3D) {
- bool failed = false;
- if (params.num_levels > 1) {
- // We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
- return std::nullopt;
- }
- TSurface new_surface = GetUncachedSurface(gpu_addr, params);
- bool modified = false;
- for (auto& surface : overlaps) {
- const SurfaceParams& src_params = surface->GetSurfaceParams();
- if (src_params.target != SurfaceTarget::Texture2D) {
- failed = true;
- break;
- }
- if (src_params.height != params.height) {
- failed = true;
- break;
- }
- if (src_params.block_depth != params.block_depth ||
- src_params.block_height != params.block_height) {
- failed = true;
- break;
- }
- const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr);
- const auto [x, y, z] = params.GetBlockOffsetXYZ(offset);
- modified |= surface->IsModified();
- const CopyParams copy_params(0, 0, 0, 0, 0, z, 0, 0, params.width, params.height,
- 1);
- ImageCopy(surface, new_surface, copy_params);
- }
- if (failed) {
- return std::nullopt;
- }
- for (const auto& surface : overlaps) {
- Unregister(surface);
- }
- new_surface->MarkAsModified(modified, Tick());
- Register(new_surface);
- auto view = new_surface->GetMainView();
- return {{std::move(new_surface), view}};
- } else {
+ GPUVAddr gpu_addr, VAddr cpu_addr,
+ bool preserve_contents) {
+ if (params.target != SurfaceTarget::Texture3D) {
for (const auto& surface : overlaps) {
if (!surface->MatchTarget(params.target)) {
if (overlaps.size() == 1 && surface->GetCpuAddr() == cpu_addr) {
- if (Settings::values.use_accurate_gpu_emulation) {
+ if (Settings::IsGPULevelExtreme()) {
return std::nullopt;
}
Unregister(surface);
- return InitializeSurface(gpu_addr, params);
+ return InitializeSurface(gpu_addr, params, preserve_contents);
}
return std::nullopt;
}
@@ -679,11 +742,60 @@ private:
continue;
}
if (surface->MatchesStructure(params) == MatchStructureResult::FullMatch) {
- return {{surface, surface->GetMainView()}};
+ return std::make_pair(surface, surface->GetMainView());
+ }
+ }
+ return InitializeSurface(gpu_addr, params, preserve_contents);
+ }
+
+ if (params.num_levels > 1) {
+ // We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
+ return std::nullopt;
+ }
+
+ if (overlaps.size() == 1) {
+ const auto& surface = overlaps[0];
+ const SurfaceParams& overlap_params = surface->GetSurfaceParams();
+ // Don't attempt to render to textures with more than one level for now
+ // The texture has to be to the right or the sample address if we want to render to it
+ if (overlap_params.num_levels == 1 && cpu_addr >= surface->GetCpuAddr()) {
+ const u32 offset = static_cast<u32>(cpu_addr - surface->GetCpuAddr());
+ const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
+ if (slice < overlap_params.depth) {
+ auto view = surface->Emplace3DView(slice, params.depth, 0, 1);
+ return std::make_pair(std::move(surface), std::move(view));
}
}
- return InitializeSurface(gpu_addr, params);
}
+
+ TSurface new_surface = GetUncachedSurface(gpu_addr, params);
+ bool modified = false;
+
+ for (auto& surface : overlaps) {
+ const SurfaceParams& src_params = surface->GetSurfaceParams();
+ if (src_params.target != SurfaceTarget::Texture2D ||
+ src_params.height != params.height ||
+ src_params.block_depth != params.block_depth ||
+ src_params.block_height != params.block_height) {
+ return std::nullopt;
+ }
+ modified |= surface->IsModified();
+
+ const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr);
+ const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
+ const u32 width = params.width;
+ const u32 height = params.height;
+ const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1);
+ TryCopyImage(surface, new_surface, copy_params);
+ }
+ for (const auto& surface : overlaps) {
+ Unregister(surface);
+ }
+ new_surface->MarkAsModified(modified, Tick());
+ Register(new_surface);
+
+ TView view = new_surface->GetMainView();
+ return std::make_pair(std::move(new_surface), std::move(view));
}
/**
@@ -705,10 +817,13 @@ private:
*
* @param gpu_addr The starting address of the candidate surface.
* @param params The parameters on the candidate surface.
+ * @param preserve_contents Indicates that the new surface should be loaded from memory or
+ * left blank.
* @param is_render Whether or not the surface is a render target.
**/
std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const VAddr cpu_addr,
- const SurfaceParams& params, bool is_render) {
+ const SurfaceParams& params, bool preserve_contents,
+ bool is_render) {
// Step 1
// Check Level 1 Cache for a fast structural match. If candidate surface
// matches at certain level we are pretty much done.
@@ -716,8 +831,9 @@ private:
TSurface& current_surface = iter->second;
const auto topological_result = current_surface->MatchesTopology(params);
if (topological_result != MatchTopologyResult::FullMatch) {
- std::vector<TSurface> overlaps{current_surface};
- return RecycleSurface(overlaps, params, gpu_addr, topological_result);
+ VectorSurface overlaps{current_surface};
+ return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
+ topological_result);
}
const auto struct_result = current_surface->MatchesStructure(params);
@@ -742,7 +858,7 @@ private:
// If none are found, we are done. we just load the surface and create it.
if (overlaps.empty()) {
- return InitializeSurface(gpu_addr, params);
+ return InitializeSurface(gpu_addr, params, preserve_contents);
}
// Step 3
@@ -752,13 +868,15 @@ private:
for (const auto& surface : overlaps) {
const auto topological_result = surface->MatchesTopology(params);
if (topological_result != MatchTopologyResult::FullMatch) {
- return RecycleSurface(overlaps, params, gpu_addr, topological_result);
+ return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
+ topological_result);
}
}
- // Check if it's a 3D texture
+ // Manage 3D textures
if (params.block_depth > 0) {
- auto surface = Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr);
+ auto surface =
+ Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr, preserve_contents);
if (surface) {
return *surface;
}
@@ -771,14 +889,12 @@ private:
// two things either the candidate surface is a supertexture of the overlap
// or they don't match in any known way.
if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) {
- if (current_surface->GetGpuAddr() == gpu_addr) {
- std::optional<std::pair<TSurface, TView>> view =
- TryReconstructSurface(overlaps, params, gpu_addr);
- if (view) {
- return *view;
- }
+ const std::optional view = TryReconstructSurface(overlaps, params, gpu_addr);
+ if (view) {
+ return *view;
}
- return RecycleSurface(overlaps, params, gpu_addr, MatchTopologyResult::FullMatch);
+ return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
+ MatchTopologyResult::FullMatch);
}
// Now we check if the candidate is a mipmap/layer of the overlap
std::optional<TView> view =
@@ -802,7 +918,7 @@ private:
pair.first->EmplaceView(params, gpu_addr, candidate_size);
if (mirage_view)
return {pair.first, *mirage_view};
- return RecycleSurface(overlaps, params, gpu_addr,
+ return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
MatchTopologyResult::FullMatch);
}
return {current_surface, *view};
@@ -818,7 +934,8 @@ private:
}
}
// We failed all the tests, recycle the overlaps into a new texture.
- return RecycleSurface(overlaps, params, gpu_addr, MatchTopologyResult::FullMatch);
+ return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
+ MatchTopologyResult::FullMatch);
}
/**
@@ -831,8 +948,7 @@ private:
* @param params The parameters on the candidate surface.
**/
Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
Deduction result{};
@@ -892,7 +1008,9 @@ private:
params.target = target;
params.is_tiled = false;
params.srgb_conversion = false;
- params.is_layered = false;
+ params.is_layered =
+ target == SurfaceTarget::Texture1DArray || target == SurfaceTarget::Texture2DArray ||
+ target == SurfaceTarget::TextureCubemap || target == SurfaceTarget::TextureCubeArray;
params.block_width = 0;
params.block_height = 0;
params.block_depth = 0;
@@ -906,7 +1024,7 @@ private:
params.pitch = 4;
params.num_levels = 1;
params.emulated_levels = 1;
- params.pixel_format = VideoCore::Surface::PixelFormat::R8U;
+ params.pixel_format = VideoCore::Surface::PixelFormat::R8_UNORM;
params.type = VideoCore::Surface::SurfaceType::ColorTexture;
auto surface = CreateSurface(0ULL, params);
invalid_memory.resize(surface->GetHostSizeInBytes(), 0U);
@@ -929,7 +1047,7 @@ private:
void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params,
const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) {
auto deduced_src = DeduceSurface(src_gpu_addr, src_params);
- auto deduced_dst = DeduceSurface(src_gpu_addr, src_params);
+ auto deduced_dst = DeduceSurface(dst_gpu_addr, dst_params);
if (deduced_src.Failed() || deduced_dst.Failed()) {
return;
}
@@ -976,10 +1094,10 @@ private:
}
std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params,
- bool do_load = true) {
+ bool preserve_contents) {
auto new_surface{GetUncachedSurface(gpu_addr, params)};
Register(new_surface);
- if (do_load) {
+ if (preserve_contents) {
LoadSurface(new_surface);
}
return {new_surface, new_surface->GetMainView()};
@@ -987,7 +1105,7 @@ private:
void LoadSurface(const TSurface& surface) {
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
- surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache);
+ surface->LoadBuffer(gpu_memory, staging_cache);
surface->UploadTexture(staging_cache.GetBuffer(0));
surface->MarkAsModified(false, Tick());
}
@@ -998,7 +1116,7 @@ private:
}
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
surface->DownloadTexture(staging_cache.GetBuffer(0));
- surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache);
+ surface->FlushBuffer(gpu_memory, staging_cache);
surface->MarkAsModified(false, Tick());
}
@@ -1025,23 +1143,25 @@ private:
}
}
- std::vector<TSurface> GetSurfacesInRegion(const VAddr cpu_addr, const std::size_t size) {
+ VectorSurface GetSurfacesInRegion(const VAddr cpu_addr, const std::size_t size) {
if (size == 0) {
return {};
}
const VAddr cpu_addr_end = cpu_addr + size;
- VAddr start = cpu_addr >> registry_page_bits;
const VAddr end = (cpu_addr_end - 1) >> registry_page_bits;
- std::vector<TSurface> surfaces;
- while (start <= end) {
- std::vector<TSurface>& list = registry[start];
- for (auto& surface : list) {
- if (!surface->IsPicked() && surface->Overlaps(cpu_addr, cpu_addr_end)) {
- surface->MarkAsPicked(true);
- surfaces.push_back(surface);
+ VectorSurface surfaces;
+ for (VAddr start = cpu_addr >> registry_page_bits; start <= end; ++start) {
+ const auto it = registry.find(start);
+ if (it == registry.end()) {
+ continue;
+ }
+ for (auto& surface : it->second) {
+ if (surface->IsPicked() || !surface->Overlaps(cpu_addr, cpu_addr_end)) {
+ continue;
}
+ surface->MarkAsPicked(true);
+ surfaces.push_back(surface);
}
- start++;
}
for (auto& surface : surfaces) {
surface->MarkAsPicked(false);
@@ -1066,6 +1186,19 @@ private:
return {};
}
+ /// Try to do an image copy logging when formats are incompatible.
+ void TryCopyImage(TSurface& src, TSurface& dst, const CopyParams& copy) {
+ const SurfaceParams& src_params = src->GetSurfaceParams();
+ const SurfaceParams& dst_params = dst->GetSurfaceParams();
+ if (!format_compatibility.TestCopy(src_params.pixel_format, dst_params.pixel_format)) {
+ LOG_ERROR(HW_GPU, "Illegal copy between formats={{{}, {}}}",
+ static_cast<int>(dst_params.pixel_format),
+ static_cast<int>(src_params.pixel_format));
+ return;
+ }
+ ImageCopy(src, dst, copy);
+ }
+
constexpr PixelFormat GetSiblingFormat(PixelFormat format) const {
return siblings_table[static_cast<std::size_t>(format)];
}
@@ -1073,7 +1206,7 @@ private:
/// Returns true the shader sampler entry is compatible with the TIC texture type.
static bool IsTypeCompatible(Tegra::Texture::TextureType tic_type,
const VideoCommon::Shader::Sampler& entry) {
- const auto shader_type = entry.GetType();
+ const auto shader_type = entry.type;
switch (tic_type) {
case Tegra::Texture::TextureType::Texture1D:
case Tegra::Texture::TextureType::Texture1DArray:
@@ -1094,7 +1227,7 @@ private:
if (shader_type == Tegra::Shader::TextureType::TextureCube) {
return true;
}
- return shader_type == Tegra::Shader::TextureType::Texture2D && entry.IsArray();
+ return shader_type == Tegra::Shader::TextureType::Texture2D && entry.is_array;
}
UNREACHABLE();
return true;
@@ -1105,9 +1238,19 @@ private:
TView view;
};
+ void AsyncFlushSurface(TSurface& surface) {
+ if (!uncommitted_flushes) {
+ uncommitted_flushes = std::make_shared<std::list<TSurface>>();
+ }
+ uncommitted_flushes->push_back(surface);
+ }
+
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager& gpu_memory;
FormatLookupTable format_lookup_table;
+ FormatCompatibility format_compatibility;
u64 ticks{};
@@ -1149,6 +1292,11 @@ private:
std::unordered_map<u32, TSurface> invalid_cache;
std::vector<u8> invalid_memory;
+ std::list<TSurface> marked_for_unregister;
+
+ std::shared_ptr<std::list<TSurface>> uncommitted_flushes{};
+ std::list<std::shared_ptr<std::list<TSurface>>> committed_flushes;
+
StagingCache staging_cache;
std::recursive_mutex mutex;
};
diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp
index f3efa7eb0..962921483 100644
--- a/src/video_core/textures/convert.cpp
+++ b/src/video_core/textures/convert.cpp
@@ -35,7 +35,7 @@ void SwapS8Z24ToZ24S8(u8* data, u32 width, u32 height) {
S8Z24 s8z24_pixel{};
Z24S8 z24s8_pixel{};
constexpr auto bpp{
- VideoCore::Surface::GetBytesPerPixel(VideoCore::Surface::PixelFormat::S8Z24)};
+ VideoCore::Surface::GetBytesPerPixel(VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
@@ -73,7 +73,7 @@ void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format,
in_data, width, height, depth, block_width, block_height);
std::copy(rgba8_data.begin(), rgba8_data.end(), out_data);
- } else if (convert_s8z24 && pixel_format == PixelFormat::S8Z24) {
+ } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) {
Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height);
}
}
@@ -85,7 +85,7 @@ void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 h
static_cast<u32>(pixel_format));
UNREACHABLE();
- } else if (convert_s8z24 && pixel_format == PixelFormat::S8Z24) {
+ } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) {
Tegra::Texture::ConvertZ24S8ToS8Z24(data, width, height);
}
}
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 7df5f1452..16d46a018 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -6,11 +6,13 @@
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/bit_util.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace Tegra::Texture {
+namespace {
/**
* This table represents the internal swizzle of a gob,
@@ -36,20 +38,10 @@ struct alignas(64) SwizzleTable {
std::array<std::array<u16, M>, N> values{};
};
-constexpr u32 gob_size_x_shift = 6;
-constexpr u32 gob_size_y_shift = 3;
-constexpr u32 gob_size_z_shift = 0;
-constexpr u32 gob_size_shift = gob_size_x_shift + gob_size_y_shift + gob_size_z_shift;
+constexpr u32 FAST_SWIZZLE_ALIGN = 16;
-constexpr u32 gob_size_x = 1U << gob_size_x_shift;
-constexpr u32 gob_size_y = 1U << gob_size_y_shift;
-constexpr u32 gob_size_z = 1U << gob_size_z_shift;
-constexpr u32 gob_size = 1U << gob_size_shift;
-
-constexpr u32 fast_swizzle_align = 16;
-
-constexpr auto legacy_swizzle_table = SwizzleTable<gob_size_y, gob_size_x, gob_size_z>();
-constexpr auto fast_swizzle_table = SwizzleTable<gob_size_y, 4, fast_swizzle_align>();
+constexpr auto LEGACY_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_X, GOB_SIZE_X, GOB_SIZE_Z>();
+constexpr auto FAST_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_Y, 4, FAST_SWIZZLE_ALIGN>();
/**
* This function manages ALL the GOBs(Group of Bytes) Inside a single block.
@@ -68,17 +60,17 @@ void PreciseProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, con
u32 y_address = z_address;
u32 pixel_base = layer_z * z + y_start * stride_x;
for (u32 y = y_start; y < y_end; y++) {
- const auto& table = legacy_swizzle_table[y % gob_size_y];
+ const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y];
for (u32 x = x_start; x < x_end; x++) {
- const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % gob_size_x]};
+ const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % GOB_SIZE_X]};
const u32 pixel_index{x * out_bytes_per_pixel + pixel_base};
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
}
pixel_base += stride_x;
- if ((y + 1) % gob_size_y == 0)
- y_address += gob_size;
+ if ((y + 1) % GOB_SIZE_Y == 0)
+ y_address += GOB_SIZE;
}
z_address += xy_block_size;
}
@@ -103,18 +95,18 @@ void FastProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const
u32 y_address = z_address;
u32 pixel_base = layer_z * z + y_start * stride_x;
for (u32 y = y_start; y < y_end; y++) {
- const auto& table = fast_swizzle_table[y % gob_size_y];
- for (u32 xb = x_startb; xb < x_endb; xb += fast_swizzle_align) {
- const u32 swizzle_offset{y_address + table[(xb / fast_swizzle_align) % 4]};
+ const auto& table = FAST_SWIZZLE_TABLE[y % GOB_SIZE_Y];
+ for (u32 xb = x_startb; xb < x_endb; xb += FAST_SWIZZLE_ALIGN) {
+ const u32 swizzle_offset{y_address + table[(xb / FAST_SWIZZLE_ALIGN) % 4]};
const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
const u32 pixel_index{out_x + pixel_base};
data_ptrs[unswizzle ? 1 : 0] = swizzled_data + swizzle_offset;
data_ptrs[unswizzle ? 0 : 1] = unswizzled_data + pixel_index;
- std::memcpy(data_ptrs[0], data_ptrs[1], fast_swizzle_align);
+ std::memcpy(data_ptrs[0], data_ptrs[1], FAST_SWIZZLE_ALIGN);
}
pixel_base += stride_x;
- if ((y + 1) % gob_size_y == 0)
- y_address += gob_size;
+ if ((y + 1) % GOB_SIZE_Y == 0)
+ y_address += GOB_SIZE;
}
z_address += xy_block_size;
}
@@ -137,9 +129,9 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool
auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
const u32 stride_x = width * out_bytes_per_pixel;
const u32 layer_z = height * stride_x;
- const u32 gob_elements_x = gob_size_x / bytes_per_pixel;
- constexpr u32 gob_elements_y = gob_size_y;
- constexpr u32 gob_elements_z = gob_size_z;
+ const u32 gob_elements_x = GOB_SIZE_X / bytes_per_pixel;
+ constexpr u32 gob_elements_y = GOB_SIZE_Y;
+ constexpr u32 gob_elements_z = GOB_SIZE_Z;
const u32 block_x_elements = gob_elements_x;
const u32 block_y_elements = gob_elements_y * block_height;
const u32 block_z_elements = gob_elements_z * block_depth;
@@ -147,7 +139,7 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool
const u32 blocks_on_x = div_ceil(aligned_width, block_x_elements);
const u32 blocks_on_y = div_ceil(height, block_y_elements);
const u32 blocks_on_z = div_ceil(depth, block_z_elements);
- const u32 xy_block_size = gob_size * block_height;
+ const u32 xy_block_size = GOB_SIZE * block_height;
const u32 block_size = xy_block_size * block_depth;
u32 tile_offset = 0;
for (u32 zb = 0; zb < blocks_on_z; zb++) {
@@ -174,12 +166,14 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool
}
}
+} // Anonymous namespace
+
void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data,
bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) {
const u32 block_height_size{1U << block_height};
const u32 block_depth_size{1U << block_depth};
- if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % fast_swizzle_align == 0) {
+ if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % FAST_SWIZZLE_ALIGN == 0) {
SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
bytes_per_pixel, out_bytes_per_pixel, block_height_size,
block_depth_size, width_spacing);
@@ -190,53 +184,6 @@ void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
}
}
-u32 BytesPerPixel(TextureFormat format) {
- switch (format) {
- case TextureFormat::DXT1:
- case TextureFormat::DXN1:
- // In this case a 'pixel' actually refers to a 4x4 tile.
- return 8;
- case TextureFormat::DXT23:
- case TextureFormat::DXT45:
- case TextureFormat::DXN2:
- case TextureFormat::BC7U:
- case TextureFormat::BC6H_UF16:
- case TextureFormat::BC6H_SF16:
- // In this case a 'pixel' actually refers to a 4x4 tile.
- return 16;
- case TextureFormat::R32_G32_B32:
- return 12;
- case TextureFormat::ASTC_2D_4X4:
- case TextureFormat::ASTC_2D_5X4:
- case TextureFormat::ASTC_2D_8X8:
- case TextureFormat::ASTC_2D_8X5:
- case TextureFormat::ASTC_2D_10X8:
- case TextureFormat::ASTC_2D_5X5:
- case TextureFormat::A8R8G8B8:
- case TextureFormat::A2B10G10R10:
- case TextureFormat::BF10GF11RF11:
- case TextureFormat::R32:
- case TextureFormat::R16_G16:
- return 4;
- case TextureFormat::A1B5G5R5:
- case TextureFormat::B5G6R5:
- case TextureFormat::G8R8:
- case TextureFormat::R16:
- return 2;
- case TextureFormat::R8:
- return 1;
- case TextureFormat::R16_G16_B16_A16:
- return 8;
- case TextureFormat::R32_G32_B32_A32:
- return 16;
- case TextureFormat::R32_G32:
- return 8;
- default:
- UNIMPLEMENTED_MSG("Format not implemented");
- return 1;
- }
-}
-
void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
u32 block_depth, u32 width_spacing) {
@@ -256,47 +203,82 @@ std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y,
}
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
+ u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data,
u32 block_height_bit, u32 offset_x, u32 offset_y) {
const u32 block_height = 1U << block_height_bit;
- const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
- gob_size_x};
+ const u32 image_width_in_gobs =
+ (swizzled_width * bytes_per_pixel + (GOB_SIZE_X - 1)) / GOB_SIZE_X;
for (u32 line = 0; line < subrect_height; ++line) {
const u32 dst_y = line + offset_y;
const u32 gob_address_y =
- (dst_y / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs +
- ((dst_y % (gob_size_y * block_height)) / gob_size_y) * gob_size;
- const auto& table = legacy_swizzle_table[dst_y % gob_size_y];
+ (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
+ ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
+ const auto& table = LEGACY_SWIZZLE_TABLE[dst_y % GOB_SIZE_Y];
for (u32 x = 0; x < subrect_width; ++x) {
const u32 dst_x = x + offset_x;
const u32 gob_address =
- gob_address_y + (dst_x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
- const u32 swizzled_offset = gob_address + table[(dst_x * bytes_per_pixel) % gob_size_x];
- u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
- u8* dest_addr = swizzled_data + swizzled_offset;
+ gob_address_y + (dst_x * bytes_per_pixel / GOB_SIZE_X) * GOB_SIZE * block_height;
+ const u32 swizzled_offset = gob_address + table[(dst_x * bytes_per_pixel) % GOB_SIZE_X];
+ const u32 unswizzled_offset = line * source_pitch + x * bytes_per_pixel;
+ const u8* const source_line = unswizzled_data + unswizzled_offset;
+ u8* const dest_addr = swizzled_data + swizzled_offset;
std::memcpy(dest_addr, source_line, bytes_per_pixel);
}
}
}
-void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
- u32 block_height_bit, u32 offset_x, u32 offset_y) {
- const u32 block_height = 1U << block_height_bit;
- for (u32 line = 0; line < subrect_height; ++line) {
- const u32 y2 = line + offset_y;
- const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height +
- ((y2 % (gob_size_y * block_height)) / gob_size_y) * gob_size;
- const auto& table = legacy_swizzle_table[y2 % gob_size_y];
- for (u32 x = 0; x < subrect_width; ++x) {
- const u32 x2 = (x + offset_x) * bytes_per_pixel;
- const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height;
- const u32 swizzled_offset = gob_address + table[x2 % gob_size_x];
- u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
- u8* source_addr = swizzled_data + swizzled_offset;
+void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
+ u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) {
+ const u32 stride = width * bytes_per_pixel;
+ const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X;
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height);
+
+ const u32 block_height_mask = (1U << block_height) - 1;
+ const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height;
+
+ for (u32 line = 0; line < line_count; ++line) {
+ const u32 src_y = line + origin_y;
+ const auto& table = LEGACY_SWIZZLE_TABLE[src_y % GOB_SIZE_Y];
+
+ const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT;
+ const u32 src_offset_y = (block_y >> block_height) * block_size +
+ ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
+ for (u32 column = 0; column < line_length_in; ++column) {
+ const u32 src_x = (column + origin_x) * bytes_per_pixel;
+ const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift;
+
+ const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X];
+ const u32 unswizzled_offset = line * pitch + column * bytes_per_pixel;
+
+ std::memcpy(output + unswizzled_offset, input + swizzled_offset, bytes_per_pixel);
+ }
+ }
+}
+
+void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height,
+ u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x,
+ u32 origin_y, u8* output, const u8* input) {
+ UNIMPLEMENTED_IF(origin_x > 0);
+ UNIMPLEMENTED_IF(origin_y > 0);
- std::memcpy(dest_line, source_addr, bytes_per_pixel);
+ const u32 stride = width * bytes_per_pixel;
+ const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X;
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
+
+ const u32 block_height_mask = (1U << block_height) - 1;
+ const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth;
+
+ for (u32 line = 0; line < line_count; ++line) {
+ const auto& table = LEGACY_SWIZZLE_TABLE[line % GOB_SIZE_Y];
+ const u32 block_y = line / GOB_SIZE_Y;
+ const u32 dst_offset_y =
+ (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE;
+ for (u32 x = 0; x < line_length_in; ++x) {
+ const u32 dst_offset =
+ ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + table[x % GOB_SIZE_X];
+ const u32 src_offset = x * bytes_per_pixel + line * pitch;
+ std::memcpy(output + dst_offset, input + src_offset, bytes_per_pixel);
}
}
}
@@ -305,17 +287,17 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32
const u32 block_height_bit, const std::size_t copy_size, const u8* source_data,
u8* swizzle_data) {
const u32 block_height = 1U << block_height_bit;
- const u32 image_width_in_gobs{(width + gob_size_x - 1) / gob_size_x};
+ const u32 image_width_in_gobs{(width + GOB_SIZE_X - 1) / GOB_SIZE_X};
std::size_t count = 0;
for (std::size_t y = dst_y; y < height && count < copy_size; ++y) {
const std::size_t gob_address_y =
- (y / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs +
- ((y % (gob_size_y * block_height)) / gob_size_y) * gob_size;
- const auto& table = legacy_swizzle_table[y % gob_size_y];
+ (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
+ ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
+ const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y];
for (std::size_t x = dst_x; x < width && count < copy_size; ++x) {
const std::size_t gob_address =
- gob_address_y + (x / gob_size_x) * gob_size * block_height;
- const std::size_t swizzled_offset = gob_address + table[x % gob_size_x];
+ gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height;
+ const std::size_t swizzled_offset = gob_address + table[x % GOB_SIZE_X];
const u8* source_line = source_data + count;
u8* dest_addr = swizzle_data + swizzled_offset;
count++;
@@ -325,58 +307,30 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32
}
}
-std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
- u32 height) {
- std::vector<u8> rgba_data;
-
- // TODO(Subv): Implement.
- switch (format) {
- case TextureFormat::DXT1:
- case TextureFormat::DXT23:
- case TextureFormat::DXT45:
- case TextureFormat::DXN1:
- case TextureFormat::DXN2:
- case TextureFormat::BC7U:
- case TextureFormat::BC6H_UF16:
- case TextureFormat::BC6H_SF16:
- case TextureFormat::ASTC_2D_4X4:
- case TextureFormat::ASTC_2D_8X8:
- case TextureFormat::ASTC_2D_5X5:
- case TextureFormat::ASTC_2D_10X8:
- case TextureFormat::A8R8G8B8:
- case TextureFormat::A2B10G10R10:
- case TextureFormat::A1B5G5R5:
- case TextureFormat::B5G6R5:
- case TextureFormat::R8:
- case TextureFormat::G8R8:
- case TextureFormat::BF10GF11RF11:
- case TextureFormat::R32_G32_B32_A32:
- case TextureFormat::R32_G32:
- case TextureFormat::R32:
- case TextureFormat::R16:
- case TextureFormat::R16_G16:
- case TextureFormat::R32_G32_B32:
- // TODO(Subv): For the time being just forward the same data without any decoding.
- rgba_data = texture_data;
- break;
- default:
- UNIMPLEMENTED_MSG("Format not implemented");
- break;
- }
-
- return rgba_data;
-}
-
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth) {
if (tiled) {
- const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, gob_size_x_shift);
- const u32 aligned_height = Common::AlignBits(height, gob_size_y_shift + block_height);
- const u32 aligned_depth = Common::AlignBits(depth, gob_size_z_shift + block_depth);
+ const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, GOB_SIZE_X_SHIFT);
+ const u32 aligned_height = Common::AlignBits(height, GOB_SIZE_Y_SHIFT + block_height);
+ const u32 aligned_depth = Common::AlignBits(depth, GOB_SIZE_Z_SHIFT + block_depth);
return aligned_width * aligned_height * aligned_depth;
} else {
return width * height * depth * bytes_per_pixel;
}
}
+u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
+ u32 bytes_per_pixel) {
+ auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
+ const u32 gobs_in_block = 1 << block_height;
+ const u32 y_blocks = GOB_SIZE_Y << block_height;
+ const u32 x_per_gob = GOB_SIZE_X / bytes_per_pixel;
+ const u32 x_blocks = div_ceil(width, x_per_gob);
+ const u32 block_size = GOB_SIZE * gobs_in_block;
+ const u32 stride = block_size * x_blocks;
+ const u32 base = (dst_y / y_blocks) * stride + (dst_x / x_per_gob) * block_size;
+ const u32 relative_y = dst_y % y_blocks;
+ return base + (relative_y / GOB_SIZE_Y) * GOB_SIZE;
+}
+
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index e5eac3f3b..01e156bc8 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -10,15 +10,15 @@
namespace Tegra::Texture {
-// GOBSize constant. Calculated by 64 bytes in x multiplied by 8 y coords, represents
-// an small rect of (64/bytes_per_pixel)X8.
-inline std::size_t GetGOBSize() {
- return 512;
-}
+constexpr u32 GOB_SIZE_X = 64;
+constexpr u32 GOB_SIZE_Y = 8;
+constexpr u32 GOB_SIZE_Z = 1;
+constexpr u32 GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z;
-inline std::size_t GetGOBSizeShift() {
- return 9;
-}
+constexpr std::size_t GOB_SIZE_X_SHIFT = 6;
+constexpr std::size_t GOB_SIZE_Y_SHIFT = 3;
+constexpr std::size_t GOB_SIZE_Z_SHIFT = 0;
+constexpr std::size_t GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
/// Unswizzles a swizzled texture without changing its format.
void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
@@ -38,26 +38,42 @@ void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing);
-/// Decodes an unswizzled texture into a A8R8G8B8 texture.
-std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
- u32 height);
-
/// This function calculates the correct size of a texture depending if it's tiled or not.
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth);
/// Copies an untiled subrectangle into a tiled surface.
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
- u32 offset_x, u32 offset_y);
+ u32 bytes_per_pixel, u8* swizzled_data, const u8* unswizzled_data,
+ u32 block_height_bit, u32 offset_x, u32 offset_y);
/// Copies a tiled subrectangle into a linear surface.
-void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
- u32 offset_x, u32 offset_y);
+void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
+ u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input);
+
+/// @brief Swizzles a 2D array of pixels into a 3D texture
+/// @param line_length_in Number of pixels per line
+/// @param line_count Number of lines
+/// @param pitch Number of bytes per line
+/// @param width Width of the swizzled texture
+/// @param height Height of the swizzled texture
+/// @param bytes_per_pixel Number of bytes used per pixel
+/// @param block_height Block height shift
+/// @param block_depth Block depth shift
+/// @param origin_x Column offset in pixels of the swizzled texture
+/// @param origin_y Row offset in pixels of the swizzled texture
+/// @param output Pointer to the pixels of the swizzled texture
+/// @param input Pointer to the 2D array of pixels used as input
+/// @pre input and output points to an array large enough to hold the number of bytes used
+void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 height,
+ u32 bytes_per_pixel, u32 block_height, u32 block_depth, u32 origin_x,
+ u32 origin_y, u8* output, const u8* input);
+
+void SwizzleKepler(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
+ std::size_t copy_size, const u8* source_data, u8* swizzle_data);
-void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
- const u32 block_height, const std::size_t copy_size, const u8* source_data,
- u8* swizzle_data);
+/// Obtains the offset of the gob for positions 'dst_x' & 'dst_y'
+u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
+ u32 bytes_per_pixel);
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index d1939d744..4171e3ef2 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -48,7 +48,7 @@ constexpr std::array<float, 256> SRGB_CONVERSION_LUT = {
};
unsigned SettingsMinimumAnisotropy() noexcept {
- switch (static_cast<Anisotropy>(Settings::values.max_anisotropy)) {
+ switch (static_cast<Anisotropy>(Settings::values.max_anisotropy.GetValue())) {
default:
case Anisotropy::Default:
return 1U;
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index eba05aced..0574fef12 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -12,10 +12,10 @@
namespace Tegra::Texture {
enum class TextureFormat : u32 {
- R32_G32_B32_A32 = 0x01,
- R32_G32_B32 = 0x02,
- R16_G16_B16_A16 = 0x03,
- R32_G32 = 0x04,
+ R32G32B32A32 = 0x01,
+ R32G32B32 = 0x02,
+ R16G16B16A16 = 0x03,
+ R32G32 = 0x04,
R32_B24G8 = 0x05,
ETC2_RGB = 0x06,
X8B8G8R8 = 0x07,
@@ -23,19 +23,19 @@ enum class TextureFormat : u32 {
A2B10G10R10 = 0x09,
ETC2_RGB_PTA = 0x0a,
ETC2_RGBA = 0x0b,
- R16_G16 = 0x0c,
- G8R24 = 0x0d,
- G24R8 = 0x0e,
+ R16G16 = 0x0c,
+ R24G8 = 0x0d,
+ R8G24 = 0x0e,
R32 = 0x0f,
- BC6H_SF16 = 0x10,
- BC6H_UF16 = 0x11,
+ BC6H_SFLOAT = 0x10,
+ BC6H_UFLOAT = 0x11,
A4B4G4R4 = 0x12,
A5B5G5R1 = 0x13,
A1B5G5R5 = 0x14,
B5G6R5 = 0x15,
B6G5R5 = 0x16,
- BC7U = 0x17,
- G8R8 = 0x18,
+ BC7 = 0x17,
+ R8G8 = 0x18,
EAC = 0x19,
EACX2 = 0x1a,
R16 = 0x1b,
@@ -43,23 +43,23 @@ enum class TextureFormat : u32 {
R8 = 0x1d,
G4R4 = 0x1e,
R1 = 0x1f,
- E5B9G9R9_SHAREDEXP = 0x20,
- BF10GF11RF11 = 0x21,
+ E5B9G9R9 = 0x20,
+ B10G11R11 = 0x21,
G8B8G8R8 = 0x22,
B8G8R8G8 = 0x23,
- DXT1 = 0x24,
- DXT23 = 0x25,
- DXT45 = 0x26,
- DXN1 = 0x27,
- DXN2 = 0x28,
- S8Z24 = 0x29,
+ BC1_RGBA = 0x24,
+ BC2 = 0x25,
+ BC3 = 0x26,
+ BC4 = 0x27,
+ BC5 = 0x28,
+ S8D24 = 0x29,
X8Z24 = 0x2a,
- Z24S8 = 0x2b,
+ D24S8 = 0x2b,
X4V4Z24__COV4R4V = 0x2c,
X4V4Z24__COV8R8V = 0x2d,
V8Z24__COV4R12V = 0x2e,
- ZF32 = 0x2f,
- ZF32_X24S8 = 0x30,
+ D32 = 0x2f,
+ D32S8 = 0x30,
X8Z24_X20V4S8__COV4R4V = 0x31,
X8Z24_X20V4S8__COV8R8V = 0x32,
ZF32_X20V4X8__COV4R4V = 0x33,
@@ -69,7 +69,7 @@ enum class TextureFormat : u32 {
X8Z24_X16V8S8__COV4R12V = 0x37,
ZF32_X16V8X8__COV4R12V = 0x38,
ZF32_X16V8S8__COV4R12V = 0x39,
- Z16 = 0x3a,
+ D16 = 0x3a,
V8Z24__COV8R24V = 0x3b,
X8Z24_X16V8S8__COV8R24V = 0x3c,
ZF32_X16V8X8__COV8R24V = 0x3d,
@@ -375,7 +375,4 @@ struct FullTextureInfo {
TSCEntry tsc;
};
-/// Returns the number of bytes per pixel of the input texture format.
-u32 BytesPerPixel(TextureFormat format);
-
} // namespace Tegra::Texture
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index f60bdc60a..dd5cee4a1 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
+
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
@@ -16,43 +17,56 @@
#include "video_core/video_core.h"
namespace {
-std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
- Core::System& system,
- Core::Frontend::GraphicsContext& context) {
- switch (Settings::values.renderer_backend) {
+
+std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
+ Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context) {
+ auto& telemetry_session = system.TelemetrySession();
+ auto& cpu_memory = system.Memory();
+
+ switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system, context);
+ return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
#ifdef HAS_VULKAN
case Settings::RendererBackend::Vulkan:
- return std::make_unique<Vulkan::RendererVulkan>(emu_window, system);
+ return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
#endif
default:
return nullptr;
}
}
+
} // Anonymous namespace
namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
+ std::unique_ptr<Tegra::GPU> gpu;
+ const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue();
+ if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ gpu = std::make_unique<VideoCommon::GPUAsynch>(system, use_nvdec);
+ } else {
+ gpu = std::make_unique<VideoCommon::GPUSynch>(system, use_nvdec);
+ }
+
auto context = emu_window.CreateSharedContext();
const auto scope = context->Acquire();
- auto renderer = CreateRenderer(emu_window, system, *context);
+
+ auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context));
if (!renderer->Init()) {
return nullptr;
}
- if (Settings::values.use_asynchronous_gpu_emulation) {
- return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer),
- std::move(context));
- }
- return std::make_unique<VideoCommon::GPUSynch>(system, std::move(renderer), std::move(context));
+ gpu->BindRenderer(std::move(renderer));
+ return gpu;
}
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
return static_cast<u16>(
- Settings::values.resolution_factor != 0
- ? Settings::values.resolution_factor
+ Settings::values.resolution_factor.GetValue() != 0
+ ? Settings::values.resolution_factor.GetValue()
: renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio());
}
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 01f2d129d..ae85a72ea 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -5,12 +5,8 @@ add_library(web_service STATIC
verify_login.h
web_backend.cpp
web_backend.h
+ web_result.h
)
create_target_directory_groups(web_service)
-
-get_directory_property(OPENSSL_LIBS
- DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
- DEFINITION OPENSSL_LIBS)
-target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT)
-target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser)
+target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 7538389bf..6215c914f 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -2,14 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <json.hpp>
+#include <nlohmann/json.hpp>
#include "common/detached_tasks.h"
-#include "common/web_result.h"
#include "web_service/telemetry_json.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
+namespace Telemetry = Common::Telemetry;
+
struct TelemetryJson::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
@@ -123,7 +125,7 @@ bool TelemetryJson::SubmitTestcase() {
Client client(impl->host, impl->username, impl->token);
auto value = client.PostJson("/gamedb/testcase", content, false);
- return value.result_code == Common::WebResult::Code::Success;
+ return value.result_code == WebResult::Code::Success;
}
} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index dfd202829..df51e00f8 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -14,25 +14,25 @@ namespace WebService {
* Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
* yuzu web service
*/
-class TelemetryJson : public Telemetry::VisitorInterface {
+class TelemetryJson : public Common::Telemetry::VisitorInterface {
public:
TelemetryJson(std::string host, std::string username, std::string token);
~TelemetryJson() override;
- void Visit(const Telemetry::Field<bool>& field) override;
- void Visit(const Telemetry::Field<double>& field) override;
- void Visit(const Telemetry::Field<float>& field) override;
- void Visit(const Telemetry::Field<u8>& field) override;
- void Visit(const Telemetry::Field<u16>& field) override;
- void Visit(const Telemetry::Field<u32>& field) override;
- void Visit(const Telemetry::Field<u64>& field) override;
- void Visit(const Telemetry::Field<s8>& field) override;
- void Visit(const Telemetry::Field<s16>& field) override;
- void Visit(const Telemetry::Field<s32>& field) override;
- void Visit(const Telemetry::Field<s64>& field) override;
- void Visit(const Telemetry::Field<std::string>& field) override;
- void Visit(const Telemetry::Field<const char*>& field) override;
- void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
+ void Visit(const Common::Telemetry::Field<bool>& field) override;
+ void Visit(const Common::Telemetry::Field<double>& field) override;
+ void Visit(const Common::Telemetry::Field<float>& field) override;
+ void Visit(const Common::Telemetry::Field<u8>& field) override;
+ void Visit(const Common::Telemetry::Field<u16>& field) override;
+ void Visit(const Common::Telemetry::Field<u32>& field) override;
+ void Visit(const Common::Telemetry::Field<u64>& field) override;
+ void Visit(const Common::Telemetry::Field<s8>& field) override;
+ void Visit(const Common::Telemetry::Field<s16>& field) override;
+ void Visit(const Common::Telemetry::Field<s32>& field) override;
+ void Visit(const Common::Telemetry::Field<s64>& field) override;
+ void Visit(const Common::Telemetry::Field<std::string>& field) override;
+ void Visit(const Common::Telemetry::Field<const char*>& field) override;
+ void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override;
void Complete() override;
bool SubmitTestcase() override;
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index ca4b43b93..ceb55ca6b 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -2,10 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <json.hpp>
-#include "common/web_result.h"
+#include <nlohmann/json.hpp>
#include "web_service/verify_login.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 737ffe409..67183e64c 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -6,21 +6,18 @@
#include <cstdlib>
#include <mutex>
#include <string>
-#include <LUrlParser.h>
+
#include <fmt/format.h>
#include <httplib.h>
-#include "common/common_types.h"
+
#include "common/logging/log.h"
-#include "common/web_result.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
constexpr std::array<const char, 1> API_VERSION{'1'};
-constexpr int HTTP_PORT = 80;
-constexpr int HTTPS_PORT = 443;
-
constexpr std::size_t TIMEOUT_SECONDS = 30;
struct Client::Impl {
@@ -33,17 +30,16 @@ struct Client::Impl {
}
/// A generic function handles POST, GET and DELETE request together
- Common::WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, bool allow_anonymous,
- const std::string& accept) {
+ WebResult GenericRequest(const std::string& method, const std::string& path,
+ const std::string& data, bool allow_anonymous,
+ const std::string& accept) {
if (jwt.empty()) {
UpdateJWT();
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
- return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
- "Credentials needed"};
+ return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""};
}
auto result = GenericRequest(method, path, data, accept, jwt);
@@ -62,33 +58,22 @@ struct Client::Impl {
* username + token is used if jwt is empty but username and token are
* not empty anonymous if all of jwt, username and token are empty
*/
- Common::WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, const std::string& accept,
- const std::string& jwt = "", const std::string& username = "",
- const std::string& token = "") {
+ WebResult GenericRequest(const std::string& method, const std::string& path,
+ const std::string& data, const std::string& accept,
+ const std::string& jwt = "", const std::string& username = "",
+ const std::string& token = "") {
if (cli == nullptr) {
- auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
- int port;
- if (parsedUrl.m_Scheme == "http") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTP_PORT;
- }
- cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
- } else if (parsedUrl.m_Scheme == "https") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTPS_PORT;
- }
- cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
- } else {
- LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
- }
+ cli = std::make_unique<httplib::Client>(host.c_str());
}
- if (cli == nullptr) {
- LOG_ERROR(WebService, "Invalid URL {}", host + path);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
+
+ if (!cli->is_valid()) {
+ LOG_ERROR(WebService, "Client is invalid, skipping request!");
+ return {};
}
- cli->set_timeout_sec(TIMEOUT_SECONDS);
+
+ cli->set_connection_timeout(TIMEOUT_SECONDS);
+ cli->set_read_timeout(TIMEOUT_SECONDS);
+ cli->set_write_timeout(TIMEOUT_SECONDS);
httplib::Headers params;
if (!jwt.empty()) {
@@ -106,7 +91,7 @@ struct Client::Impl {
std::string(API_VERSION.begin(), API_VERSION.end()));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
- };
+ }
httplib::Request request;
request.method = method;
@@ -118,29 +103,28 @@ struct Client::Impl {
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
- return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
+ return WebResult{WebResult::Code::LibError, "Null response", ""};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
- return Common::WebResult{Common::WebResult::Code::HttpError,
- std::to_string(response.status)};
+ return WebResult{WebResult::Code::HttpError, std::to_string(response.status), ""};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
- return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+ return WebResult{WebResult::Code::WrongContent, "", ""};
}
if (content_type->second.find(accept) == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
- return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
+ return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
}
- return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
+ return WebResult{WebResult::Code::Success, "", response.body};
}
// Retrieve a new JWT from given username and token
@@ -150,7 +134,7 @@ struct Client::Impl {
}
auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
- if (result.result_code != Common::WebResult::Code::Success) {
+ if (result.result_code != WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
std::lock_guard lock{jwt_cache.mutex};
@@ -180,29 +164,28 @@ Client::Client(std::string host, std::string username, std::string token)
Client::~Client() = default;
-Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
+WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) {
return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
}
-Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
}
-Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
+WebResult Client::DeleteJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
}
-Common::WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
}
-Common::WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
}
-Common::WebResult Client::GetExternalJWT(const std::string& audience) {
+WebResult Client::GetExternalJWT(const std::string& audience) {
return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
"text/html");
}
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 04121f17e..81f58583c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -7,12 +7,10 @@
#include <memory>
#include <string>
-namespace Common {
-struct WebResult;
-}
-
namespace WebService {
+struct WebResult;
+
class Client {
public:
Client(std::string host, std::string username, std::string token);
@@ -25,8 +23,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult PostJson(const std::string& path, const std::string& data,
- bool allow_anonymous);
+ WebResult PostJson(const std::string& path, const std::string& data, bool allow_anonymous);
/**
* Gets JSON from the specified path.
@@ -34,7 +31,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
+ WebResult GetJson(const std::string& path, bool allow_anonymous);
/**
* Deletes JSON to the specified path.
@@ -43,8 +40,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous);
+ WebResult DeleteJson(const std::string& path, const std::string& data, bool allow_anonymous);
/**
* Gets a plain string from the specified path.
@@ -52,7 +48,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetPlain(const std::string& path, bool allow_anonymous);
+ WebResult GetPlain(const std::string& path, bool allow_anonymous);
/**
* Gets an PNG image from the specified path.
@@ -60,14 +56,14 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetImage(const std::string& path, bool allow_anonymous);
+ WebResult GetImage(const std::string& path, bool allow_anonymous);
/**
* Requests an external JWT for the specific audience provided.
* @param audience the audience of the JWT requested.
* @return the result of the request.
*/
- Common::WebResult GetExternalJWT(const std::string& audience);
+ WebResult GetExternalJWT(const std::string& audience);
private:
struct Impl;
diff --git a/src/web_service/web_result.h b/src/web_service/web_result.h
new file mode 100644
index 000000000..3aeeb5288
--- /dev/null
+++ b/src/web_service/web_result.h
@@ -0,0 +1,25 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "common/common_types.h"
+
+namespace WebService {
+struct WebResult {
+ enum class Code : u32 {
+ Success,
+ InvalidURL,
+ CredentialsMissing,
+ LibError,
+ HttpError,
+ WrongContent,
+ NoWebservice,
+ };
+ Code result_code;
+ std::string result_string;
+ std::string returned_data;
+};
+} // namespace WebService
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 8b9404718..b16b54032 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -9,6 +9,9 @@ add_executable(yuzu
about_dialog.cpp
about_dialog.h
aboutdialog.ui
+ applets/controller.cpp
+ applets/controller.h
+ applets/controller.ui
applets/error.cpp
applets/error.h
applets/profile_select.cpp
@@ -24,13 +27,24 @@ add_executable(yuzu
compatibility_list.h
configuration/config.cpp
configuration/config.h
+ configuration/configuration_shared.cpp
+ configuration/configuration_shared.h
configuration/configure.ui
configuration/configure_audio.cpp
configuration/configure_audio.h
configuration/configure_audio.ui
+ configuration/configure_cpu.cpp
+ configuration/configure_cpu.h
+ configuration/configure_cpu.ui
+ configuration/configure_cpu_debug.cpp
+ configuration/configure_cpu_debug.h
+ configuration/configure_cpu_debug.ui
configuration/configure_debug.cpp
configuration/configure_debug.h
configuration/configure_debug.ui
+ configuration/configure_debug_controller.cpp
+ configuration/configure_debug_controller.h
+ configuration/configure_debug_controller.ui
configuration/configure_dialog.cpp
configuration/configure_dialog.h
configuration/configure_filesystem.cpp
@@ -51,18 +65,27 @@ add_executable(yuzu
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input.ui
+ configuration/configure_input_advanced.cpp
+ configuration/configure_input_advanced.h
+ configuration/configure_input_advanced.ui
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_input_player.ui
- configuration/configure_input_simple.cpp
- configuration/configure_input_simple.h
- configuration/configure_input_simple.ui
+ configuration/configure_input_profile_dialog.cpp
+ configuration/configure_input_profile_dialog.h
+ configuration/configure_input_profile_dialog.ui
+ configuration/configure_motion_touch.cpp
+ configuration/configure_motion_touch.h
+ configuration/configure_motion_touch.ui
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_mouse_advanced.ui
- configuration/configure_per_general.cpp
- configuration/configure_per_general.h
- configuration/configure_per_general.ui
+ configuration/configure_per_game.cpp
+ configuration/configure_per_game.h
+ configuration/configure_per_game.ui
+ configuration/configure_per_game_addons.cpp
+ configuration/configure_per_game_addons.h
+ configuration/configure_per_game_addons.ui
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_profile_manager.ui
@@ -72,15 +95,24 @@ add_executable(yuzu
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
+ configuration/configure_touch_from_button.cpp
+ configuration/configure_touch_from_button.h
+ configuration/configure_touch_from_button.ui
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_touchscreen_advanced.ui
+ configuration/configure_touch_widget.h
configuration/configure_ui.cpp
configuration/configure_ui.h
configuration/configure_ui.ui
+ configuration/configure_vibration.cpp
+ configuration/configure_vibration.h
+ configuration/configure_vibration.ui
configuration/configure_web.cpp
configuration/configure_web.h
configuration/configure_web.ui
+ configuration/input_profiles.cpp
+ configuration/input_profiles.h
debugger/console.cpp
debugger/console.h
debugger/profiler.cpp
@@ -93,11 +125,13 @@ add_executable(yuzu
game_list_p.h
game_list_worker.cpp
game_list_worker.h
+ hotkeys.cpp
+ hotkeys.h
+ install_dialog.cpp
+ install_dialog.h
loading_screen.cpp
loading_screen.h
loading_screen.ui
- hotkeys.cpp
- hotkeys.h
main.cpp
main.h
main.ui
@@ -120,11 +154,44 @@ file(GLOB COMPAT_LIST
file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
+if (ENABLE_QT_TRANSLATION)
+ set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
+ option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
+
+ # Update source TS file if enabled
+ if (GENERATE_QT_TRANSLATION)
+ get_target_property(SRCS yuzu SOURCES)
+ qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts)
+ add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts)
+ endif()
+
+ # Find all TS files except en.ts
+ file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts)
+ list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
+
+ # Compile TS files to QM files
+ qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
+
+ # Build a QRC file from the QM file list
+ set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
+ file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
+ foreach (QM ${LANGUAGES_QM})
+ get_filename_component(QM_FILE ${QM} NAME)
+ file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
+ endforeach (QM)
+ file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
+
+ # Add the QRC file to package in all QM files
+ qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
+else()
+ set(LANGUAGES)
+endif()
target_sources(yuzu
PRIVATE
${COMPAT_LIST}
${ICONS}
+ ${LANGUAGES}
${THEMES}
)
@@ -147,7 +214,7 @@ endif()
create_target_directory_groups(yuzu)
target_link_libraries(yuzu PRIVATE common core input_common video_core)
-target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
+target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (ENABLE_VULKAN AND NOT WIN32)
@@ -202,10 +269,14 @@ endif()
if (MSVC)
include(CopyYuzuQt5Deps)
include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
+ include(CopyYuzuFFmpegDeps)
copy_yuzu_Qt5_deps(yuzu)
copy_yuzu_SDL_deps(yuzu)
- copy_yuzu_unicorn_deps(yuzu)
+ copy_yuzu_FFmpeg_deps(yuzu)
+endif()
+
+if (NOT APPLE)
+ target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
if (ENABLE_VULKAN)
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index f122ba39d..1b320630c 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -160,32 +160,12 @@ p, li { white-space: pre-wrap; }
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>248</x>
- <y>254</y>
- </hint>
- <hint type="destinationlabel">
- <x>157</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>316</x>
- <y>260</y>
- </hint>
- <hint type="destinationlabel">
- <x>286</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
new file mode 100644
index 000000000..8ecfec770
--- /dev/null
+++ b/src/yuzu/applets/controller.cpp
@@ -0,0 +1,637 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <thread>
+
+#include "common/assert.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/lock.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "ui_controller.h"
+#include "yuzu/applets/controller.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/main.h"
+
+namespace {
+
+constexpr std::size_t HANDHELD_INDEX = 8;
+
+constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
+ {true, false, false, false},
+ {true, true, false, false},
+ {true, true, true, false},
+ {true, true, true, true},
+ {true, false, false, true},
+ {true, false, true, false},
+ {true, false, true, true},
+ {false, true, true, false},
+}};
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+ bool connected) {
+ Core::System& system{Core::System::GetInstance()};
+
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ auto& npad =
+ sm.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
+}
+
+// Returns true if the given controller type is compatible with the given parameters.
+bool IsControllerCompatible(Settings::ControllerType controller_type,
+ Core::Frontend::ControllerParameters parameters) {
+ switch (controller_type) {
+ case Settings::ControllerType::ProController:
+ return parameters.allow_pro_controller;
+ case Settings::ControllerType::DualJoyconDetached:
+ return parameters.allow_dual_joycons;
+ case Settings::ControllerType::LeftJoycon:
+ return parameters.allow_left_joycon;
+ case Settings::ControllerType::RightJoycon:
+ return parameters.allow_right_joycon;
+ case Settings::ControllerType::Handheld:
+ return parameters.enable_single_mode && parameters.allow_handheld;
+ default:
+ return false;
+ }
+}
+
+/// Maps the controller type combobox index to Controller Type enum
+constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
+ switch (index) {
+ case 0:
+ default:
+ return Settings::ControllerType::ProController;
+ case 1:
+ return Settings::ControllerType::DualJoyconDetached;
+ case 2:
+ return Settings::ControllerType::LeftJoycon;
+ case 3:
+ return Settings::ControllerType::RightJoycon;
+ case 4:
+ return Settings::ControllerType::Handheld;
+ }
+}
+
+/// Maps the Controller Type enum to controller type combobox index
+constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
+ switch (type) {
+ case Settings::ControllerType::ProController:
+ default:
+ return 0;
+ case Settings::ControllerType::DualJoyconDetached:
+ return 1;
+ case Settings::ControllerType::LeftJoycon:
+ return 2;
+ case Settings::ControllerType::RightJoycon:
+ return 3;
+ case Settings::ControllerType::Handheld:
+ return 4;
+ }
+}
+
+} // namespace
+
+QtControllerSelectorDialog::QtControllerSelectorDialog(
+ QWidget* parent, Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
+ parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
+ input_profiles(std::make_unique<InputProfiles>()) {
+ ui->setupUi(this);
+
+ player_widgets = {
+ ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
+ ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
+ };
+
+ player_groupboxes = {
+ ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
+ ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
+ ui->groupPlayer7Connected, ui->groupPlayer8Connected,
+ };
+
+ connected_controller_icons = {
+ ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
+ ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
+ };
+
+ led_patterns_boxes = {{
+ {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
+ ui->checkboxPlayer1LED4},
+ {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
+ ui->checkboxPlayer2LED4},
+ {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
+ ui->checkboxPlayer3LED4},
+ {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
+ ui->checkboxPlayer4LED4},
+ {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
+ ui->checkboxPlayer5LED4},
+ {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
+ ui->checkboxPlayer6LED4},
+ {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
+ ui->checkboxPlayer7LED4},
+ {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
+ ui->checkboxPlayer8LED4},
+ }};
+
+ explain_text_labels = {
+ ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
+ ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
+ ui->labelPlayer7Explain, ui->labelPlayer8Explain,
+ };
+
+ emulated_controllers = {
+ ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
+ ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
+ ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
+ };
+
+ player_labels = {
+ ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
+ ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
+ };
+
+ connected_controller_labels = {
+ ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
+ ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
+ ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
+ };
+
+ connected_controller_checkboxes = {
+ ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+ ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+ ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+ };
+
+ // Setup/load everything prior to setting up connections.
+ // This avoids unintentionally changing the states of elements while loading them in.
+ SetSupportedControllers();
+ DisableUnsupportedPlayers();
+ LoadConfiguration();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ SetExplainText(i);
+ UpdateControllerIcon(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+
+ connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
+ if (checked) {
+ for (std::size_t index = 0; index <= i; ++index) {
+ connected_controller_checkboxes[index]->setChecked(checked);
+ }
+ } else {
+ for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
+ connected_controller_checkboxes[index]->setChecked(checked);
+ }
+ }
+ });
+
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this, i](int) {
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ CheckIfParametersMet();
+ });
+
+ connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+ player_groupboxes[i]->setChecked(state == Qt::Checked);
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+ CheckIfParametersMet();
+ });
+
+ if (i == 0) {
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this](int index) {
+ UpdateDockedState(GetControllerTypeFromIndex(index) ==
+ Settings::ControllerType::Handheld);
+ });
+ }
+ }
+
+ connect(ui->vibrationButton, &QPushButton::clicked, this,
+ &QtControllerSelectorDialog::CallConfigureVibrationDialog);
+
+ connect(ui->inputConfigButton, &QPushButton::clicked, this,
+ &QtControllerSelectorDialog::CallConfigureInputProfileDialog);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &QtControllerSelectorDialog::ApplyConfiguration);
+
+ // Enhancement: Check if the parameters have already been met before disconnecting controllers.
+ // If all the parameters are met AND only allows a single player,
+ // stop the constructor here as we do not need to continue.
+ if (CheckIfParametersMet() && parameters.enable_single_mode) {
+ return;
+ }
+
+ // If keep_controllers_connected is false, forcefully disconnect all controllers
+ if (!parameters.keep_controllers_connected) {
+ for (auto player : player_groupboxes) {
+ player->setChecked(false);
+ }
+ }
+
+ resize(0, 0);
+}
+
+QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
+
+int QtControllerSelectorDialog::exec() {
+ if (parameters_met && parameters.enable_single_mode) {
+ return QDialog::Accepted;
+ }
+ return QDialog::exec();
+}
+
+void QtControllerSelectorDialog::ApplyConfiguration() {
+ const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+ Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+ OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
+
+ Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+ Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
+}
+
+void QtControllerSelectorDialog::LoadConfiguration() {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ const auto connected =
+ Settings::values.players.GetValue()[index].connected ||
+ (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
+ player_groupboxes[index]->setChecked(connected);
+ connected_controller_checkboxes[index]->setChecked(connected);
+ emulated_controllers[index]->setCurrentIndex(
+ GetIndexFromControllerType(Settings::values.players.GetValue()[index].controller_type));
+ }
+
+ UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
+
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+ ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
+}
+
+void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
+ ConfigureVibration dialog(this);
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+ }
+}
+
+void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
+ ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get());
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+}
+
+bool QtControllerSelectorDialog::CheckIfParametersMet() {
+ // Here, we check and validate the current configuration against all applicable parameters.
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [this](const QGroupBox* player) { return player->isChecked(); }));
+
+ const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ // First, check against the number of connected players.
+ if (num_connected_players < min_supported_players ||
+ num_connected_players > max_supported_players) {
+ parameters_met = false;
+ ui->buttonBox->setEnabled(parameters_met);
+ return parameters_met;
+ }
+
+ // Next, check against all connected controllers.
+ const auto all_controllers_compatible = [this] {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ // Skip controllers that are not used, we only care about the currently connected ones.
+ if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
+ continue;
+ }
+
+ const auto compatible = IsControllerCompatible(
+ GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()),
+ parameters);
+
+ // If any controller is found to be incompatible, return false early.
+ if (!compatible) {
+ return false;
+ }
+ }
+
+ // Reaching here means all currently connected controllers are compatible.
+ return true;
+ }();
+
+ parameters_met = all_controllers_compatible;
+ ui->buttonBox->setEnabled(parameters_met);
+ return parameters_met;
+}
+
+void QtControllerSelectorDialog::SetSupportedControllers() {
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ if (parameters.enable_single_mode && parameters.allow_handheld) {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
+ } else {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_dual_joycons) {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
+ } else {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_left_joycon) {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
+ } else {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_right_joycon) {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
+ } else {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_pro_controller) {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
+ } else {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
+ .arg(theme));
+ }
+
+ // enable_single_mode overrides min_players and max_players.
+ if (parameters.enable_single_mode) {
+ ui->numberSupportedLabel->setText(QStringLiteral("1"));
+ return;
+ }
+
+ if (parameters.min_players == parameters.max_players) {
+ ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
+ } else {
+ ui->numberSupportedLabel->setText(
+ QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
+ }
+}
+
+void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked()) {
+ connected_controller_icons[player_index]->setStyleSheet(QString{});
+ player_labels[player_index]->show();
+ return;
+ }
+
+ const QString stylesheet = [this, player_index] {
+ switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
+ case Settings::ControllerType::DualJoyconDetached:
+ return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
+ case Settings::ControllerType::LeftJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
+ case Settings::ControllerType::RightJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
+ case Settings::ControllerType::Handheld:
+ return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
+ default:
+ return QString{};
+ }
+ }();
+
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
+ player_labels[player_index]->hide();
+}
+
+void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ const auto controller_type =
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
+ const auto player_connected = player_groupboxes[player_index]->isChecked() &&
+ controller_type != Settings::ControllerType::Handheld;
+
+ if (player.controller_type == controller_type && player.connected == player_connected) {
+ // Set vibration devices in the event that the input device has changed.
+ ConfigureVibration::SetVibrationDevices(player_index);
+ return;
+ }
+
+ // Disconnect the controller first.
+ UpdateController(controller_type, player_index, false);
+
+ player.controller_type = controller_type;
+ player.connected = player_connected;
+
+ ConfigureVibration::SetVibrationDevices(player_index);
+
+ // Handheld
+ if (player_index == 0) {
+ auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ if (controller_type == Settings::ControllerType::Handheld) {
+ handheld = player;
+ }
+ handheld.connected = player_groupboxes[player_index]->isChecked() &&
+ controller_type == Settings::ControllerType::Handheld;
+ UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
+ }
+
+ if (!player.connected) {
+ return;
+ }
+
+ // This emulates a delay between disconnecting and reconnecting controllers as some games
+ // do not respond to a change in controller type if it was instantaneous.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(20ms);
+
+ UpdateController(controller_type, player_index, player_connected);
+}
+
+void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked() ||
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) ==
+ Settings::ControllerType::Handheld) {
+ led_patterns_boxes[player_index][0]->setChecked(false);
+ led_patterns_boxes[player_index][1]->setChecked(false);
+ led_patterns_boxes[player_index][2]->setChecked(false);
+ led_patterns_boxes[player_index][3]->setChecked(false);
+ return;
+ }
+
+ led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
+ led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
+ led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
+ led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
+}
+
+void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
+ if (!parameters.enable_border_color ||
+ player_index >= static_cast<std::size_t>(parameters.max_players) ||
+ player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
+ return;
+ }
+
+ player_groupboxes[player_index]->setStyleSheet(
+ player_groupboxes[player_index]->styleSheet().append(
+ QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
+ "{ border: 1px solid rgba(%2, %3, %4, %5); }")
+ .arg(player_index + 1)
+ .arg(parameters.border_colors[player_index][0])
+ .arg(parameters.border_colors[player_index][1])
+ .arg(parameters.border_colors[player_index][2])
+ .arg(parameters.border_colors[player_index][3])));
+}
+
+void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
+ if (!parameters.enable_explain_text ||
+ player_index >= static_cast<std::size_t>(parameters.max_players)) {
+ return;
+ }
+
+ explain_text_labels[player_index]->setText(QString::fromStdString(
+ Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
+ parameters.explain_text[player_index].size())));
+}
+
+void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
+ // Disallow changing the console mode if the controller type is handheld.
+ ui->radioDocked->setEnabled(!is_handheld);
+ ui->radioUndocked->setEnabled(!is_handheld);
+
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
+ }
+}
+
+void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ switch (max_supported_players) {
+ case 0:
+ default:
+ UNREACHABLE();
+ return;
+ case 1:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ ui->widgetSpacer4->hide();
+ break;
+ case 2:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ break;
+ case 3:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ break;
+ case 4:
+ ui->widgetSpacer->hide();
+ break;
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ break;
+ }
+
+ for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
+ // Disconnect any unsupported players here and disable or hide them if applicable.
+ Settings::values.players.GetValue()[index].connected = false;
+ UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false);
+ // Hide the player widgets when max_supported_controllers is less than or equal to 4.
+ if (max_supported_players <= 4) {
+ player_widgets[index]->hide();
+ }
+
+ // Disable and hide the following to prevent these from interaction.
+ player_widgets[index]->setDisabled(true);
+ connected_controller_checkboxes[index]->setDisabled(true);
+ connected_controller_labels[index]->hide();
+ connected_controller_checkboxes[index]->hide();
+ }
+}
+
+QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
+ connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
+ &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
+ &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
+}
+
+QtControllerSelector::~QtControllerSelector() = default;
+
+void QtControllerSelector::ReconfigureControllers(
+ std::function<void()> callback, const Core::Frontend::ControllerParameters& parameters) const {
+ this->callback = std::move(callback);
+ emit MainWindowReconfigureControllers(parameters);
+}
+
+void QtControllerSelector::MainWindowReconfigureFinished() {
+ // Acquire the HLE mutex
+ std::lock_guard lock(HLE::g_hle_lock);
+ callback();
+}
diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h
new file mode 100644
index 000000000..4344e1dd0
--- /dev/null
+++ b/src/yuzu/applets/controller.h
@@ -0,0 +1,144 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#include "core/frontend/applets/controller.h"
+
+class GMainWindow;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class QtControllerSelectorDialog;
+}
+
+class QtControllerSelectorDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelectorDialog(QWidget* parent,
+ Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_);
+ ~QtControllerSelectorDialog() override;
+
+ int exec() override;
+
+private:
+ // Applies the current configuration.
+ void ApplyConfiguration();
+
+ // Loads the current input configuration into the frontend applet.
+ void LoadConfiguration();
+
+ // Initializes the "Configure Vibration" Dialog.
+ void CallConfigureVibrationDialog();
+
+ // Initializes the "Create Input Profile" Dialog.
+ void CallConfigureInputProfileDialog();
+
+ // Checks the current configuration against the given parameters.
+ // This sets and returns the value of parameters_met.
+ bool CheckIfParametersMet();
+
+ // Sets the controller icons for "Supported Controller Types".
+ void SetSupportedControllers();
+
+ // Updates the controller icons per player.
+ void UpdateControllerIcon(std::size_t player_index);
+
+ // Updates the controller state (type and connection status) per player.
+ void UpdateControllerState(std::size_t player_index);
+
+ // Updates the LED pattern per player.
+ void UpdateLEDPattern(std::size_t player_index);
+
+ // Updates the border color per player.
+ void UpdateBorderColor(std::size_t player_index);
+
+ // Sets the "Explain Text" per player.
+ void SetExplainText(std::size_t player_index);
+
+ // Updates the console mode.
+ void UpdateDockedState(bool is_handheld);
+
+ // Disables and disconnects unsupported players based on the given parameters.
+ void DisableUnsupportedPlayers();
+
+ std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
+
+ // Parameters sent in from the backend HLE applet.
+ Core::Frontend::ControllerParameters parameters;
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<InputProfiles> input_profiles;
+
+ // This is true if and only if all parameters are met. Otherwise, this is false.
+ // This determines whether the "OK" button can be clicked to exit the applet.
+ bool parameters_met{false};
+
+ static constexpr std::size_t NUM_PLAYERS = 8;
+
+ // Widgets encapsulating the groupboxes and comboboxes per player.
+ std::array<QWidget*, NUM_PLAYERS> player_widgets;
+
+ // Groupboxes encapsulating the controller icons and LED patterns per player.
+ std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
+
+ // Icons for currently connected controllers/players.
+ std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
+
+ // Labels that represent the player numbers in place of the controller icons.
+ std::array<QLabel*, NUM_PLAYERS> player_labels;
+
+ // LED patterns for currently connected controllers/players.
+ std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
+
+ // Labels representing additional information known as "Explain Text" per player.
+ std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
+
+ // Comboboxes with a list of emulated controllers per player.
+ std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
+
+ // Labels representing the number of connected controllers
+ // above the "Connected Controllers" checkboxes.
+ std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
+
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
+};
+
+class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelector(GMainWindow& parent);
+ ~QtControllerSelector() override;
+
+ void ReconfigureControllers(
+ std::function<void()> callback,
+ const Core::Frontend::ControllerParameters& parameters) const override;
+
+signals:
+ void MainWindowReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) const;
+
+private:
+ void MainWindowReconfigureFinished();
+
+ mutable std::function<void()> callback;
+};
diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui
new file mode 100644
index 000000000..c8cb6bcf3
--- /dev/null
+++ b/src/yuzu/applets/controller.ui
@@ -0,0 +1,2653 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtControllerSelectorDialog</class>
+ <widget class="QDialog" name="QtControllerSelectorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>839</width>
+ <height>630</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Controller Applet</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="controllersSupportedLabel">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Supported Controller Types:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported1" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported3" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported4" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="playersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>16</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="maxSupportedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Players:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="numberSupportedLabel">
+ <property name="font">
+ <font>
+ <pointsize>14</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>1 - 8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="middleControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item row="1" column="7">
+ <widget class="QWidget" name="widgetPlayer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer4Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer4" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer4">
+ <property name="text">
+ <string>P4</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player4LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player4Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_39">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer4Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QWidget" name="widgetPlayer2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer2Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer2" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer2">
+ <property name="text">
+ <string>P2</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player2LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player2Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_37">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer2Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QWidget" name="widgetPlayer1" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_30">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer1Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer1" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer1">
+ <property name="text">
+ <string>P1</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player1LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player1Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_36">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer1Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QWidget" name="widgetSpacer2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_31">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QWidget" name="widgetSpacer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_33">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QWidget" name="widgetSpacer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_32">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QWidget" name="widgetPlayer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer3Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer3" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer3">
+ <property name="text">
+ <string>P3</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player3LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player3Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_38">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer3Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="widgetSpacer5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_34">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="5">
+ <widget class="QWidget" name="widgetPlayer7" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_25">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer7Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer7" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer7">
+ <property name="text">
+ <string>P7</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player7LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player7Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_42">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer7Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="7">
+ <widget class="QWidget" name="widgetPlayer8" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer8Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer8" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer8">
+ <property name="text">
+ <string>P8</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player8LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player8Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_35">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer8Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QWidget" name="widgetPlayer5" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer5Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer5" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer5">
+ <property name="text">
+ <string>P5</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player5LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player5Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_40">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer5Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QWidget" name="widgetPlayer6" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer6Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer6" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer6">
+ <property name="text">
+ <string>P6</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player6LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player6Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_41">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer6Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="QWidget" name="widgetSpacer" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QWidget" name="widgetSpacer6" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QWidget" name="widgetSpacer7" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="widgetSpacer9" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_17">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="rightMargin">
+ <number>15</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>8</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Undocked</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="vibrationButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="inputConfigGroup">
+ <property name="title">
+ <string>Profiles</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="inputConfigButton">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Create</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="connectedControllers" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelControllers">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="labelConnectedPlayer1">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="labelConnectedPlayer2">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="labelConnectedPlayer4">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="labelConnectedPlayer3">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelConnected">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="labelConnectedPlayer5">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="labelConnectedPlayer7">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="labelConnectedPlayer6">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="labelConnectedPlayer8">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtControllerSelectorDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 6aff38735..c9a2f8601 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -17,6 +17,7 @@
#include "yuzu/applets/profile_select.h"
#include "yuzu/main.h"
+namespace {
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
return QtProfileSelectionDialog::tr(
"%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
@@ -25,7 +26,7 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
}
QString GetImagePath(Common::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
@@ -41,6 +42,7 @@ QPixmap GetIcon(Common::UUID uuid) {
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
+} // Anonymous namespace
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
@@ -112,6 +114,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
+int QtProfileSelectionDialog::exec() {
+ // Skip profile selection when there's only one.
+ if (profile_manager->GetUserCount() == 1) {
+ user_index = 0;
+ return QDialog::Accepted;
+ }
+ return QDialog::exec();
+}
+
void QtProfileSelectionDialog::accept() {
QDialog::accept();
}
diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h
index cee886a77..29c33cca0 100644
--- a/src/yuzu/applets/profile_select.h
+++ b/src/yuzu/applets/profile_select.h
@@ -27,6 +27,7 @@ public:
explicit QtProfileSelectionDialog(QWidget* parent);
~QtProfileSelectionDialog() override;
+ int exec() override;
void accept() override;
void reject() override;
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 1cac2f942..d62b0efc2 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -8,13 +8,17 @@
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMessageBox>
-#include <QOffscreenSurface>
-#include <QOpenGLContext>
#include <QPainter>
#include <QScreen>
+#include <QString>
#include <QStringList>
#include <QWindow>
+#ifdef HAS_OPENGL
+#include <QOffscreenSurface>
+#include <QOpenGLContext>
+#endif
+
#if !defined(WIN32) && HAS_VULKAN
#include <qpa/qplatformnativeinterface.h>
#endif
@@ -27,6 +31,7 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
+#include "core/hle/kernel/process.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
@@ -41,49 +46,66 @@ EmuThread::EmuThread() = default;
EmuThread::~EmuThread() = default;
void EmuThread::run() {
- MicroProfileOnThreadCreate("EmuThread");
+ std::string name = "yuzu:EmuControlThread";
+ MicroProfileOnThreadCreate(name.c_str());
+ Common::SetCurrentThreadName(name.c_str());
+
+ auto& system = Core::System::GetInstance();
+
+ system.RegisterHostThread();
+
+ auto& gpu = system.GPU();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
- Core::System::GetInstance().GPU().Start();
+ gpu.Start();
+
+ gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
- Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
- stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
+ system.Renderer().Rasterizer().LoadDiskResources(
+ system.CurrentProcess()->GetTitleID(), stop_run,
+ [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+ gpu.ReleaseContext();
+
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
bool was_active = false;
while (!stop_run) {
if (running) {
- if (!was_active)
+ if (was_active) {
emit DebugModeLeft();
+ }
- Core::System::ResultStatus result = Core::System::GetInstance().RunLoop();
+ running_guard = true;
+ Core::System::ResultStatus result = system.Run();
if (result != Core::System::ResultStatus::Success) {
+ running_guard = false;
this->SetRunning(false);
- emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
+ emit ErrorThrown(result, system.GetStatusDetails());
}
+ running_wait.Wait();
+ result = system.Pause();
+ if (result != Core::System::ResultStatus::Success) {
+ running_guard = false;
+ this->SetRunning(false);
+ emit ErrorThrown(result, system.GetStatusDetails());
+ }
+ running_guard = false;
- was_active = running || exec_step;
- if (!was_active && !stop_run)
+ if (!stop_run) {
+ was_active = true;
emit DebugModeEntered();
+ }
} else if (exec_step) {
- if (!was_active)
- emit DebugModeLeft();
-
- exec_step = false;
- Core::System::GetInstance().SingleStep();
- emit DebugModeEntered();
- yieldCurrentThread();
-
- was_active = false;
+ UNIMPLEMENTED();
} else {
std::unique_lock lock{running_mutex};
running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
@@ -91,13 +113,14 @@ void EmuThread::run() {
}
// Shutdown the core emulation
- Core::System::GetInstance().Shutdown();
+ system.Shutdown();
#if MICROPROFILE_ENABLED
MicroProfileOnThreadExit();
#endif
}
+#ifdef HAS_OPENGL
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
@@ -106,6 +129,9 @@ public:
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
+ if (Settings::values.renderer_debug) {
+ format.setOption(QSurfaceFormat::FormatOption::DebugContext);
+ }
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
@@ -122,7 +148,7 @@ public:
// disable vsync for any shared contexts
auto format = share_context->format();
- format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0);
+ format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
@@ -150,18 +176,19 @@ public:
}
void MakeCurrent() override {
- if (is_current) {
- return;
+ // We can't track the current state of the underlying context in this wrapper class because
+ // Qt may make the underlying context not current for one reason or another. In particular,
+ // the WebBrowser uses GL, so it seems to conflict if we aren't careful.
+ // Instead of always just making the context current (which does not have any caching to
+ // check if the underlying context is already current) we can check for the current context
+ // in the thread local data by calling `currentContext()` and checking if its ours.
+ if (QOpenGLContext::currentContext() != context.get()) {
+ context->makeCurrent(surface);
}
- is_current = context->makeCurrent(surface);
}
void DoneCurrent() override {
- if (!is_current) {
- return;
- }
context->doneCurrent();
- is_current = false;
}
QOpenGLContext* GetShareContext() {
@@ -178,8 +205,8 @@ private:
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
- bool is_current = false;
};
+#endif
class DummyContext : public Core::Frontend::GraphicsContext {};
@@ -192,15 +219,6 @@ public:
virtual ~RenderWidget() = default;
- /// Called on the UI thread when this Widget is ready to draw
- /// Dervied classes can override this to draw the latest frame.
- virtual void Present() {}
-
- void paintEvent(QPaintEvent* event) override {
- Present();
- update();
- }
-
QPaintEngine* paintEngine() const override {
return nullptr;
}
@@ -219,20 +237,8 @@ public:
context = std::move(context_);
}
- void Present() override {
- if (!isVisible()) {
- return;
- }
-
- context->MakeCurrent();
- if (Core::System::GetInstance().Renderer().TryPresent(100)) {
- context->SwapBuffers();
- glFinish();
- }
- }
-
private:
- std::unique_ptr<Core::Frontend::GraphicsContext> context{};
+ std::unique_ptr<Core::Frontend::GraphicsContext> context;
};
#ifdef HAS_VULKAN
@@ -280,8 +286,9 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow*
return wsi;
}
-GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
- : QWidget(parent_), emu_thread(emu_thread_) {
+GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
+ : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),
@@ -290,13 +297,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
auto layout = new QHBoxLayout(this);
layout->setMargin(0);
setLayout(layout);
- InputCommon::Init();
+ input_subsystem->Initialize();
- connect(this, &GRenderWindow::FirstFrameDisplayed, parent_, &GMainWindow::OnLoadComplete);
+ this->setMouseTracking(true);
+
+ connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
}
void GRenderWindow::PollEvents() {
@@ -350,7 +359,7 @@ QByteArray GRenderWindow::saveGeometry() {
}
qreal GRenderWindow::windowPixelRatio() const {
- return devicePixelRatio();
+ return devicePixelRatioF();
}
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF& pos) const {
@@ -365,15 +374,20 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
}
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->PressKey(event->key());
+ input_subsystem->GetKeyboard()->PressKey(event->key());
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->ReleaseKey(event->key());
+ input_subsystem->GetKeyboard()->ReleaseKey(event->key());
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
- // touch input is handled in TouchBeginEvent
+ if (!Settings::values.touchscreen.enabled) {
+ input_subsystem->GetKeyboard()->PressKey(event->button());
+ return;
+ }
+
+ // Touch input is handled in TouchBeginEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
@@ -383,12 +397,13 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
const auto [x, y] = ScaleTouch(pos);
this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
+ QWidget::mousePressEvent(event);
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
- // touch input is handled in TouchUpdateEvent
+ // Touch input is handled in TouchUpdateEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
@@ -396,11 +411,17 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
this->TouchMoved(x, y);
- InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+ input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y());
+ QWidget::mouseMoveEvent(event);
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
- // touch input is handled in TouchEndEvent
+ if (!Settings::values.touchscreen.enabled) {
+ input_subsystem->GetKeyboard()->ReleaseKey(event->button());
+ return;
+ }
+
+ // Touch input is handled in TouchEndEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
@@ -408,7 +429,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
this->TouchReleased();
} else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->EndTilt();
+ input_subsystem->GetMotionEmu()->EndTilt();
}
}
@@ -423,7 +444,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
int active_points = 0;
// average all active touch points
- for (const auto tp : event->touchPoints()) {
+ for (const auto& tp : event->touchPoints()) {
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
active_points++;
pos += tp.pos();
@@ -457,7 +478,7 @@ bool GRenderWindow::event(QEvent* event) {
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
- InputCommon::GetKeyboard()->ReleaseAllKeys();
+ input_subsystem->GetKeyboard()->ReleaseAllKeys();
}
void GRenderWindow::resizeEvent(QResizeEvent* event) {
@@ -466,13 +487,15 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
}
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
- if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
+#ifdef HAS_OPENGL
+ if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
// Bind the shared contexts to the main surface in case the backend wants to take over
// presentation
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
child_widget->windowHandle());
}
+#endif
return std::make_unique<DummyContext>();
}
@@ -481,7 +504,7 @@ bool GRenderWindow::InitRenderTarget() {
first_frame = false;
- switch (Settings::values.renderer_backend) {
+ switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
if (!InitializeOpenGL()) {
return false;
@@ -508,7 +531,7 @@ bool GRenderWindow::InitRenderTarget() {
OnFramebufferSizeChanged();
BackupGeometry();
- if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
+ if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
if (!LoadOpenGL()) {
return false;
}
@@ -537,7 +560,7 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
screenshot_image.bits(),
- [=] {
+ [=, this] {
const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
@@ -553,6 +576,7 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
}
bool GRenderWindow::InitializeOpenGL() {
+#ifdef HAS_OPENGL
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this);
@@ -564,6 +588,11 @@ bool GRenderWindow::InitializeOpenGL() {
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
return true;
+#else
+ QMessageBox::warning(this, tr("OpenGL not available!"),
+ tr("yuzu has not been compiled with OpenGL support."));
+ return false;
+#endif
}
bool GRenderWindow::InitializeVulkan() {
@@ -585,19 +614,33 @@ bool GRenderWindow::LoadOpenGL() {
auto context = CreateSharedContext();
auto scope = context->Acquire();
if (!gladLoadGL()) {
- QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
- tr("Your GPU may not support OpenGL 4.3, or you do not have the "
- "latest graphics driver."));
+ QMessageBox::warning(
+ this, tr("Error while initializing OpenGL!"),
+ tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
+ return false;
+ }
+
+ const QString renderer =
+ QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
+
+ if (!GLAD_GL_VERSION_4_3) {
+ LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
+ QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
+ tr("Your GPU may not support OpenGL 4.3, or you do not have the "
+ "latest graphics driver.<br><br>GL Renderer:<br>%1")
+ .arg(renderer));
return false;
}
QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
if (!unsupported_gl_extensions.empty()) {
- QMessageBox::critical(
+ QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
- "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
- unsupported_gl_extensions.join(QStringLiteral("<br>")));
+ "have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
+ "extensions:<br>%2")
+ .arg(renderer)
+ .arg(unsupported_gl_extensions.join(QStringLiteral("<br>"))));
return false;
}
return true;
@@ -627,8 +670,13 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
if (!GLAD_GL_ARB_depth_buffer_float)
unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
- for (const QString& ext : unsupported_ext)
- LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+ if (!unsupported_ext.empty()) {
+ LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
+ glGetString(GL_RENDERER));
+ }
+ for (const QString& ext : unsupported_ext) {
+ LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+ }
return unsupported_ext;
}
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 3626604ca..ca35cf831 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -6,6 +6,7 @@
#include <atomic>
#include <condition_variable>
+#include <memory>
#include <mutex>
#include <QImage>
@@ -23,6 +24,10 @@ class QKeyEvent;
class QTouchEvent;
class QStringList;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace VideoCore {
enum class LoadCallbackStage;
}
@@ -59,6 +64,12 @@ public:
this->running = running;
lock.unlock();
running_cv.notify_all();
+ if (!running) {
+ running_wait.Set();
+ /// Wait until effectively paused
+ while (running_guard)
+ ;
+ }
}
/**
@@ -84,6 +95,8 @@ private:
std::atomic_bool stop_run{false};
std::mutex running_mutex;
std::condition_variable running_cv;
+ Common::Event running_wait{};
+ std::atomic_bool running_guard{false};
signals:
/**
@@ -113,7 +126,8 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
- GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
+ explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
~GRenderWindow() override;
// EmuWindow implementation.
@@ -175,6 +189,7 @@ private:
QStringList GetUnsupportedGLExtensions() const;
EmuThread* emu_thread;
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 5477f050c..649912557 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -54,7 +54,8 @@ void CompatDB::Submit() {
back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
Core::System::GetInstance().TelemetrySession().AddField(
- Telemetry::FieldType::UserFeedback, "Compatibility", compatibility->checkedId());
+ Common::Telemetry::FieldType::UserFeedback, "Compatibility",
+ compatibility->checkedId());
button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting"));
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 3b9ab38dd..3c423a271 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -5,52 +5,59 @@
#include <array>
#include <QKeySequence>
#include <QSettings>
+#include "common/common_paths.h"
#include "common/file_util.h"
-#include "configure_input_simple.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "yuzu/configuration/config.h"
-#include "yuzu/uisettings.h"
-Config::Config() {
- // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
- FileUtil::CreateFullPath(qt_config_loc);
- qt_config =
- std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
- Reload();
+namespace FS = Common::FS;
+
+Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
+ global = config_type == ConfigType::GlobalConfig;
+
+ Initialize(config_name);
}
Config::~Config() {
- Save();
+ if (global) {
+ Save();
+ }
}
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
- Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
- Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
- Qt::Key_H, Qt::Key_G, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right, Qt::Key_Down, Qt::Key_J,
- Qt::Key_I, Qt::Key_L, Qt::Key_K, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
+ Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
+ Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
+ Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
+};
+
+const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
+ Qt::Key_7,
+ Qt::Key_8,
};
-const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
{
Qt::Key_Up,
Qt::Key_Down,
Qt::Key_Left,
Qt::Key_Right,
- Qt::Key_E,
},
{
Qt::Key_I,
Qt::Key_K,
Qt::Key_J,
Qt::Key_L,
- Qt::Key_R,
},
}};
+const std::array<int, 2> Config::default_stick_mod = {
+ Qt::Key_E,
+ Qt::Key_R,
+};
+
const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons =
{
Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal,
@@ -212,95 +219,174 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
-const std::array<UISettings::Shortcut, 15> default_hotkeys{{
- {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
+const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
+ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
- {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
{QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
+ {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
- {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
- {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
+ {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
- {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
}};
// clang-format on
-void Config::ReadPlayerValues() {
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- auto& player = Settings::values.players[p];
+void Config::Initialize(const std::string& config_name) {
+ switch (type) {
+ case ConfigType::GlobalConfig:
+ qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir),
+ config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ Reload();
+ break;
+ case ConfigType::PerGameConfig:
+ qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ Reload();
+ break;
+ case ConfigType::InputProfile:
+ qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ break;
+ }
+}
+
+void Config::ReadPlayerValue(std::size_t player_index) {
+ const QString player_prefix = [this, player_index] {
+ if (type == ConfigType::InputProfile) {
+ return QString{};
+ } else {
+ return QStringLiteral("player_%1_").arg(player_index);
+ }
+ }();
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ if (player_prefix.isEmpty()) {
+ const auto controller = static_cast<Settings::ControllerType>(
+ qt_config
+ ->value(QStringLiteral("%1type").arg(player_prefix),
+ static_cast<u8>(Settings::ControllerType::ProController))
+ .toUInt());
+ if (controller == Settings::ControllerType::LeftJoycon ||
+ controller == Settings::ControllerType::RightJoycon) {
+ player.controller_type = controller;
+ }
+ } else {
player.connected =
- ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
+ ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
+ .toBool();
- player.type = static_cast<Settings::ControllerType>(
+ player.controller_type = static_cast<Settings::ControllerType>(
qt_config
- ->value(QStringLiteral("player_%1_type").arg(p),
- static_cast<u8>(Settings::ControllerType::DualJoycon))
+ ->value(QStringLiteral("%1type").arg(player_prefix),
+ static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
+ player.vibration_enabled =
+ qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
+ .toBool();
+
+ player.vibration_strength =
+ qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
+ .toInt();
+
player.body_color_left = qt_config
- ->value(QStringLiteral("player_%1_body_color_left").arg(p),
+ ->value(QStringLiteral("%1body_color_left").arg(player_prefix),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
- player.body_color_right = qt_config
- ->value(QStringLiteral("player_%1_body_color_right").arg(p),
- Settings::JOYCON_BODY_NEON_RED)
- .toUInt();
- player.button_color_left = qt_config
- ->value(QStringLiteral("player_%1_button_color_left").arg(p),
- Settings::JOYCON_BUTTONS_NEON_BLUE)
- .toUInt();
+ player.body_color_right =
+ qt_config
+ ->value(QStringLiteral("%1body_color_right").arg(player_prefix),
+ Settings::JOYCON_BODY_NEON_RED)
+ .toUInt();
+ player.button_color_left =
+ qt_config
+ ->value(QStringLiteral("%1button_color_left").arg(player_prefix),
+ Settings::JOYCON_BUTTONS_NEON_BLUE)
+ .toUInt();
player.button_color_right =
qt_config
- ->value(QStringLiteral("player_%1_button_color_right").arg(p),
+ ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
+ }
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param =
- InputCommon::GenerateKeyboardParam(default_buttons[i]);
- auto& player_buttons = player.buttons[i];
-
- player_buttons = qt_config
- ->value(QStringLiteral("player_%1_").arg(p) +
- QString::fromUtf8(Settings::NativeButton::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_buttons.empty()) {
- player_buttons = default_param;
- }
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& player_buttons = player.buttons[i];
+
+ player_buttons = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_buttons.empty()) {
+ player_buttons = default_param;
}
+ }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
- auto& player_analogs = player.analogs[i];
-
- player_analogs = qt_config
- ->value(QStringLiteral("player_%1_").arg(p) +
- QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_analogs.empty()) {
- player_analogs = default_param;
- }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& player_analogs = player.analogs[i];
+
+ player_analogs = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_analogs.empty()) {
+ player_analogs = default_param;
}
}
- std::stable_partition(
- Settings::values.players.begin(),
- Settings::values.players.begin() +
- Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
- [](const auto& player) { return player.connected; });
+ for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+ auto& player_vibrations = player.vibrations[i];
+
+ player_vibrations =
+ qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeVibration::mapping[i]),
+ QString{})
+ .toString()
+ .toStdString();
+ }
+
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ auto& player_motions = player.motions[i];
+
+ player_motions = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeMotion::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_motions.empty()) {
+ player_motions = default_param;
+ }
+ }
}
void Config::ReadDebugValues() {
@@ -325,7 +411,7 @@ void Config::ReadDebugValues() {
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
debug_pad_analogs = qt_config
@@ -392,26 +478,22 @@ void Config::ReadTouchscreenValues() {
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
}
-void Config::ApplyDefaultProfileIfInputInvalid() {
- if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(),
- [](const Settings::PlayerInput& in) { return in.connected; })) {
- ApplyInputProfileConfiguration(UISettings::values.profile_index);
- }
-}
-
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
- .toString()
- .toStdString();
- Settings::values.enable_audio_stretching =
- ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
- Settings::values.audio_device_id =
- ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
- .toString()
- .toStdString();
- Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
+ if (global) {
+ Settings::values.sink_id =
+ ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
+ .toString()
+ .toStdString();
+ Settings::values.audio_device_id =
+ ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
+ .toString()
+ .toStdString();
+ }
+ ReadSettingGlobal(Settings::values.enable_audio_stretching,
+ QStringLiteral("enable_audio_stretching"), true);
+ ReadSettingGlobal(Settings::values.volume, QStringLiteral("volume"), 1);
qt_config->endGroup();
}
@@ -419,17 +501,73 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
- ReadPlayerValues();
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadPlayerValue(p);
+ }
ReadDebugValues();
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
+ ReadMotionTouchValues();
+
+ ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false);
+ ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"),
+ true);
+ ReadSettingGlobal(Settings::values.enable_accurate_vibrations,
+ QStringLiteral("enable_accurate_vibrations"), false);
+ ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true);
+
+ qt_config->endGroup();
+}
+
+void Config::ReadMotionTouchValues() {
+ int num_touch_from_button_maps =
+ qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
+
+ if (num_touch_from_button_maps > 0) {
+ const auto append_touch_from_button_map = [this] {
+ Settings::TouchFromButtonMap map;
+ map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
+ .toString()
+ .toStdString();
+ const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
+ map.buttons.reserve(num_touch_maps);
+ for (int i = 0; i < num_touch_maps; i++) {
+ qt_config->setArrayIndex(i);
+ std::string touch_mapping =
+ ReadSetting(QStringLiteral("bind")).toString().toStdString();
+ map.buttons.emplace_back(std::move(touch_mapping));
+ }
+ qt_config->endArray(); // entries
+ Settings::values.touch_from_button_maps.emplace_back(std::move(map));
+ };
+
+ for (int i = 0; i < num_touch_from_button_maps; ++i) {
+ qt_config->setArrayIndex(i);
+ append_touch_from_button_map();
+ }
+ } else {
+ Settings::values.touch_from_button_maps.emplace_back(
+ Settings::TouchFromButtonMap{"default", {}});
+ num_touch_from_button_maps = 1;
+ }
+ qt_config->endArray();
Settings::values.motion_device =
ReadSetting(QStringLiteral("motion_device"),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
.toString()
.toStdString();
+ Settings::values.touch_device =
+ ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
+ .toString()
+ .toStdString();
+ Settings::values.use_touch_from_button =
+ ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
+ Settings::values.touch_from_button_map_index =
+ ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
+ Settings::values.touch_from_button_map_index =
+ std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
Settings::values.udp_input_address =
ReadSetting(QStringLiteral("udp_input_address"),
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -440,14 +578,12 @@ void Config::ReadControlValues() {
.toInt());
Settings::values.udp_pad_index =
static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
-
- qt_config->endGroup();
}
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool();
+ ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), true);
qt_config->endGroup();
}
@@ -456,63 +592,42 @@ void Config::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
- FileUtil::GetUserPath(
- FileUtil::UserPath::NANDDir,
- qt_config
- ->value(QStringLiteral("nand_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::SDMCDir,
- qt_config
- ->value(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::LoadDir,
- qt_config
- ->value(QStringLiteral("load_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::DumpDir,
- qt_config
- ->value(QStringLiteral("dump_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::CacheDir,
- qt_config
- ->value(QStringLiteral("cache_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
- .toString()
- .toStdString());
+ FS::GetUserPath(FS::UserPath::NANDDir,
+ qt_config
+ ->value(QStringLiteral("nand_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::SDMCDir,
+ qt_config
+ ->value(QStringLiteral("sdmc_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::LoadDir,
+ qt_config
+ ->value(QStringLiteral("load_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::DumpDir,
+ qt_config
+ ->value(QStringLiteral("dump_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::CacheDir,
+ qt_config
+ ->value(QStringLiteral("cache_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)))
+ .toString()
+ .toStdString());
Settings::values.gamecard_inserted =
ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
Settings::values.gamecard_current_game =
ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();
Settings::values.gamecard_path =
- ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString();
- Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
- ReadSetting(QStringLiteral("nand_total_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)))
- .toULongLong());
- Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
- ReadSetting(QStringLiteral("nand_user_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)))
- .toULongLong());
- Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
- ReadSetting(QStringLiteral("nand_system_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)))
- .toULongLong());
- Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
- ReadSetting(QStringLiteral("sdmc_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)))
- .toULongLong());
+ ReadSetting(QStringLiteral("gamecard_path"), QString{}).toString().toStdString();
qt_config->endGroup();
}
@@ -526,12 +641,16 @@ void Config::ReadDebuggingValues() {
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
Settings::values.program_args =
- ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString();
+ ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();
Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
Settings::values.reporting_services =
ReadSetting(QStringLiteral("reporting_services"), false).toBool();
Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool();
+ Settings::values.disable_macro_jit =
+ ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool();
+ Settings::values.extended_logging =
+ ReadSetting(QStringLiteral("extended_logging"), false).toBool();
qt_config->endGroup();
}
@@ -557,8 +676,7 @@ void Config::ReadDisabledAddOnValues() {
const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));
for (int j = 0; j < d_size; ++j) {
qt_config->setArrayIndex(j);
- out.push_back(
- ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString());
+ out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString());
}
qt_config->endArray();
Settings::values.disabled_addons.insert_or_assign(title_id, out);
@@ -584,7 +702,6 @@ void Config::ReadPathValues() {
UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
- UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
UISettings::values.game_dir_deprecated =
ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
UISettings::values.game_dir_deprecated_deepscan =
@@ -617,6 +734,40 @@ void Config::ReadPathValues() {
}
}
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
+ UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
+
+ qt_config->endGroup();
+}
+
+void Config::ReadCpuValues() {
+ qt_config->beginGroup(QStringLiteral("Cpu"));
+
+ if (global) {
+ Settings::values.cpu_accuracy = static_cast<Settings::CPUAccuracy>(
+ ReadSetting(QStringLiteral("cpu_accuracy"), 0).toInt());
+
+ Settings::values.cpuopt_page_tables =
+ ReadSetting(QStringLiteral("cpuopt_page_tables"), true).toBool();
+ Settings::values.cpuopt_block_linking =
+ ReadSetting(QStringLiteral("cpuopt_block_linking"), true).toBool();
+ Settings::values.cpuopt_return_stack_buffer =
+ ReadSetting(QStringLiteral("cpuopt_return_stack_buffer"), true).toBool();
+ Settings::values.cpuopt_fast_dispatcher =
+ ReadSetting(QStringLiteral("cpuopt_fast_dispatcher"), true).toBool();
+ Settings::values.cpuopt_context_elimination =
+ ReadSetting(QStringLiteral("cpuopt_context_elimination"), true).toBool();
+ Settings::values.cpuopt_const_prop =
+ ReadSetting(QStringLiteral("cpuopt_const_prop"), true).toBool();
+ Settings::values.cpuopt_misc_ir =
+ ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool();
+ Settings::values.cpuopt_reduce_misalign_checks =
+ ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool();
+
+ Settings::values.cpuopt_unsafe_unfuse_fma =
+ ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool();
+ Settings::values.cpuopt_unsafe_reduce_fp_error =
+ ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool();
+ }
qt_config->endGroup();
}
@@ -624,30 +775,46 @@ void Config::ReadPathValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- Settings::values.renderer_backend =
- static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt());
- Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool();
- Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt();
- Settings::values.resolution_factor =
- ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
- Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt();
- Settings::values.max_anisotropy = ReadSetting(QStringLiteral("max_anisotropy"), 0).toInt();
- Settings::values.use_frame_limit =
- ReadSetting(QStringLiteral("use_frame_limit"), true).toBool();
- Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
- Settings::values.use_disk_shader_cache =
- ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
- Settings::values.use_accurate_gpu_emulation =
- ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool();
- Settings::values.use_asynchronous_gpu_emulation =
- ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool();
- Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool();
- Settings::values.force_30fps_mode =
- ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool();
-
- Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
- Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
- Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
+ ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0);
+ ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false);
+ ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0);
+ ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0);
+ ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0);
+ ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true);
+ ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100);
+ ReadSettingGlobal(Settings::values.use_disk_shader_cache,
+ QStringLiteral("use_disk_shader_cache"), true);
+ ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0);
+ ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation,
+ QStringLiteral("use_asynchronous_gpu_emulation"), true);
+ ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"),
+ true);
+ ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
+ ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
+ true);
+ ReadSettingGlobal(Settings::values.use_asynchronous_shaders,
+ QStringLiteral("use_asynchronous_shaders"), false);
+ ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"),
+ true);
+ ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0);
+ ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0);
+ ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0);
+
+ qt_config->endGroup();
+}
+
+void Config::ReadScreenshotValues() {
+ qt_config->beginGroup(QStringLiteral("Screenshots"));
+
+ UISettings::values.enable_screenshot_save_as =
+ ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
+ FS::GetUserPath(
+ FS::UserPath::ScreenshotsDir,
+ qt_config
+ ->value(QStringLiteral("screenshot_path"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)))
+ .toString()
+ .toStdString());
qt_config->endGroup();
}
@@ -659,11 +826,13 @@ void Config::ReadShortcutValues() {
const auto& [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
+ // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
+ // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
+ // a file dialog in windowed mode
UISettings::values.shortcuts.push_back(
{name,
group,
- {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
- ReadSetting(QStringLiteral("Context"), context).toInt()}});
+ {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
qt_config->endGroup();
qt_config->endGroup();
}
@@ -674,33 +843,45 @@ void Config::ReadShortcutValues() {
void Config::ReadSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- Settings::values.use_docked_mode =
- ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
+ ReadSettingGlobal(Settings::values.current_user, QStringLiteral("current_user"), 0);
+ Settings::values.current_user =
+ std::clamp<int>(Settings::values.current_user, 0, Service::Account::MAX_USERS - 1);
- Settings::values.current_user = std::clamp<int>(
- ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1);
+ ReadSettingGlobal(Settings::values.language_index, QStringLiteral("language_index"), 1);
- Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt();
+ ReadSettingGlobal(Settings::values.region_index, QStringLiteral("region_index"), 1);
- Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt();
+ ReadSettingGlobal(Settings::values.time_zone_index, QStringLiteral("time_zone_index"), 0);
- const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool();
- if (rng_seed_enabled) {
- Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong();
- } else {
- Settings::values.rng_seed = std::nullopt;
+ bool rng_seed_enabled;
+ ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false);
+ bool rng_seed_global =
+ global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool();
+ Settings::values.rng_seed.SetGlobal(rng_seed_global);
+ if (global || !rng_seed_global) {
+ if (rng_seed_enabled) {
+ Settings::values.rng_seed.SetValue(
+ ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong());
+ } else {
+ Settings::values.rng_seed.SetValue(std::nullopt);
+ }
}
- const auto custom_rtc_enabled =
- ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc =
- std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong());
- } else {
- Settings::values.custom_rtc = std::nullopt;
+ bool custom_rtc_enabled;
+ ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false);
+ bool custom_rtc_global =
+ global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool();
+ Settings::values.custom_rtc.SetGlobal(custom_rtc_global);
+ if (global || !custom_rtc_global) {
+ if (custom_rtc_enabled) {
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()));
+ } else {
+ Settings::values.custom_rtc.SetValue(std::nullopt);
+ }
}
- Settings::values.sound_index = ReadSetting(QStringLiteral("sound_index"), 1).toInt();
+ ReadSettingGlobal(Settings::values.sound_index, QStringLiteral("sound_index"), 1);
qt_config->endGroup();
}
@@ -713,14 +894,13 @@ void Config::ReadUIValues() {
.toString();
UISettings::values.enable_discord_presence =
ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
- UISettings::values.screenshot_resolution_factor =
- static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
UISettings::values.select_user_on_boot =
ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool();
ReadUIGamelistValues();
ReadUILayoutValues();
ReadPathValues();
+ ReadScreenshotValues();
ReadShortcutValues();
UISettings::values.single_window_mode =
@@ -737,11 +917,10 @@ void Config::ReadUIValues() {
UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
- UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();
UISettings::values.pause_when_in_background =
ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
-
- ApplyDefaultProfileIfInputInvalid();
+ UISettings::values.hide_mouse =
+ ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();
qt_config->endGroup();
}
@@ -794,54 +973,81 @@ void Config::ReadWebServiceValues() {
}
void Config::ReadValues() {
- ReadControlValues();
+ if (global) {
+ ReadControlValues();
+ ReadDataStorageValues();
+ ReadDebuggingValues();
+ ReadDisabledAddOnValues();
+ ReadServiceValues();
+ ReadUIValues();
+ ReadWebServiceValues();
+ ReadMiscellaneousValues();
+ }
ReadCoreValues();
+ ReadCpuValues();
ReadRendererValues();
ReadAudioValues();
- ReadDataStorageValues();
ReadSystemValues();
- ReadMiscellaneousValues();
- ReadDebuggingValues();
- ReadWebServiceValues();
- ReadServiceValues();
- ReadDisabledAddOnValues();
- ReadUIValues();
}
-void Config::SavePlayerValues() {
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- const auto& player = Settings::values.players[p];
+void Config::SavePlayerValue(std::size_t player_index) {
+ const QString player_prefix = [this, player_index] {
+ if (type == ConfigType::InputProfile) {
+ return QString{};
+ } else {
+ return QStringLiteral("player_%1_").arg(player_index);
+ }
+ }();
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
- WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
- WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type),
- static_cast<u8>(Settings::ControllerType::DualJoycon));
+ WriteSetting(QStringLiteral("%1type").arg(player_prefix),
+ static_cast<u8>(player.controller_type),
+ static_cast<u8>(Settings::ControllerType::ProController));
- WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
+ if (!player_prefix.isEmpty()) {
+ WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false);
+ WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
+ player.vibration_enabled, true);
+ WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
+ player.vibration_strength, 100);
+ WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
Settings::JOYCON_BODY_NEON_BLUE);
- WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
- Settings::JOYCON_BODY_NEON_RED);
- WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
- Settings::JOYCON_BUTTONS_NEON_BLUE);
- WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
+ WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
+ player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
+ WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
+ player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
+ WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
+ }
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param =
- InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QStringLiteral("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(player.buttons[i]),
- QString::fromStdString(default_param));
- }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
- WriteSetting(QStringLiteral("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(player.analogs[i]),
- QString::fromStdString(default_param));
- }
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(player.buttons[i]),
+ QString::fromStdString(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(player.analogs[i]),
+ QString::fromStdString(default_param));
+ }
+ for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeVibration::mapping[i]),
+ QString::fromStdString(player.vibrations[i]), QString{});
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeMotion::mapping[i]),
+ QString::fromStdString(player.motions[i]),
+ QString::fromStdString(default_param));
}
}
@@ -857,7 +1063,7 @@ void Config::SaveDebugValues() {
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(QStringLiteral("debug_pad_") +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.debug_pad_analogs[i]),
@@ -891,31 +1097,74 @@ void Config::SaveTouchscreenValues() {
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
+void Config::SaveMotionTouchValues() {
+ WriteSetting(QStringLiteral("motion_device"),
+ QString::fromStdString(Settings::values.motion_device),
+ QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
+ WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
+ false);
+ WriteSetting(QStringLiteral("touch_from_button_map"),
+ Settings::values.touch_from_button_map_index, 0);
+ WriteSetting(QStringLiteral("udp_input_address"),
+ QString::fromStdString(Settings::values.udp_input_address),
+ QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
+ WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
+ InputCommon::CemuhookUDP::DEFAULT_PORT);
+ WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
+
+ qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
+ for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
+ qt_config->setArrayIndex(static_cast<int>(p));
+ WriteSetting(QStringLiteral("name"),
+ QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
+ QStringLiteral("default"));
+ qt_config->beginWriteArray(QStringLiteral("entries"));
+ for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
+ ++q) {
+ qt_config->setArrayIndex(static_cast<int>(q));
+ WriteSetting(
+ QStringLiteral("bind"),
+ QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
+ }
+ qt_config->endArray();
+ }
+ qt_config->endArray();
+}
+
void Config::SaveValues() {
- SaveControlValues();
+ if (global) {
+ SaveControlValues();
+ SaveDataStorageValues();
+ SaveDebuggingValues();
+ SaveDisabledAddOnValues();
+ SaveServiceValues();
+ SaveUIValues();
+ SaveWebServiceValues();
+ SaveMiscellaneousValues();
+ }
SaveCoreValues();
+ SaveCpuValues();
SaveRendererValues();
SaveAudioValues();
- SaveDataStorageValues();
SaveSystemValues();
- SaveMiscellaneousValues();
- SaveDebuggingValues();
- SaveWebServiceValues();
- SaveServiceValues();
- SaveDisabledAddOnValues();
- SaveUIValues();
}
void Config::SaveAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
- QStringLiteral("auto"));
- WriteSetting(QStringLiteral("enable_audio_stretching"),
- Settings::values.enable_audio_stretching, true);
- WriteSetting(QStringLiteral("output_device"),
- QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
- WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
+ if (global) {
+ WriteSetting(QStringLiteral("output_engine"),
+ QString::fromStdString(Settings::values.sink_id), QStringLiteral("auto"));
+ WriteSetting(QStringLiteral("output_device"),
+ QString::fromStdString(Settings::values.audio_device_id),
+ QStringLiteral("auto"));
+ }
+ WriteSettingGlobal(QStringLiteral("enable_audio_stretching"),
+ Settings::values.enable_audio_stretching, true);
+ WriteSettingGlobal(QStringLiteral("volume"), Settings::values.volume, 1.0f);
qt_config->endGroup();
}
@@ -923,21 +1172,27 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
- SavePlayerValues();
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SavePlayerValue(p);
+ }
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
-
+ SaveMotionTouchValues();
+
+ WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
+ WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled,
+ true);
+ WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"),
+ Settings::values.enable_accurate_vibrations, false);
+ WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
WriteSetting(QStringLiteral("motion_device"),
QString::fromStdString(Settings::values.motion_device),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
- WriteSetting(QStringLiteral("udp_input_address"),
- QString::fromStdString(Settings::values.udp_input_address),
- QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
- WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
- InputCommon::CemuhookUDP::DEFAULT_PORT);
- WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
qt_config->endGroup();
}
@@ -945,7 +1200,7 @@ void Config::SaveControlValues() {
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
+ WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, true);
qt_config->endGroup();
}
@@ -955,37 +1210,26 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
WriteSetting(QStringLiteral("nand_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)));
WriteSetting(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)));
WriteSetting(QStringLiteral("load_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)));
WriteSetting(QStringLiteral("dump_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)));
WriteSetting(QStringLiteral("cache_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)));
WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
false);
WriteSetting(QStringLiteral("gamecard_path"),
- QString::fromStdString(Settings::values.gamecard_path), QStringLiteral(""));
- WriteSetting(QStringLiteral("nand_total_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)));
- WriteSetting(QStringLiteral("nand_user_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)));
- WriteSetting(QStringLiteral("nand_system_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)));
- WriteSetting(QStringLiteral("sdmc_size"),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)),
- QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)));
+ QString::fromStdString(Settings::values.gamecard_path), QString{});
+
qt_config->endGroup();
}
@@ -997,10 +1241,11 @@ void Config::SaveDebuggingValues() {
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteSetting(QStringLiteral("program_args"),
- QString::fromStdString(Settings::values.program_args), QStringLiteral(""));
+ QString::fromStdString(Settings::values.program_args), QString{});
WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false);
+ WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false);
qt_config->endGroup();
}
@@ -1023,8 +1268,7 @@ void Config::SaveDisabledAddOnValues() {
qt_config->beginWriteArray(QStringLiteral("disabled"));
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(static_cast<int>(j));
- WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]),
- QStringLiteral(""));
+ WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{});
}
qt_config->endArray();
++i;
@@ -1048,7 +1292,6 @@ void Config::SavePathValues() {
WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
- WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
qt_config->beginWriteArray(QStringLiteral("gamedirs"));
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
qt_config->setArrayIndex(i);
@@ -1059,6 +1302,38 @@ void Config::SavePathValues() {
}
qt_config->endArray();
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
+ WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
+
+ qt_config->endGroup();
+}
+
+void Config::SaveCpuValues() {
+ qt_config->beginGroup(QStringLiteral("Cpu"));
+
+ if (global) {
+ WriteSetting(QStringLiteral("cpu_accuracy"),
+ static_cast<int>(Settings::values.cpu_accuracy), 0);
+
+ WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables,
+ true);
+ WriteSetting(QStringLiteral("cpuopt_block_linking"), Settings::values.cpuopt_block_linking,
+ true);
+ WriteSetting(QStringLiteral("cpuopt_return_stack_buffer"),
+ Settings::values.cpuopt_return_stack_buffer, true);
+ WriteSetting(QStringLiteral("cpuopt_fast_dispatcher"),
+ Settings::values.cpuopt_fast_dispatcher, true);
+ WriteSetting(QStringLiteral("cpuopt_context_elimination"),
+ Settings::values.cpuopt_context_elimination, true);
+ WriteSetting(QStringLiteral("cpuopt_const_prop"), Settings::values.cpuopt_const_prop, true);
+ WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true);
+ WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"),
+ Settings::values.cpuopt_reduce_misalign_checks, true);
+
+ WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"),
+ Settings::values.cpuopt_unsafe_unfuse_fma, true);
+ WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
+ Settings::values.cpuopt_unsafe_reduce_fp_error, true);
+ }
qt_config->endGroup();
}
@@ -1066,28 +1341,46 @@ void Config::SavePathValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0);
+ WriteSettingGlobal(QStringLiteral("backend"),
+ static_cast<int>(Settings::values.renderer_backend.GetValue(global)),
+ Settings::values.renderer_backend.UsingGlobal(), 0);
WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false);
- WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
- WriteSetting(QStringLiteral("resolution_factor"),
- static_cast<double>(Settings::values.resolution_factor), 1.0);
- WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0);
- WriteSetting(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0);
- WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
- WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
- WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
- true);
- WriteSetting(QStringLiteral("use_accurate_gpu_emulation"),
- Settings::values.use_accurate_gpu_emulation, false);
- WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"),
- Settings::values.use_asynchronous_gpu_emulation, false);
- WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
- WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false);
-
+ WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
+ WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0);
+ WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0);
+ WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
+ WriteSettingGlobal(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
+ WriteSettingGlobal(QStringLiteral("use_disk_shader_cache"),
+ Settings::values.use_disk_shader_cache, true);
+ WriteSettingGlobal(QStringLiteral("gpu_accuracy"),
+ static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)),
+ Settings::values.gpu_accuracy.UsingGlobal(), 0);
+ WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"),
+ Settings::values.use_asynchronous_gpu_emulation, true);
+ WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation,
+ true);
+ WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
+ WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
+ Settings::values.use_assembly_shaders, true);
+ WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"),
+ Settings::values.use_asynchronous_shaders, false);
+ WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time,
+ true);
// Cast to double because Qt's written float values are not human-readable
- WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
- WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
- WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
+ WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0);
+ WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0);
+ WriteSettingGlobal(QStringLiteral("bg_blue"), Settings::values.bg_blue, 0.0);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveScreenshotValues() {
+ qt_config->beginGroup(QStringLiteral("Screenshots"));
+
+ WriteSetting(QStringLiteral("enable_screenshot_save_as"),
+ UISettings::values.enable_screenshot_save_as);
+ WriteSetting(QStringLiteral("screenshot_path"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)));
qt_config->endGroup();
}
@@ -1115,22 +1408,28 @@ void Config::SaveShortcutValues() {
void Config::SaveSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0);
- WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1);
- WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1);
-
- WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false);
- WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0);
-
- WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
- false);
- WriteSetting(QStringLiteral("custom_rtc"),
- QVariant::fromValue<long long>(
- Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
- 0);
-
- WriteSetting(QStringLiteral("sound_index"), Settings::values.sound_index, 1);
+ WriteSettingGlobal(QStringLiteral("language_index"), Settings::values.language_index, 1);
+ WriteSettingGlobal(QStringLiteral("region_index"), Settings::values.region_index, 1);
+ WriteSettingGlobal(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0);
+
+ WriteSettingGlobal(QStringLiteral("rng_seed_enabled"),
+ Settings::values.rng_seed.GetValue(global).has_value(),
+ Settings::values.rng_seed.UsingGlobal(), false);
+ WriteSettingGlobal(QStringLiteral("rng_seed"),
+ Settings::values.rng_seed.GetValue(global).value_or(0),
+ Settings::values.rng_seed.UsingGlobal(), 0);
+
+ WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"),
+ Settings::values.custom_rtc.GetValue(global).has_value(),
+ Settings::values.custom_rtc.UsingGlobal(), false);
+ WriteSettingGlobal(
+ QStringLiteral("custom_rtc"),
+ QVariant::fromValue<long long>(
+ Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()),
+ Settings::values.custom_rtc.UsingGlobal(), 0);
+
+ WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1);
qt_config->endGroup();
}
@@ -1142,14 +1441,13 @@ void Config::SaveUIValues() {
QString::fromUtf8(UISettings::themes[0].second));
WriteSetting(QStringLiteral("enable_discord_presence"),
UISettings::values.enable_discord_presence, true);
- WriteSetting(QStringLiteral("screenshot_resolution_factor"),
- UISettings::values.screenshot_resolution_factor, 0);
WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot,
false);
SaveUIGamelistValues();
SaveUILayoutValues();
SavePathValues();
+ SaveScreenshotValues();
SaveShortcutValues();
WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
@@ -1161,9 +1459,9 @@ void Config::SaveUIValues() {
WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
- WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);
WriteSetting(QStringLiteral("pauseWhenInBackground"),
UISettings::values.pause_when_in_background, false);
+ WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);
qt_config->endGroup();
}
@@ -1223,6 +1521,34 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
+template <typename Type>
+void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name) {
+ const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+ setting.SetGlobal(use_global);
+ if (global || !use_global) {
+ setting.SetValue(ReadSetting(name).value<Type>());
+ }
+}
+
+template <typename Type>
+void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
+ const QVariant& default_value) {
+ const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+ setting.SetGlobal(use_global);
+ if (global || !use_global) {
+ setting.SetValue(ReadSetting(name, default_value).value<Type>());
+ }
+}
+
+template <typename Type>
+void Config::ReadSettingGlobal(Type& setting, const QString& name,
+ const QVariant& default_value) const {
+ const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+ if (global || !use_global) {
+ setting = ReadSetting(name, default_value).value<Type>();
+ }
+}
+
void Config::WriteSetting(const QString& name, const QVariant& value) {
qt_config->setValue(name, value);
}
@@ -1233,13 +1559,65 @@ void Config::WriteSetting(const QString& name, const QVariant& value,
qt_config->setValue(name, value);
}
+template <typename Type>
+void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting) {
+ if (!global) {
+ qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
+ }
+ if (global || !setting.UsingGlobal()) {
+ qt_config->setValue(name, setting.GetValue(global));
+ }
+}
+
+template <typename Type>
+void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
+ const QVariant& default_value) {
+ if (!global) {
+ qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
+ }
+ if (global || !setting.UsingGlobal()) {
+ qt_config->setValue(name + QStringLiteral("/default"),
+ setting.GetValue(global) == default_value.value<Type>());
+ qt_config->setValue(name, setting.GetValue(global));
+ }
+}
+
+void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
+ const QVariant& default_value) {
+ if (!global) {
+ qt_config->setValue(name + QStringLiteral("/use_global"), use_global);
+ }
+ if (global || !use_global) {
+ qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
+ qt_config->setValue(name, value);
+ }
+}
+
void Config::Reload() {
ReadValues();
+ Settings::Sanitize();
// To apply default value changes
SaveValues();
Settings::Apply();
}
void Config::Save() {
+ Settings::Sanitize();
SaveValues();
}
+
+void Config::ReadControlPlayerValue(std::size_t player_index) {
+ qt_config->beginGroup(QStringLiteral("Controls"));
+ ReadPlayerValue(player_index);
+ qt_config->endGroup();
+}
+
+void Config::SaveControlPlayerValue(std::size_t player_index) {
+ qt_config->beginGroup(QStringLiteral("Controls"));
+ SavePlayerValue(player_index);
+ qt_config->endGroup();
+}
+
+const std::string& Config::GetConfigFilePath() const {
+ return qt_config_loc;
+}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ba6888004..8a600e19d 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -7,34 +7,53 @@
#include <array>
#include <memory>
#include <string>
+#include <QMetaType>
#include <QVariant>
#include "core/settings.h"
+#include "yuzu/uisettings.h"
class QSettings;
class Config {
public:
- Config();
+ enum class ConfigType {
+ GlobalConfig,
+ PerGameConfig,
+ InputProfile,
+ };
+
+ explicit Config(const std::string& config_name = "qt-config",
+ ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
void Save();
+ void ReadControlPlayerValue(std::size_t player_index);
+ void SaveControlPlayerValue(std::size_t player_index);
+
+ const std::string& GetConfigFilePath() const;
+
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
- static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+ static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
+ static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+ static const std::array<int, 2> default_stick_mod;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
+ static const std::array<UISettings::Shortcut, 16> default_hotkeys;
private:
+ void Initialize(const std::string& config_name);
+
void ReadValues();
- void ReadPlayerValues();
+ void ReadPlayerValue(std::size_t player_index);
void ReadDebugValues();
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
- void ApplyDefaultProfileIfInputInvalid();
+ void ReadMotionTouchValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@@ -46,7 +65,9 @@ private:
void ReadDisabledAddOnValues();
void ReadMiscellaneousValues();
void ReadPathValues();
+ void ReadCpuValues();
void ReadRendererValues();
+ void ReadScreenshotValues();
void ReadShortcutValues();
void ReadSystemValues();
void ReadUIValues();
@@ -55,10 +76,11 @@ private:
void ReadWebServiceValues();
void SaveValues();
- void SavePlayerValues();
+ void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
+ void SaveMotionTouchValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
@@ -70,7 +92,9 @@ private:
void SaveDisabledAddOnValues();
void SaveMiscellaneousValues();
void SavePathValues();
+ void SaveCpuValues();
void SaveRendererValues();
+ void SaveScreenshotValues();
void SaveShortcutValues();
void SaveSystemValues();
void SaveUIValues();
@@ -80,9 +104,33 @@ private:
QVariant ReadSetting(const QString& name) const;
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
+ // Templated ReadSettingGlobal functions will also look for the use_global setting and set
+ // both the value and the global state properly
+ template <typename Type>
+ void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name);
+ template <typename Type>
+ void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
+ const QVariant& default_value);
+ template <typename Type>
+ void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
+ // Templated WriteSettingGlobal functions will also write the global state if needed and will
+ // skip writing the actual setting if it defers to the global value
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
+ template <typename Type>
+ void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting);
+ template <typename Type>
+ void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
+ const QVariant& default_value);
+ void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
+ const QVariant& default_value);
+ ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
+ bool global;
};
+
+// These metatype declarations cannot be in core/settings.h because core is devoid of QT
+Q_DECLARE_METATYPE(Settings::RendererBackend);
+Q_DECLARE_METATYPE(Settings::GPUAccuracy);
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
new file mode 100644
index 000000000..18482795c
--- /dev/null
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -0,0 +1,134 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QObject>
+#include <QString>
+#include "core/settings.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/configure_per_game.h"
+
+void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting,
+ const QCheckBox* checkbox,
+ const CheckState& tracker) {
+ if (tracker == CheckState::Global) {
+ setting->SetGlobal(true);
+ } else {
+ setting->SetGlobal(false);
+ setting->SetValue(checkbox->checkState());
+ }
+}
+
+void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting,
+ const QComboBox* combobox) {
+ if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ setting->SetGlobal(true);
+ } else {
+ setting->SetGlobal(false);
+ setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET);
+ }
+}
+
+void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
+ const QComboBox* combobox) {
+ if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ setting->SetGlobal(true);
+ } else {
+ setting->SetGlobal(false);
+ setting->SetValue(static_cast<Settings::RendererBackend>(
+ combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET));
+ }
+}
+
+void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
+ const Settings::Setting<bool>* setting) {
+ if (setting->UsingGlobal()) {
+ checkbox->setCheckState(Qt::PartiallyChecked);
+ } else {
+ checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
+ }
+}
+
+void ConfigurationShared::SetPerGameSetting(QComboBox* combobox,
+ const Settings::Setting<int>* setting) {
+ combobox->setCurrentIndex(setting->UsingGlobal()
+ ? ConfigurationShared::USE_GLOBAL_INDEX
+ : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET);
+}
+
+void ConfigurationShared::SetPerGameSetting(
+ QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) {
+ combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
+ : static_cast<int>(setting->GetValue()) +
+ ConfigurationShared::USE_GLOBAL_OFFSET);
+}
+
+void ConfigurationShared::SetPerGameSetting(
+ QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) {
+ combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
+ : static_cast<int>(setting->GetValue()) +
+ ConfigurationShared::USE_GLOBAL_OFFSET);
+}
+
+void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
+ if (highlighted) {
+ widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
+ .arg(widget->objectName()));
+ } else {
+ widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
+ .arg(widget->objectName()));
+ }
+ widget->show();
+}
+
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
+ const Settings::Setting<bool>& setting,
+ CheckState& tracker) {
+ if (setting.UsingGlobal()) {
+ tracker = CheckState::Global;
+ } else {
+ tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
+ tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+ static_cast<int>(CheckState::Count));
+ if (tracker == CheckState::Global) {
+ checkbox->setChecked(setting.GetValue(true));
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ });
+}
+
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
+ bool global_state, CheckState& tracker) {
+ if (global) {
+ tracker = CheckState::Global;
+ } else {
+ tracker = (state == global_state) ? CheckState::On : CheckState::Off;
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
+ tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+ static_cast<int>(CheckState::Count));
+ if (tracker == CheckState::Global) {
+ checkbox->setChecked(global_state);
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ });
+}
+
+void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
+ InsertGlobalItem(combobox, global);
+ QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
+ [target](int index) { SetHighlight(target, index != 0); });
+}
+
+void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
+ const QString use_global_text =
+ ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
+ combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
+ combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
+}
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
new file mode 100644
index 000000000..312b9e549
--- /dev/null
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -0,0 +1,51 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QString>
+#include "core/settings.h"
+
+namespace ConfigurationShared {
+
+constexpr int USE_GLOBAL_INDEX = 0;
+constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
+constexpr int USE_GLOBAL_OFFSET = 2;
+
+enum class CheckState {
+ Off,
+ On,
+ Global,
+ Count,
+};
+
+// Global-aware apply and set functions
+
+void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox,
+ const CheckState& tracker);
+void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox);
+void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
+ const QComboBox* combobox);
+void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting,
+ const QComboBox* combobox);
+
+void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting);
+void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting);
+void SetPerGameSetting(QComboBox* combobox,
+ const Settings::Setting<Settings::RendererBackend>* setting);
+void SetPerGameSetting(QComboBox* combobox,
+ const Settings::Setting<Settings::GPUAccuracy>* setting);
+
+void SetHighlight(QWidget* widget, bool highlighted);
+void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting,
+ CheckState& tracker);
+void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
+ CheckState& tracker);
+void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
+
+void InsertGlobalItem(QComboBox* combobox, int global_index);
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 9aec1bd09..f92c3aff3 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>382</width>
+ <width>650</width>
<height>650</height>
</rect>
</property>
@@ -26,13 +26,13 @@
<widget class="QListWidget" name="selectorList">
<property name="minimumSize">
<size>
- <width>150</width>
+ <width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>150</width>
+ <width>120</width>
<height>16777215</height>
</size>
</property>
@@ -44,66 +44,121 @@
<number>0</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
+ <property name="accessibleName">
+ <string>General</string>
+ </property>
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureUi" name="uiTab">
+ <property name="accessibleName">
+ <string>UI</string>
+ </property>
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
+ <property name="accessibleName">
+ <string>System</string>
+ </property>
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureProfileManager" name="profileManagerTab">
+ <property name="accessibleName">
+ <string>Profiles</string>
+ </property>
<attribute name="title">
<string>Profiles</string>
</attribute>
</widget>
<widget class="ConfigureFilesystem" name="filesystemTab">
+ <property name="accessibleName">
+ <string>Filesystem</string>
+ </property>
<attribute name="title">
<string>Filesystem</string>
</attribute>
</widget>
- <widget class="ConfigureInputSimple" name="inputTab">
+ <widget class="ConfigureInput" name="inputTab">
+ <property name="accessibleName">
+ <string>Controls</string>
+ </property>
<attribute name="title">
- <string>Input</string>
+ <string>Controls</string>
</attribute>
</widget>
<widget class="ConfigureHotkeys" name="hotkeysTab">
+ <property name="accessibleName">
+ <string>Hotkeys</string>
+ </property>
<attribute name="title">
<string>Hotkeys</string>
</attribute>
</widget>
+ <widget class="ConfigureCpu" name="cpuTab">
+ <property name="accessibleName">
+ <string>CPU</string>
+ </property>
+ <attribute name="title">
+ <string>CPU</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureCpuDebug" name="cpuDebugTab">
+ <property name="accessibleName">
+ <string>Debug</string>
+ </property>
+ <attribute name="title">
+ <string>Debug</string>
+ </attribute>
+ </widget>
<widget class="ConfigureGraphics" name="graphicsTab">
+ <property name="accessibleName">
+ <string>Graphics</string>
+ </property>
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
+ <property name="accessibleName">
+ <string>Advanced</string>
+ </property>
<attribute name="title">
<string>GraphicsAdvanced</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
+ <property name="accessibleName">
+ <string>Audio</string>
+ </property>
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
+ <property name="accessibleName">
+ <string>Debug</string>
+ </property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
+ <property name="accessibleName">
+ <string>Web</string>
+ </property>
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
<widget class="ConfigureService" name="serviceTab">
+ <property name="accessibleName">
+ <string>Services</string>
+ </property>
<attribute name="title">
<string>Services</string>
</attribute>
@@ -159,6 +214,18 @@
<container>1</container>
</customwidget>
<customwidget>
+ <class>ConfigureCpu</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_cpu.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureCpuDebug</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_cpu_debug.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
<header>configuration/configure_graphics.h</header>
@@ -183,9 +250,9 @@
<container>1</container>
</customwidget>
<customwidget>
- <class>ConfigureInputSimple</class>
+ <class>ConfigureInput</class>
<extends>QWidget</extends>
- <header>configuration/configure_input_simple.h</header>
+ <header>configuration/configure_input.h</header>
<container>1</container>
</customwidget>
<customwidget>
@@ -208,32 +275,12 @@
<signal>accepted()</signal>
<receiver>ConfigureDialog</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>220</x>
- <y>380</y>
- </hint>
- <hint type="destinationlabel">
- <x>220</x>
- <y>200</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDialog</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>220</x>
- <y>380</y>
- </hint>
- <hint type="destinationlabel">
- <x>220</x>
- <y>200</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index f370c690f..db9518798 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -11,6 +11,7 @@
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_audio.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
ConfigureAudio::ConfigureAudio(QWidget* parent)
@@ -24,6 +25,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioDevices);
+ ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
+ ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
+
+ SetupPerGameUI();
+
SetConfiguration();
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
@@ -41,8 +47,21 @@ void ConfigureAudio::SetConfiguration() {
SetAudioDeviceFromDeviceID();
- ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
- ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
+ ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum());
+
+ ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
+
+ if (!Settings::IsConfiguringGlobal()) {
+ if (Settings::values.volume.UsingGlobal()) {
+ ui->volume_combo_box->setCurrentIndex(0);
+ ui->volume_slider->setEnabled(false);
+ } else {
+ ui->volume_combo_box->setCurrentIndex(1);
+ ui->volume_slider->setEnabled(true);
+ }
+ ConfigurationShared::SetHighlight(ui->volume_layout,
+ !Settings::values.volume.UsingGlobal());
+ }
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
@@ -80,15 +99,37 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
- Settings::values.sink_id =
- ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
- .toStdString();
- Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
- Settings::values.audio_device_id =
- ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
- .toStdString();
- Settings::values.volume =
- static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
+ if (Settings::IsConfiguringGlobal()) {
+ Settings::values.sink_id =
+ ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
+ .toStdString();
+ Settings::values.audio_device_id =
+ ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
+ .toStdString();
+
+ // Guard if during game and set to game-specific value
+ if (Settings::values.enable_audio_stretching.UsingGlobal()) {
+ Settings::values.enable_audio_stretching.SetValue(
+ ui->toggle_audio_stretching->isChecked());
+ }
+ if (Settings::values.volume.UsingGlobal()) {
+ Settings::values.volume.SetValue(
+ static_cast<float>(ui->volume_slider->sliderPosition()) /
+ ui->volume_slider->maximum());
+ }
+ } else {
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
+ ui->toggle_audio_stretching,
+ enable_audio_stretching);
+ if (ui->volume_combo_box->currentIndex() == 0) {
+ Settings::values.volume.SetGlobal(true);
+ } else {
+ Settings::values.volume.SetGlobal(false);
+ Settings::values.volume.SetValue(
+ static_cast<float>(ui->volume_slider->sliderPosition()) /
+ ui->volume_slider->maximum());
+ }
+ }
}
void ConfigureAudio::changeEvent(QEvent* event) {
@@ -122,3 +163,26 @@ void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
+
+void ConfigureAudio::SetupPerGameUI() {
+ if (Settings::IsConfiguringGlobal()) {
+ ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
+ ui->toggle_audio_stretching->setEnabled(
+ Settings::values.enable_audio_stretching.UsingGlobal());
+
+ return;
+ }
+
+ ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
+ Settings::values.enable_audio_stretching,
+ enable_audio_stretching);
+ connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+ ui->volume_slider->setEnabled(index == 1);
+ ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
+ });
+
+ ui->output_sink_combo_box->setVisible(false);
+ ui->output_sink_label->setVisible(false);
+ ui->audio_device_combo_box->setVisible(false);
+ ui->audio_device_label->setVisible(false);
+}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index ea83bd72d..9dbd3d93e 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -7,6 +7,10 @@
#include <memory>
#include <QWidget>
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
namespace Ui {
class ConfigureAudio;
}
@@ -34,5 +38,9 @@ private:
void SetAudioDeviceFromDeviceID();
void SetVolumeIndicatorText(int percentage);
+ void SetupPerGameUI();
+
std::unique_ptr<Ui::ConfigureAudio> ui;
+
+ ConfigurationShared::CheckState enable_audio_stretching;
};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index a098b9acc..9bd0cca96 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>188</width>
- <height>246</height>
+ <width>367</width>
+ <height>368</height>
</rect>
</property>
<layout class="QVBoxLayout">
@@ -18,9 +18,9 @@
</property>
<layout class="QVBoxLayout">
<item>
- <layout class="QHBoxLayout">
+ <layout class="QHBoxLayout" name="_3">
<item>
- <widget class="QLabel" name="label_1">
+ <widget class="QLabel" name="output_sink_label">
<property name="text">
<string>Output Engine:</string>
</property>
@@ -31,20 +31,20 @@
</item>
</layout>
</item>
- <item>
- <widget class="QCheckBox" name="toggle_audio_stretching">
- <property name="toolTip">
- <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
- </property>
- <property name="text">
- <string>Enable audio stretching</string>
- </property>
- </widget>
- </item>
<item>
- <layout class="QHBoxLayout">
+ <widget class="QCheckBox" name="toggle_audio_stretching">
+ <property name="toolTip">
+ <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
+ </property>
+ <property name="text">
+ <string>Enable audio stretching</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="_2">
<item>
- <widget class="QLabel" name="label_2">
+ <widget class="QLabel" name="audio_device_label">
<property name="text">
<string>Audio Device:</string>
</property>
@@ -56,66 +56,91 @@
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <property name="topMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Volume:</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QSlider" name="volume_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>100</number>
- </property>
- <property name="pageStep">
- <number>10</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="volume_indicator">
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>0 %</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="volume_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QComboBox" name="volume_combo_box">
+ <item>
+ <property name="text">
+ <string>Use global volume</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Set volume:</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="volume_label">
+ <property name="text">
+ <string>Volume:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QSlider" name="volume_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="pageStep">
+ <number>10</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="volume_indicator">
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>0 %</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
new file mode 100644
index 000000000..37fcd6adc
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -0,0 +1,76 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QComboBox>
+#include <QMessageBox>
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/settings.h"
+#include "ui_configure_cpu.h"
+#include "yuzu/configuration/configure_cpu.h"
+
+ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) {
+ ui->setupUi(this);
+
+ SetConfiguration();
+
+ connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this,
+ &ConfigureCpu::AccuracyUpdated);
+ connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureCpu::UpdateGroup);
+}
+
+ConfigureCpu::~ConfigureCpu() = default;
+
+void ConfigureCpu::SetConfiguration() {
+ const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+
+ ui->accuracy->setEnabled(runtime_lock);
+ ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy));
+ UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy));
+
+ ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma);
+ ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error);
+}
+
+void ConfigureCpu::AccuracyUpdated(int index) {
+ if (static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) {
+ const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"),
+ tr("CPU Debug Mode is only intended for developer "
+ "use. Are you sure you want to enable this?"),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::No) {
+ ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate));
+ UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate));
+ }
+ }
+}
+
+void ConfigureCpu::UpdateGroup(int index) {
+ ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) ==
+ Settings::CPUAccuracy::Unsafe);
+}
+
+void ConfigureCpu::ApplyConfiguration() {
+ Settings::values.cpu_accuracy =
+ static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex());
+ Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked();
+ Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked();
+}
+
+void ConfigureCpu::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureCpu::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
new file mode 100644
index 000000000..3c5683d81
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+#include "core/settings.h"
+
+namespace Ui {
+class ConfigureCpu;
+}
+
+class ConfigureCpu : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureCpu(QWidget* parent = nullptr);
+ ~ConfigureCpu() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void AccuracyUpdated(int index);
+ void UpdateGroup(int index);
+
+ void SetConfiguration();
+
+ std::unique_ptr<Ui::ConfigureCpu> ui;
+};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
new file mode 100644
index 000000000..ebdd2e6e9
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureCpu</class>
+ <widget class="QWidget" name="ConfigureCpu">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>321</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox">
+ <property name="title">
+ <string>General</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="text">
+ <string>Accuracy:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="accuracy">
+ <item>
+ <property name="text">
+ <string>Accurate</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Unsafe</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Enable Debug Mode</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel">
+ <property name="wordWrap">
+ <bool>1</bool>
+ </property>
+ <property name="text">
+ <string>We recommend setting accuracy to "Accurate".</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="unsafe_group">
+ <property name="title">
+ <string>Unsafe CPU Optimization Settings</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="wordWrap">
+ <bool>1</bool>
+ </property>
+ <property name="text">
+ <string>These settings reduce accuracy for speed.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
+ <property name="text">
+ <string>Unfuse FMA (improve performance on CPUs without FMA)</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
+ <property name="text">
+ <string>Faster FRSQRTE and FRECPE</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_disable_info">
+ <property name="text">
+ <string>CPU settings are available only when game is not running.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_cpu_debug.cpp b/src/yuzu/configuration/configure_cpu_debug.cpp
new file mode 100644
index 000000000..3385b2cf6
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu_debug.cpp
@@ -0,0 +1,65 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QComboBox>
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/settings.h"
+#include "ui_configure_cpu_debug.h"
+#include "yuzu/configuration/configure_cpu_debug.h"
+
+ConfigureCpuDebug::ConfigureCpuDebug(QWidget* parent)
+ : QWidget(parent), ui(new Ui::ConfigureCpuDebug) {
+ ui->setupUi(this);
+
+ SetConfiguration();
+}
+
+ConfigureCpuDebug::~ConfigureCpuDebug() = default;
+
+void ConfigureCpuDebug::SetConfiguration() {
+ const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+
+ ui->cpuopt_page_tables->setEnabled(runtime_lock);
+ ui->cpuopt_page_tables->setChecked(Settings::values.cpuopt_page_tables);
+ ui->cpuopt_block_linking->setEnabled(runtime_lock);
+ ui->cpuopt_block_linking->setChecked(Settings::values.cpuopt_block_linking);
+ ui->cpuopt_return_stack_buffer->setEnabled(runtime_lock);
+ ui->cpuopt_return_stack_buffer->setChecked(Settings::values.cpuopt_return_stack_buffer);
+ ui->cpuopt_fast_dispatcher->setEnabled(runtime_lock);
+ ui->cpuopt_fast_dispatcher->setChecked(Settings::values.cpuopt_fast_dispatcher);
+ ui->cpuopt_context_elimination->setEnabled(runtime_lock);
+ ui->cpuopt_context_elimination->setChecked(Settings::values.cpuopt_context_elimination);
+ ui->cpuopt_const_prop->setEnabled(runtime_lock);
+ ui->cpuopt_const_prop->setChecked(Settings::values.cpuopt_const_prop);
+ ui->cpuopt_misc_ir->setEnabled(runtime_lock);
+ ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir);
+ ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock);
+ ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks);
+}
+
+void ConfigureCpuDebug::ApplyConfiguration() {
+ Settings::values.cpuopt_page_tables = ui->cpuopt_page_tables->isChecked();
+ Settings::values.cpuopt_block_linking = ui->cpuopt_block_linking->isChecked();
+ Settings::values.cpuopt_return_stack_buffer = ui->cpuopt_return_stack_buffer->isChecked();
+ Settings::values.cpuopt_fast_dispatcher = ui->cpuopt_fast_dispatcher->isChecked();
+ Settings::values.cpuopt_context_elimination = ui->cpuopt_context_elimination->isChecked();
+ Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked();
+ Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked();
+ Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked();
+}
+
+void ConfigureCpuDebug::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureCpuDebug::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_cpu_debug.h b/src/yuzu/configuration/configure_cpu_debug.h
new file mode 100644
index 000000000..c9941ef3b
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu_debug.h
@@ -0,0 +1,31 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+#include "core/settings.h"
+
+namespace Ui {
+class ConfigureCpuDebug;
+}
+
+class ConfigureCpuDebug : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureCpuDebug(QWidget* parent = nullptr);
+ ~ConfigureCpuDebug() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void SetConfiguration();
+
+ std::unique_ptr<Ui::ConfigureCpuDebug> ui;
+};
diff --git a/src/yuzu/configuration/configure_cpu_debug.ui b/src/yuzu/configuration/configure_cpu_debug.ui
new file mode 100644
index 000000000..a90dc64fe
--- /dev/null
+++ b/src/yuzu/configuration/configure_cpu_debug.ui
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureCpuDebug</class>
+ <widget class="QWidget" name="ConfigureCpuDebug">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>321</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox">
+ <property name="title">
+ <string>Toggle CPU Optimizations</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="wordWrap">
+ <bool>1</bool>
+ </property>
+ <property name="text">
+ <string>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is "Debug Mode".
+ &lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_page_tables">
+ <property name="text">
+ <string>Enable inline page tables</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div style="white-space: nowrap"&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style="white-space: nowrap"&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style="white-space: nowrap"&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_block_linking">
+ <property name="text">
+ <string>Enable block linking</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_return_stack_buffer">
+ <property name="text">
+ <string>Enable return stack buffer</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_fast_dispatcher">
+ <property name="text">
+ <string>Enable fast dispatcher</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_context_elimination">
+ <property name="text">
+ <string>Enable context elimination</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_const_prop">
+ <property name="text">
+ <string>Enable constant propagation</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_misc_ir">
+ <property name="text">
+ <string>Enable miscellaneous optimizations</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_reduce_misalign_checks">
+ <property name="text">
+ <string>Enable misalignment check reduction</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div style="white-space: nowrap"&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style="white-space: nowrap"&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_disable_info">
+ <property name="text">
+ <string>CPU settings are available only when game is not running.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 9631059c7..027099ab7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -19,7 +19,8 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
SetConfiguration();
connect(ui->open_log_button, &QPushButton::clicked, []() {
- QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
+ const auto path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
}
@@ -38,6 +39,9 @@ void ConfigureDebug::SetConfiguration() {
ui->quest_flag->setChecked(Settings::values.quest_flag);
ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
+ ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit);
+ ui->extended_logging->setChecked(Settings::values.extended_logging);
}
void ConfigureDebug::ApplyConfiguration() {
@@ -49,6 +53,8 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
+ Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
+ Settings::values.extended_logging = ui->extended_logging->isChecked();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index e028c4c80..6f94fe304 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -90,7 +90,7 @@
<item>
<widget class="QCheckBox" name="toggle_console">
<property name="text">
- <string>Show Log Console (Windows Only)</string>
+ <string>Show Log in Console</string>
</property>
</widget>
</item>
@@ -103,6 +103,34 @@
</item>
</layout>
</item>
+ <item>
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
+ </property>
+ <property name="text">
+ <string>Enable Extended Logging</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>This will be reset automatically when yuzu closes.</string>
+ </property>
+ <property name="indent">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -115,7 +143,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="label_4">
<property name="text">
<string>Arguments String</string>
</property>
@@ -140,14 +168,27 @@
<property name="enabled">
<bool>true</bool>
</property>
- <property name="whatsThis">
- <string>When checked, the graphics API enters in a slower debugging mode</string>
+ <property name="toolTip">
+ <string>When checked, the graphics API enters a slower debugging mode</string>
</property>
<property name="text">
<string>Enable Graphics Debugging</string>
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="disable_macro_jit">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
+ </property>
+ <property name="text">
+ <string>Disable Macro JIT</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -156,27 +197,7 @@
<property name="title">
<string>Dump</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <item>
- <widget class="QCheckBox" name="dump_decompressed_nso">
- <property name="whatsThis">
- <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump Decompressed NSOs</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="dump_exefs">
- <property name="whatsThis">
- <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump ExeFS</string>
- </property>
- </widget>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="reporting_services">
<property name="text">
@@ -185,7 +206,7 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_5">
<property name="font">
<font>
<italic>true</italic>
@@ -207,7 +228,7 @@
<property name="title">
<string>Advanced</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QCheckBox" name="quest_flag">
<property name="text">
@@ -244,8 +265,6 @@
<tabstop>open_log_button</tabstop>
<tabstop>homebrew_args_edit</tabstop>
<tabstop>enable_graphics_debugging</tabstop>
- <tabstop>dump_decompressed_nso</tabstop>
- <tabstop>dump_exefs</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>quest_flag</tabstop>
</tabstops>
diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp
new file mode 100644
index 000000000..a878ef9c6
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.cpp
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_debug_controller.h"
+#include "yuzu/configuration/configure_debug_controller.h"
+#include "yuzu/configuration/configure_input_player.h"
+
+ConfigureDebugController::ConfigureDebugController(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
+ debug_controller(
+ new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) {
+ ui->setupUi(this);
+
+ ui->controllerLayout->addWidget(debug_controller);
+
+ connect(ui->clear_all_button, &QPushButton::clicked, this,
+ [this] { debug_controller->ClearAll(); });
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ [this] { debug_controller->RestoreDefaults(); });
+
+ RetranslateUI();
+}
+
+ConfigureDebugController::~ConfigureDebugController() = default;
+
+void ConfigureDebugController::ApplyConfiguration() {
+ debug_controller->ApplyConfiguration();
+}
+
+void ConfigureDebugController::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureDebugController::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h
new file mode 100644
index 000000000..b4f53fad5
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.h
@@ -0,0 +1,41 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QPushButton;
+
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureDebugController;
+}
+
+class ConfigureDebugController : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles);
+ ~ConfigureDebugController() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureDebugController> ui;
+
+ ConfigureInputPlayer* debug_controller;
+};
diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui
new file mode 100644
index 000000000..7b7e6582c
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.ui
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureDebugController</class>
+ <widget class="QDialog" name="ConfigureDebugController">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>780</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Debug Controller</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="controllerLayout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="clear_all_button">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restore_defaults_button">
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureDebugController</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureDebugController</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index df4473b46..5041e0bf8 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -12,15 +12,21 @@
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
-ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
+ InputCommon::InputSubsystem* input_subsystem)
: QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
+ Settings::SetConfiguringGlobal(true);
+
ui->setupUi(this);
ui->hotkeysTab->Populate(registry);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ ui->inputTab->Initialize(input_subsystem);
+
SetConfiguration();
PopulateSelectionList();
+ connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
@@ -40,6 +46,8 @@ void ConfigureDialog::ApplyConfiguration() {
ui->filesystemTab->applyConfiguration();
ui->inputTab->ApplyConfiguration();
ui->hotkeysTab->ApplyConfiguration(registry);
+ ui->cpuTab->ApplyConfiguration();
+ ui->cpuDebugTab->ApplyConfiguration();
ui->graphicsTab->ApplyConfiguration();
ui->graphicsAdvancedTab->ApplyConfiguration();
ui->audioTab->ApplyConfiguration();
@@ -74,12 +82,13 @@ void ConfigureDialog::RetranslateUI() {
Q_DECLARE_METATYPE(QList<QWidget*>);
void ConfigureDialog::PopulateSelectionList() {
- const std::array<std::pair<QString, QList<QWidget*>>, 5> items{
- {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}},
+ const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
+ {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
+ {tr("CPU"), {ui->cpuTab, ui->cpuDebugTab}},
{tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
{tr("Audio"), {ui->audioTab}},
- {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
+ {tr("Controls"), ui->inputTab->GetSubTabs()}},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -93,6 +102,14 @@ void ConfigureDialog::PopulateSelectionList() {
}
}
+void ConfigureDialog::OnLanguageChanged(const QString& locale) {
+ emit LanguageChanged(locale);
+ // first apply the configuration, and then restore the display
+ ApplyConfiguration();
+ RetranslateUI();
+ SetConfiguration();
+}
+
void ConfigureDialog::UpdateVisibleTabs() {
const auto items = ui->selectorList->selectedItems();
if (items.isEmpty()) {
@@ -103,8 +120,10 @@ void ConfigureDialog::UpdateVisibleTabs() {
{ui->generalTab, tr("General")},
{ui->systemTab, tr("System")},
{ui->profileManagerTab, tr("Profiles")},
- {ui->inputTab, tr("Input")},
+ {ui->inputTab, tr("Controls")},
{ui->hotkeysTab, tr("Hotkeys")},
+ {ui->cpuTab, tr("CPU")},
+ {ui->cpuDebugTab, tr("Debug")},
{ui->graphicsTab, tr("Graphics")},
{ui->graphicsAdvancedTab, tr("Advanced")},
{ui->audioTab, tr("Audio")},
@@ -122,6 +141,6 @@ void ConfigureDialog::UpdateVisibleTabs() {
const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
for (const auto tab : tabs) {
- ui->tabWidget->addTab(tab, widgets.at(tab));
+ ui->tabWidget->addTab(tab, tab->accessibleName());
}
}
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 2d3bfc2da..570c3b941 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -9,6 +9,10 @@
class HotkeyRegistry;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureDialog;
}
@@ -17,11 +21,18 @@ class ConfigureDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
+ InputCommon::InputSubsystem* input_subsystem);
~ConfigureDialog() override;
void ApplyConfiguration();
+private slots:
+ void OnLanguageChanged(const QString& locale);
+
+signals:
+ void LanguageChanged(const QString& locale);
+
private:
void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index 29f540eb7..7ab4a80f7 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -11,19 +11,6 @@
#include "yuzu/configuration/configure_filesystem.h"
#include "yuzu/uisettings.h"
-namespace {
-
-template <typename T>
-void SetComboBoxFromData(QComboBox* combo_box, T data) {
- const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data)));
- if (index >= combo_box->count() || index < 0)
- return;
-
- combo_box->setCurrentIndex(index);
-}
-
-} // Anonymous namespace
-
ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
ui->setupUi(this);
@@ -55,16 +42,16 @@ ConfigureFilesystem::~ConfigureFilesystem() = default;
void ConfigureFilesystem::setConfiguration() {
ui->nand_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
ui->sdmc_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
ui->dump_path_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));
ui->load_path_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));
ui->cache_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));
ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
@@ -73,23 +60,20 @@ void ConfigureFilesystem::setConfiguration() {
ui->cache_game_list->setChecked(UISettings::values.cache_game_list);
- SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size);
- SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size);
- SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size);
- SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size);
-
UpdateEnabledControls();
}
void ConfigureFilesystem::applyConfiguration() {
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- ui->nand_directory_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- ui->sdmc_directory_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
- ui->cache_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir,
+ ui->nand_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir,
+ ui->sdmc_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::DumpDir,
+ ui->dump_path_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::LoadDir,
+ ui->load_path_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir,
+ ui->cache_directory_edit->text().toStdString());
Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
@@ -98,15 +82,6 @@ void ConfigureFilesystem::applyConfiguration() {
Settings::values.dump_nso = ui->dump_nso->isChecked();
UISettings::values.cache_game_list = ui->cache_game_list->isChecked();
-
- Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
- ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong());
- Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
- ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong());
- Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
- ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong());
- Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
- ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong());
}
void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
@@ -138,7 +113,7 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),
QStringLiteral("NX Gamecard;*.xci"));
} else {
- str = QFileDialog::getExistingDirectory(this, caption, edit->text());
+ str = QFileDialog::getExistingDirectory(this, caption, edit->text()) + QDir::separator();
}
if (str.isEmpty())
@@ -148,12 +123,13 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
}
void ConfigureFilesystem::ResetMetadata() {
- if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
- "game_list")) {
+ if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The metadata cache is already empty."));
- } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
- DIR_SEP + "game_list")) {
+ } else if (Common::FS::DeleteDirRecursively(
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The operation completed successfully."));
UISettings::values.is_game_list_reload_pending.exchange(true);
diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui
index 58cd07f52..84bea0600 100644
--- a/src/yuzu/configuration/configure_filesystem.ui
+++ b/src/yuzu/configuration/configure_filesystem.ui
@@ -116,127 +116,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Storage Sizes</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="3" column="0">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>SD Card</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>System NAND</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="sysnand_size">
- <item>
- <property name="text">
- <string>2.5 GB</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QComboBox" name="sdmc_size">
- <property name="currentText">
- <string>32 GB</string>
- </property>
- <item>
- <property name="text">
- <string>1 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>16 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>32 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>64 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>128 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>256 GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>1 TB</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="usrnand_size">
- <item>
- <property name="text">
- <string>26 GB</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>User NAND</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>NAND</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="nand_size">
- <item>
- <property name="text">
- <string>29.1 GB</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Patch Manager</string>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 5ef927114..d4d29d422 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -7,38 +7,77 @@
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_general.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_general.h"
#include "yuzu/uisettings.h"
ConfigureGeneral::ConfigureGeneral(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
-
ui->setupUi(this);
+ SetupPerGameUI();
+
SetConfiguration();
- connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
+ if (Settings::IsConfiguringGlobal()) {
+ connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit,
+ [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); });
+ }
}
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
+ const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+
+ ui->use_multi_core->setEnabled(runtime_lock);
+ ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
+
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
+ ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse);
+
+ ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue());
+ ui->frame_limit->setValue(Settings::values.frame_limit.GetValue());
- ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
- ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
- ui->frame_limit->setValue(Settings::values.frame_limit);
+ if (Settings::IsConfiguringGlobal()) {
+ ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue());
+ } else {
+ ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() &&
+ use_frame_limit != ConfigurationShared::CheckState::Global);
+ }
}
void ConfigureGeneral::ApplyConfiguration() {
- UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
- UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
+ if (Settings::IsConfiguringGlobal()) {
+ UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
+ UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
+ UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
+ UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
+
+ // Guard if during game and set to game-specific value
+ if (Settings::values.use_frame_limit.UsingGlobal()) {
+ Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
+ Qt::Checked);
+ Settings::values.frame_limit.SetValue(ui->frame_limit->value());
+ }
+ if (Settings::values.use_multi_core.UsingGlobal()) {
+ Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked());
+ }
+ } else {
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core,
+ ui->use_multi_core, use_multi_core);
- Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
- Settings::values.frame_limit = ui->frame_limit->value();
+ bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global;
+ Settings::values.use_frame_limit.SetGlobal(global_frame_limit);
+ Settings::values.frame_limit.SetGlobal(global_frame_limit);
+ if (!global_frame_limit) {
+ Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
+ Qt::Checked);
+ Settings::values.frame_limit.SetValue(ui->frame_limit->value());
+ }
+ }
}
void ConfigureGeneral::changeEvent(QEvent* event) {
@@ -52,3 +91,27 @@ void ConfigureGeneral::changeEvent(QEvent* event) {
void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
+
+void ConfigureGeneral::SetupPerGameUI() {
+ if (Settings::IsConfiguringGlobal()) {
+ ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal());
+ ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
+
+ return;
+ }
+
+ ui->toggle_check_exit->setVisible(false);
+ ui->toggle_user_on_boot->setVisible(false);
+ ui->toggle_background_pause->setVisible(false);
+ ui->toggle_hide_mouse->setVisible(false);
+
+ ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit,
+ Settings::values.use_frame_limit, use_frame_limit);
+ ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
+ use_multi_core);
+
+ connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() {
+ ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() &&
+ (use_frame_limit != ConfigurationShared::CheckState::Global));
+ });
+}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index ef05ce065..323ffbd8f 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -7,6 +7,10 @@
#include <memory>
#include <QWidget>
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
class HotkeyRegistry;
namespace Ui {
@@ -28,5 +32,10 @@ private:
void SetConfiguration();
+ void SetupPerGameUI();
+
std::unique_ptr<Ui::ConfigureGeneral> ui;
+
+ ConfigurationShared::CheckState use_frame_limit;
+ ConfigurationShared::CheckState use_multi_core;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 857119bb3..2711116a2 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -52,6 +52,13 @@
</layout>
</item>
<item>
+ <widget class="QCheckBox" name="use_multi_core">
+ <property name="text">
+ <string>Multicore CPU Emulation</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
@@ -72,6 +79,13 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="toggle_hide_mouse">
+ <property name="text">
+ <string>Hide mouse on inactivity</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index ea667caef..6fda0ce35 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -13,65 +13,32 @@
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
#ifdef HAS_VULKAN
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#endif
-namespace {
-enum class Resolution : int {
- Auto,
- Scale1x,
- Scale2x,
- Scale3x,
- Scale4x,
-};
-
-float ToResolutionFactor(Resolution option) {
- switch (option) {
- case Resolution::Auto:
- return 0.f;
- case Resolution::Scale1x:
- return 1.f;
- case Resolution::Scale2x:
- return 2.f;
- case Resolution::Scale3x:
- return 3.f;
- case Resolution::Scale4x:
- return 4.f;
- }
- return 0.f;
-}
-
-Resolution FromResolutionFactor(float factor) {
- if (factor == 0.f) {
- return Resolution::Auto;
- } else if (factor == 1.f) {
- return Resolution::Scale1x;
- } else if (factor == 2.f) {
- return Resolution::Scale2x;
- } else if (factor == 3.f) {
- return Resolution::Scale3x;
- } else if (factor == 4.f) {
- return Resolution::Scale4x;
- }
- return Resolution::Auto;
-}
-} // Anonymous namespace
-
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
- vulkan_device = Settings::values.vulkan_device;
+ vulkan_device = Settings::values.vulkan_device.GetValue();
RetrieveVulkanDevices();
ui->setupUi(this);
+ SetupPerGameUI();
+
SetConfiguration();
- connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- [this] { UpdateDeviceComboBox(); });
- connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
+ connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
+ UpdateDeviceComboBox();
+ if (!Settings::IsConfiguringGlobal()) {
+ ConfigurationShared::SetHighlight(
+ ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
+ }
+ });
+ connect(ui->device, qOverload<int>(&QComboBox::activated), this,
[this](int device) { UpdateDeviceSelection(device); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
@@ -81,6 +48,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
}
UpdateBackgroundColorButton(new_bg_color);
});
+
+ ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
+ ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
void ConfigureGraphics::UpdateDeviceSelection(int device) {
@@ -98,31 +68,103 @@ void ConfigureGraphics::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->api->setEnabled(runtime_lock);
- ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend));
- ui->resolution_factor_combobox->setCurrentIndex(
- static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
- ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio);
- ui->use_disk_shader_cache->setEnabled(runtime_lock);
- ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
- ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
- UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
- Settings::values.bg_blue));
+ ui->use_disk_shader_cache->setEnabled(runtime_lock);
+ ui->use_nvdec_emulation->setEnabled(runtime_lock);
+ ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
+ ui->use_asynchronous_gpu_emulation->setChecked(
+ Settings::values.use_asynchronous_gpu_emulation.GetValue());
+ ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue());
+
+ if (Settings::IsConfiguringGlobal()) {
+ ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
+ ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
+ } else {
+ ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
+ ConfigurationShared::SetHighlight(ui->api_layout,
+ !Settings::values.renderer_backend.UsingGlobal());
+ ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
+ &Settings::values.aspect_ratio);
+
+ ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
+ ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->ar_label,
+ !Settings::values.aspect_ratio.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
+ }
+
+ UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(),
+ Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue()));
UpdateDeviceComboBox();
}
void ConfigureGraphics::ApplyConfiguration() {
- Settings::values.renderer_backend = GetCurrentGraphicsBackend();
- Settings::values.vulkan_device = vulkan_device;
- Settings::values.resolution_factor =
- ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
- Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex();
- Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
- Settings::values.use_asynchronous_gpu_emulation =
- ui->use_asynchronous_gpu_emulation->isChecked();
- Settings::values.bg_red = static_cast<float>(bg_color.redF());
- Settings::values.bg_green = static_cast<float>(bg_color.greenF());
- Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
+ if (Settings::IsConfiguringGlobal()) {
+ // Guard if during game and set to game-specific value
+ if (Settings::values.renderer_backend.UsingGlobal()) {
+ Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
+ }
+ if (Settings::values.vulkan_device.UsingGlobal()) {
+ Settings::values.vulkan_device.SetValue(vulkan_device);
+ }
+ if (Settings::values.aspect_ratio.UsingGlobal()) {
+ Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex());
+ }
+ if (Settings::values.use_disk_shader_cache.UsingGlobal()) {
+ Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked());
+ }
+ if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) {
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(
+ ui->use_asynchronous_gpu_emulation->isChecked());
+ }
+ if (Settings::values.use_nvdec_emulation.UsingGlobal()) {
+ Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked());
+ }
+ if (Settings::values.bg_red.UsingGlobal()) {
+ Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
+ Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
+ Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
+ }
+ } else {
+ if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ Settings::values.renderer_backend.SetGlobal(true);
+ Settings::values.vulkan_device.SetGlobal(true);
+ } else {
+ Settings::values.renderer_backend.SetGlobal(false);
+ Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
+ if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
+ Settings::values.vulkan_device.SetGlobal(false);
+ Settings::values.vulkan_device.SetValue(vulkan_device);
+ } else {
+ Settings::values.vulkan_device.SetGlobal(true);
+ }
+ }
+
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
+ ui->aspect_ratio_combobox);
+
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
+ ui->use_disk_shader_cache, use_disk_shader_cache);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
+ ui->use_asynchronous_gpu_emulation,
+ use_asynchronous_gpu_emulation);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,
+ ui->use_nvdec_emulation, use_nvdec_emulation);
+
+ if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ Settings::values.bg_red.SetGlobal(true);
+ Settings::values.bg_green.SetGlobal(true);
+ Settings::values.bg_blue.SetGlobal(true);
+ } else {
+ Settings::values.bg_red.SetGlobal(false);
+ Settings::values.bg_green.SetGlobal(false);
+ Settings::values.bg_blue.SetGlobal(false);
+ Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
+ Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
+ Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
+ }
+ }
}
void ConfigureGraphics::changeEvent(QEvent* event) {
@@ -151,19 +193,27 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
ui->device->clear();
bool enabled = false;
+
+ if (!Settings::IsConfiguringGlobal() &&
+ ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ vulkan_device = Settings::values.vulkan_device.GetValue();
+ }
switch (GetCurrentGraphicsBackend()) {
case Settings::RendererBackend::OpenGL:
ui->device->addItem(tr("OpenGL Graphics Device"));
enabled = false;
break;
case Settings::RendererBackend::Vulkan:
- for (const auto device : vulkan_devices) {
+ for (const auto& device : vulkan_devices) {
ui->device->addItem(device);
}
ui->device->setCurrentIndex(vulkan_device);
enabled = !vulkan_devices.empty();
break;
}
+ // If in per-game config and use global is selected, don't enable.
+ enabled &= !(!Settings::IsConfiguringGlobal() &&
+ ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX);
ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
}
@@ -177,5 +227,48 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
- return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
+ if (Settings::IsConfiguringGlobal()) {
+ return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
+ }
+
+ if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ Settings::values.renderer_backend.SetGlobal(true);
+ return Settings::values.renderer_backend.GetValue();
+ }
+ Settings::values.renderer_backend.SetGlobal(false);
+ return static_cast<Settings::RendererBackend>(ui->api->currentIndex() -
+ ConfigurationShared::USE_GLOBAL_OFFSET);
+}
+
+void ConfigureGraphics::SetupPerGameUI() {
+ if (Settings::IsConfiguringGlobal()) {
+ ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
+ ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
+ ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
+ ui->use_asynchronous_gpu_emulation->setEnabled(
+ Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
+ ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal());
+ ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
+ ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
+
+ return;
+ }
+
+ connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+ ui->bg_button->setEnabled(index == 1);
+ ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
+ });
+
+ ConfigurationShared::SetColoredTristate(
+ ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
+ ConfigurationShared::SetColoredTristate(
+ ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation);
+ ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
+ Settings::values.use_asynchronous_gpu_emulation,
+ use_asynchronous_gpu_emulation);
+
+ ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
+ Settings::values.aspect_ratio.GetValue(true));
+ ConfigurationShared::InsertGlobalItem(
+ ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 7e0596d9c..1fefc88eb 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -10,6 +10,10 @@
#include <QWidget>
#include "core/settings.h"
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
namespace Ui {
class ConfigureGraphics;
}
@@ -35,11 +39,17 @@ private:
void RetrieveVulkanDevices();
+ void SetupPerGameUI();
+
Settings::RendererBackend GetCurrentGraphicsBackend() const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
+ ConfigurationShared::CheckState use_nvdec_emulation;
+ ConfigurationShared::CheckState use_disk_shader_cache;
+ ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
+
std::vector<QString> vulkan_devices;
u32 vulkan_device{};
};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index c816d6108..58486eb1e 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>400</width>
+ <width>437</width>
<height>321</height>
</rect>
</property>
@@ -23,43 +23,56 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>API:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="api">
- <item>
+ <widget class="QWidget" name="api_layout" native="true">
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="api_label">
<property name="text">
- <string notr="true">OpenGL</string>
+ <string>API:</string>
</property>
- </item>
- <item>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="api">
+ <item>
+ <property name="text">
+ <string notr="true">OpenGL</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Vulkan</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="device_label">
<property name="text">
- <string notr="true">Vulkan</string>
+ <string>Device:</string>
</property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="device"/>
- </item>
- </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="device"/>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
@@ -85,100 +98,140 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Internal Resolution:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="resolution_factor_combobox">
- <item>
- <property name="text">
- <string>Auto (Window Size)</string>
- </property>
- </item>
- <item>
+ <widget class="QCheckBox" name="use_nvdec_emulation">
+ <property name="text">
+ <string>Use NVDEC emulation</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="aspect_ratio_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="ar_label">
<property name="text">
- <string>Native (1280x720)</string>
+ <string>Aspect Ratio:</string>
</property>
- </item>
- <item>
- <property name="text">
- <string>2x Native (2560x1440)</string>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="aspect_ratio_combobox">
+ <item>
+ <property name="text">
+ <string>Default (16:9)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Force 4:3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Force 21:9</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Stretch to Window</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bg_layout" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QComboBox" name="bg_combobox">
+ <property name="currentText">
+ <string>Use global background color</string>
</property>
- </item>
- <item>
- <property name="text">
- <string>3x Native (3840x2160)</string>
+ <property name="currentIndex">
+ <number>0</number>
</property>
- </item>
- <item>
- <property name="text">
- <string>4x Native (5120x2880)</string>
+ <property name="maxVisibleItems">
+ <number>10</number>
</property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <item>
- <widget class="QLabel" name="ar_label">
- <property name="text">
- <string>Aspect Ratio:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="aspect_ratio_combobox">
- <item>
+ <item>
+ <property name="text">
+ <string>Use global background color</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Set background color:</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="bg_label">
<property name="text">
- <string>Default (16:9)</string>
+ <string>Background Color:</string>
</property>
- </item>
- <item>
- <property name="text">
- <string>Force 4:3</string>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
- </item>
- <item>
- <property name="text">
- <string>Force 21:9</string>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
</property>
- </item>
- <item>
- <property name="text">
- <string>Stretch to Window</string>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="bg_button">
+ <property name="maximumSize">
+ <size>
+ <width>40</width>
+ <height>16777215</height>
+ </size>
</property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QLabel" name="bg_label">
- <property name="text">
- <string>Background Color:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="bg_button">
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- </widget>
- </item>
- </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index b9f429f84..383c7bac8 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -5,6 +5,7 @@
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics_advanced.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)
@@ -12,6 +13,8 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)
ui->setupUi(this);
+ SetupPerGameUI();
+
SetConfiguration();
}
@@ -19,20 +22,86 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
void ConfigureGraphicsAdvanced::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
- ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
ui->use_vsync->setEnabled(runtime_lock);
- ui->use_vsync->setChecked(Settings::values.use_vsync);
- ui->force_30fps_mode->setEnabled(runtime_lock);
- ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
+ ui->use_assembly_shaders->setEnabled(runtime_lock);
+ ui->use_asynchronous_shaders->setEnabled(runtime_lock);
ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
- ui->anisotropic_filtering_combobox->setCurrentIndex(Settings::values.max_anisotropy);
+
+ ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
+ ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());
+ ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
+ ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
+
+ if (Settings::IsConfiguringGlobal()) {
+ ui->gpu_accuracy->setCurrentIndex(
+ static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
+ ui->anisotropic_filtering_combobox->setCurrentIndex(
+ Settings::values.max_anisotropy.GetValue());
+ } else {
+ ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
+ ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
+ &Settings::values.max_anisotropy);
+ ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
+ !Settings::values.gpu_accuracy.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->af_label,
+ !Settings::values.max_anisotropy.UsingGlobal());
+ }
}
void ConfigureGraphicsAdvanced::ApplyConfiguration() {
- Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
- Settings::values.use_vsync = ui->use_vsync->isChecked();
- Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
- Settings::values.max_anisotropy = ui->anisotropic_filtering_combobox->currentIndex();
+ // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots)
+ const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(
+ ui->gpu_accuracy->currentIndex() -
+ ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
+
+ if (Settings::IsConfiguringGlobal()) {
+ // Must guard in case of a during-game configuration when set to be game-specific.
+ if (Settings::values.gpu_accuracy.UsingGlobal()) {
+ Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
+ }
+ if (Settings::values.use_vsync.UsingGlobal()) {
+ Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked());
+ }
+ if (Settings::values.use_assembly_shaders.UsingGlobal()) {
+ Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked());
+ }
+ if (Settings::values.use_asynchronous_shaders.UsingGlobal()) {
+ Settings::values.use_asynchronous_shaders.SetValue(
+ ui->use_asynchronous_shaders->isChecked());
+ }
+ if (Settings::values.use_asynchronous_shaders.UsingGlobal()) {
+ Settings::values.use_asynchronous_shaders.SetValue(
+ ui->use_asynchronous_shaders->isChecked());
+ }
+ if (Settings::values.use_fast_gpu_time.UsingGlobal()) {
+ Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked());
+ }
+ if (Settings::values.max_anisotropy.UsingGlobal()) {
+ Settings::values.max_anisotropy.SetValue(
+ ui->anisotropic_filtering_combobox->currentIndex());
+ }
+ } else {
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
+ ui->anisotropic_filtering_combobox);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync,
+ use_vsync);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders,
+ ui->use_assembly_shaders, use_assembly_shaders);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
+ ui->use_asynchronous_shaders,
+ use_asynchronous_shaders);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
+ ui->use_fast_gpu_time, use_fast_gpu_time);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
+ ui->anisotropic_filtering_combobox);
+
+ if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ Settings::values.gpu_accuracy.SetGlobal(true);
+ } else {
+ Settings::values.gpu_accuracy.SetGlobal(false);
+ Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
+ }
+ }
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -46,3 +115,34 @@ void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
void ConfigureGraphicsAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
+
+void ConfigureGraphicsAdvanced::SetupPerGameUI() {
+ // Disable if not global (only happens during game)
+ if (Settings::IsConfiguringGlobal()) {
+ ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
+ ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
+ ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());
+ ui->use_asynchronous_shaders->setEnabled(
+ Settings::values.use_asynchronous_shaders.UsingGlobal());
+ ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
+ ui->anisotropic_filtering_combobox->setEnabled(
+ Settings::values.max_anisotropy.UsingGlobal());
+
+ return;
+ }
+
+ ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
+ ConfigurationShared::SetColoredTristate(
+ ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);
+ ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
+ Settings::values.use_asynchronous_shaders,
+ use_asynchronous_shaders);
+ ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
+ Settings::values.use_fast_gpu_time, use_fast_gpu_time);
+ ConfigurationShared::SetColoredComboBox(
+ ui->gpu_accuracy, ui->label_gpu_accuracy,
+ static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
+ ConfigurationShared::SetColoredComboBox(
+ ui->anisotropic_filtering_combobox, ui->af_label,
+ static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
+}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index bbc9d4355..e61b571c7 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -7,6 +7,10 @@
#include <memory>
#include <QWidget>
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
namespace Ui {
class ConfigureGraphicsAdvanced;
}
@@ -26,5 +30,12 @@ private:
void SetConfiguration();
+ void SetupPerGameUI();
+
std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
+
+ ConfigurationShared::CheckState use_vsync;
+ ConfigurationShared::CheckState use_assembly_shaders;
+ ConfigurationShared::CheckState use_asynchronous_shaders;
+ ConfigurationShared::CheckState use_fast_gpu_time;
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 42eec278e..846a30586 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>400</width>
+ <width>404</width>
<height>321</height>
</rect>
</property>
@@ -23,10 +23,47 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <widget class="QCheckBox" name="use_accurate_gpu_emulation">
- <property name="text">
- <string>Use accurate GPU emulation (slow)</string>
- </property>
+ <widget class="QWidget" name="gpu_accuracy_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_gpu_accuracy">
+ <property name="text">
+ <string>Accuracy Level:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="gpu_accuracy">
+ <item>
+ <property name="text">
+ <string notr="true">Normal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">High</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Extreme(very slow)</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -40,51 +77,85 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="force_30fps_mode">
+ <widget class="QCheckBox" name="use_assembly_shaders">
+ <property name="toolTip">
+ <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string>
+ </property>
<property name="text">
- <string>Force 30 FPS mode</string>
+ <string>Use assembly shaders (experimental, Nvidia OpenGL only)</string>
</property>
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <item>
- <widget class="QLabel" name="af_label">
- <property name="text">
- <string>Anisotropic Filtering:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anisotropic_filtering_combobox">
- <item>
- <property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8x</string>
- </property>
- </item>
- <item>
+ <widget class="QCheckBox" name="use_asynchronous_shaders">
+ <property name="toolTip">
+ <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
+ </property>
+ <property name="text">
+ <string>Use asynchronous shader building (experimental)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="use_fast_gpu_time">
+ <property name="text">
+ <string>Use Fast GPU Time</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="af_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="af_label">
<property name="text">
- <string>16x</string>
+ <string>Anisotropic Filtering:</string>
</property>
- </item>
- </widget>
- </item>
- </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="anisotropic_filtering_combobox">
+ <item>
+ <property name="text">
+ <string>Default</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>2x</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>4x</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>8x</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>16x</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index fa9052136..cbee51a5e 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -2,10 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include "core/settings.h"
#include "ui_configure_hotkeys.h"
+#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_hotkeys.h"
#include "yuzu/hotkeys.h"
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
@@ -19,6 +21,9 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
model->setColumnCount(3);
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
+ connect(ui->hotkey_list, &QTreeView::customContextMenuRequested, this,
+ &ConfigureHotkeys::PopupContextMenu);
+ ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->hotkey_list->setModel(model);
// TODO(Kloen): Make context configurable as well (hiding the column for now)
@@ -27,6 +32,10 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
ui->hotkey_list->setColumnWidth(0, 200);
ui->hotkey_list->resizeColumnToContents(1);
+ connect(ui->button_restore_defaults, &QPushButton::clicked, this,
+ &ConfigureHotkeys::RestoreDefaults);
+ connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll);
+
RetranslateUI();
}
@@ -71,7 +80,6 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
}
index = index.sibling(index.row(), 1);
- auto* const model = ui->hotkey_list->model();
const auto previous_key = model->data(index);
SequenceDialog hotkey_dialog{this};
@@ -81,31 +89,33 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {
return;
}
+ const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence);
- if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
- QMessageBox::warning(this, tr("Conflicting Key Sequence"),
- tr("The entered key sequence is already assigned to another hotkey."));
+ if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) {
+ QMessageBox::warning(
+ this, tr("Conflicting Key Sequence"),
+ tr("The entered key sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
}
}
-bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
- for (int r = 0; r < model->rowCount(); r++) {
+std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
+ for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* const parent = model->item(r, 0);
- for (int r2 = 0; r2 < parent->rowCount(); r2++) {
+ for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
const QStandardItem* const key_seq_item = parent->child(r2, 1);
const auto key_seq_str = key_seq_item->text();
const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
if (key_sequence == key_seq) {
- return true;
+ return std::make_pair(true, parent->child(r2, 0)->text());
}
}
}
- return false;
+ return std::make_pair(false, QString());
}
void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
@@ -128,3 +138,55 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
registry.SaveHotkeys();
}
+
+void ConfigureHotkeys::RestoreDefaults() {
+ for (int r = 0; r < model->rowCount(); ++r) {
+ const QStandardItem* parent = model->item(r, 0);
+
+ for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
+ model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first);
+ }
+ }
+}
+
+void ConfigureHotkeys::ClearAll() {
+ for (int r = 0; r < model->rowCount(); ++r) {
+ const QStandardItem* parent = model->item(r, 0);
+
+ for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
+ model->item(r, 0)->child(r2, 1)->setText(QString{});
+ }
+ }
+}
+
+void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
+ QModelIndex index = ui->hotkey_list->indexAt(menu_location);
+ if (!index.parent().isValid()) {
+ return;
+ }
+
+ const auto selected = index.sibling(index.row(), 1);
+ QMenu context_menu;
+
+ QAction* restore_default = context_menu.addAction(tr("Restore Default"));
+ QAction* clear = context_menu.addAction(tr("Clear"));
+
+ connect(restore_default, &QAction::triggered, [this, selected] {
+ const QKeySequence& default_key_sequence = QKeySequence::fromString(
+ Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText);
+ const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
+
+ if (key_sequence_used &&
+ default_key_sequence != QKeySequence(model->data(selected).toString())) {
+
+ QMessageBox::warning(
+ this, tr("Conflicting Key Sequence"),
+ tr("The default key sequence is already assigned to: %1").arg(used_action));
+ } else {
+ model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
+ }
+ });
+ connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
+
+ context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
+}
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index 8f8c6173b..a2ec3323e 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -35,7 +35,11 @@ private:
void RetranslateUI();
void Configure(QModelIndex index);
- bool IsUsedKey(QKeySequence key_sequence) const;
+ std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const;
+
+ void RestoreDefaults();
+ void ClearAll();
+ void PopupContextMenu(const QPoint& menu_location);
std::unique_ptr<Ui::ConfigureHotkeys> ui;
diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui
index 0d0b70f38..6d9f861e3 100644
--- a/src/yuzu/configuration/configure_hotkeys.ui
+++ b/src/yuzu/configuration/configure_hotkeys.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>363</width>
- <height>388</height>
+ <width>439</width>
+ <height>510</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
@@ -24,6 +24,37 @@
</widget>
</item>
<item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_clear_all">
+ <property name="text">
+ <string>Clear All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_restore_defaults">
+ <property name="text">
+ <string>Restore Defaults</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
<widget class="QTreeView" name="hotkey_list">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
@@ -39,4 +70,4 @@
</widget>
<resources/>
<connections/>
-</ui> \ No newline at end of file
+</ui>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index f2977719c..d9009091b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -8,18 +8,35 @@
#include <QSignalBlocker>
#include <QTimer>
-#include "configuration/configure_touchscreen_advanced.h"
#include "core/core.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
-#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/sm/sm.h"
#include "ui_configure_input.h"
+#include "ui_configure_input_advanced.h"
#include "ui_configure_input_player.h"
+#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
+#include "yuzu/configuration/configure_touchscreen_advanced.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+
+namespace {
+template <typename Dialog, typename... Args>
+void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
+ Dialog dialog(&parent, std::forward<Args>(args)...);
+
+ const auto res = dialog.exec();
+ if (res == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+ }
+}
+} // Anonymous namespace
void OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
@@ -48,97 +65,134 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
}
}
-namespace {
-template <typename Dialog, typename... Args>
-void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
- parent.ApplyConfiguration();
- Dialog dialog(&parent, std::forward<Args>(args)...);
-
- const auto res = dialog.exec();
- if (res == QDialog::Accepted) {
- dialog.ApplyConfiguration();
- }
-}
-} // Anonymous namespace
-
ConfigureInput::ConfigureInput(QWidget* parent)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
+ profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
+}
+
+ConfigureInput::~ConfigureInput() = default;
- players_controller = {
- ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox,
- ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox,
+void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
+ std::size_t max_players) {
+ player_controllers = {
+ new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
};
- players_configure = {
- ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure,
- ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,
+ player_tabs = {
+ ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,
+ ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
};
- RetranslateUI();
- LoadConfiguration();
- UpdateUIEnabled();
+ player_connected = {
+ ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+ ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+ ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+ };
- connect(ui->restore_defaults_button, &QPushButton::clicked, this,
- &ConfigureInput::RestoreDefaults);
+ std::array<QLabel*, 8> player_connected_labels = {
+ ui->label, ui->label_3, ui->label_4, ui->label_5,
+ ui->label_6, ui->label_7, ui->label_8, ui->label_9,
+ };
- for (auto* enabled : players_controller) {
- connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureInput::UpdateUIEnabled);
- }
- connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
- &ConfigureInput::UpdateUIEnabled);
- connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
- &ConfigureInput::UpdateUIEnabled);
-
- for (std::size_t i = 0; i < players_configure.size(); ++i) {
- connect(players_configure[i], &QPushButton::clicked, this,
- [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); });
+ for (std::size_t i = 0; i < player_tabs.size(); ++i) {
+ player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
+ player_tabs[i]->layout()->addWidget(player_controllers[i]);
+ connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
+ if (is_connected) {
+ for (std::size_t index = 0; index <= i; ++index) {
+ player_connected[index]->setChecked(is_connected);
+ }
+ } else {
+ for (std::size_t index = i; index < player_tabs.size(); ++index) {
+ player_connected[index]->setChecked(is_connected);
+ }
+ }
+ });
+ connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
+ &ConfigureInput::UpdateAllInputDevices);
+ connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
+ &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
+ connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
+ player_controllers[i]->ConnectPlayer(state == Qt::Checked);
+ });
+
+ // Remove/hide all the elements that exceed max_players, if applicable.
+ if (i >= max_players) {
+ ui->tabWidget->removeTab(static_cast<int>(max_players));
+ player_connected[i]->hide();
+ player_connected_labels[i]->hide();
+ }
}
+ // Only the first player can choose handheld mode so connect the signal just to player 1
+ connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
+ [this](bool is_handheld) { UpdateDockedState(is_handheld); });
+
+ advanced = new ConfigureInputAdvanced(this);
+ ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
+ ui->tabAdvanced->layout()->addWidget(advanced);
+ connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
+ });
+ connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
+ });
+ connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
+ [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
+ connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
+ [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
+ });
- connect(ui->handheld_configure, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); });
+ connect(ui->vibrationButton, &QPushButton::clicked,
+ [this] { CallConfigureDialog<ConfigureVibration>(*this); });
- connect(ui->debug_configure, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); });
+ connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
+ });
- connect(ui->mouse_advanced, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); });
+ connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
- connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
+ RetranslateUI();
+ LoadConfiguration();
}
-ConfigureInput::~ConfigureInput() = default;
+QList<QWidget*> ConfigureInput::GetSubTabs() const {
+ return {
+ ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
+ ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced,
+ };
+}
void ConfigureInput::ApplyConfiguration() {
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- const auto controller_type_index = players_controller[i]->currentIndex();
+ for (auto controller : player_controllers) {
+ controller->ApplyConfiguration();
+ }
- Settings::values.players[i].connected = controller_type_index != 0;
+ advanced->ApplyConfiguration();
- if (controller_type_index > 0) {
- Settings::values.players[i].type =
- static_cast<Settings::ControllerType>(controller_type_index - 1);
- } else {
- Settings::values.players[i].type = Settings::ControllerType::DualJoycon;
- }
- }
+ const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+ Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+ OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
- const bool pre_docked_mode = Settings::values.use_docked_mode;
- Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
- Settings::values
- .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
- .connected = ui->handheld_connected->isChecked();
- Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
- Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
- Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
- Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
+ Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+ Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void ConfigureInput::changeEvent(QEvent* event) {
@@ -146,94 +200,74 @@ void ConfigureInput::changeEvent(QEvent* event) {
RetranslateUI();
}
- QDialog::changeEvent(event);
+ QWidget::changeEvent(event);
}
void ConfigureInput::RetranslateUI() {
ui->retranslateUi(this);
- RetranslateControllerComboBoxes();
}
-void ConfigureInput::RetranslateControllerComboBoxes() {
- for (auto* controller_box : players_controller) {
- [[maybe_unused]] const QSignalBlocker blocker(controller_box);
-
- controller_box->clear();
- controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
- tr("Single Right Joycon"), tr("Single Left Joycon")});
- }
-
+void ConfigureInput::LoadConfiguration() {
LoadPlayerControllerIndices();
+ UpdateDockedState(Settings::values.players.GetValue()[8].connected);
+
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+ ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
-void ConfigureInput::UpdateUIEnabled() {
- bool hit_disabled = false;
- for (auto* player : players_controller) {
- player->setDisabled(hit_disabled);
- if (hit_disabled) {
- player->setCurrentIndex(0);
- }
- if (!hit_disabled && player->currentIndex() == 0) {
- hit_disabled = true;
- }
+void ConfigureInput::LoadPlayerControllerIndices() {
+ for (std::size_t i = 0; i < player_connected.size(); ++i) {
+ const auto connected = Settings::values.players.GetValue()[i].connected ||
+ (i == 0 && Settings::values.players.GetValue()[8].connected);
+ player_connected[i]->setChecked(connected);
}
+}
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
- }
+void ConfigureInput::ClearAll() {
+ // We don't have a good way to know what tab is active, but we can find out by getting the
+ // parent of the consoleInputSettings
+ auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
+ player_tab->ClearAll();
+}
- ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() &&
- !ui->use_docked_mode->isChecked());
- ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
- ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
- !ui->use_docked_mode->isChecked());
- ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
- ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
- ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
+void ConfigureInput::RestoreDefaults() {
+ // We don't have a good way to know what tab is active, but we can find out by getting the
+ // parent of the consoleInputSettings
+ auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
+ player_tab->RestoreDefaults();
+
+ ui->radioDocked->setChecked(true);
+ ui->radioUndocked->setChecked(false);
+ ui->vibrationGroup->setChecked(true);
+ ui->motionGroup->setChecked(true);
}
-void ConfigureInput::LoadConfiguration() {
- std::stable_partition(
- Settings::values.players.begin(),
- Settings::values.players.begin() +
- Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
- [](const auto& player) { return player.connected; });
+void ConfigureInput::UpdateDockedState(bool is_handheld) {
+ // Disallow changing the console mode if the controller type is handheld.
+ ui->radioDocked->setEnabled(!is_handheld);
+ ui->radioUndocked->setEnabled(!is_handheld);
- LoadPlayerControllerIndices();
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
- ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
- ui->handheld_connected->setChecked(
- Settings::values
- .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
- .connected);
- ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
- ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
- ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
- ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
-
- UpdateUIEnabled();
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
+ }
}
-void ConfigureInput::LoadPlayerControllerIndices() {
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- const auto connected = Settings::values.players[i].connected;
- players_controller[i]->setCurrentIndex(
- connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
+void ConfigureInput::UpdateAllInputDevices() {
+ for (const auto& player : player_controllers) {
+ player->UpdateInputDeviceCombobox();
}
}
-void ConfigureInput::RestoreDefaults() {
- players_controller[0]->setCurrentIndex(2);
+void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
+ for (std::size_t i = 0; i < player_controllers.size(); ++i) {
+ if (i == player_index) {
+ continue;
+ }
- for (std::size_t i = 1; i < players_controller.size(); ++i) {
- players_controller[i]->setCurrentIndex(0);
+ player_controllers[i]->UpdateInputProfiles();
}
-
- ui->use_docked_mode->setCheckState(Qt::Unchecked);
- ui->handheld_connected->setCheckState(Qt::Unchecked);
- ui->mouse_enabled->setCheckState(Qt::Unchecked);
- ui->keyboard_enabled->setCheckState(Qt::Unchecked);
- ui->debug_enabled->setCheckState(Qt::Unchecked);
- ui->touchscreen_enabled->setCheckState(Qt::Checked);
- UpdateUIEnabled();
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 2f70cb3ca..f4eb0d78b 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,37 +7,52 @@
#include <array>
#include <memory>
-#include <QDialog>
#include <QKeyEvent>
+#include <QList>
+#include <QWidget>
-#include "ui_configure_input.h"
-
-class QPushButton;
+class QCheckBox;
class QString;
class QTimer;
+class ConfigureInputAdvanced;
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureInput;
}
void OnDockedModeChanged(bool last_state, bool new_state);
-class ConfigureInput : public QDialog {
+class ConfigureInput : public QWidget {
Q_OBJECT
public:
explicit ConfigureInput(QWidget* parent = nullptr);
~ConfigureInput() override;
- /// Save all button configurations to settings file
+ /// Initializes the input dialog with the given input subsystem.
+ void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
+
+ /// Save all button configurations to settings file.
void ApplyConfiguration();
+ QList<QWidget*> GetSubTabs() const;
+
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void RetranslateControllerComboBoxes();
+ void ClearAll();
- void UpdateUIEnabled();
+ void UpdateDockedState(bool is_handheld);
+ void UpdateAllInputDevices();
+ void UpdateAllInputProfiles(std::size_t player_index);
/// Load configuration settings.
void LoadConfiguration();
@@ -48,6 +63,10 @@ private:
std::unique_ptr<Ui::ConfigureInput> ui;
- std::array<QComboBox*, 8> players_controller;
- std::array<QPushButton*, 8> players_configure;
+ std::unique_ptr<InputProfiles> profiles;
+
+ std::array<ConfigureInputPlayer*, 8> player_controllers;
+ std::array<QWidget*, 8> player_tabs;
+ std::array<QCheckBox*, 8> player_connected;
+ ConfigureInputAdvanced* advanced;
};
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index efffd8487..2707025e7 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -1,529 +1,548 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
- <widget class="QDialog" name="ConfigureInput">
+ <widget class="QWidget" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>384</width>
- <height>576</height>
+ <width>680</width>
+ <height>540</height>
</rect>
</property>
<property name="windowTitle">
- <string>Custom Input Settings</string>
+ <string>ConfigureInput</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
<item>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="gridGroupBox_1">
- <property name="title">
- <string>Players</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="2">
- <widget class="QComboBox" name="player1_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="QPushButton" name="player1_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Controller Type</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QComboBox" name="player2_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QComboBox" name="player3_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="4" column="2">
- <widget class="QComboBox" name="player4_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="QComboBox" name="player5_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="6" column="2">
- <widget class="QComboBox" name="player6_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QComboBox" name="player7_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="8" column="2">
- <widget class="QComboBox" name="player8_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="player2_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="3" column="3">
- <widget class="QPushButton" name="player3_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="4" column="3">
- <widget class="QPushButton" name="player4_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="5" column="3">
- <widget class="QPushButton" name="player5_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="6" column="3">
- <widget class="QPushButton" name="player6_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="7" column="3">
- <widget class="QPushButton" name="player7_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="8" column="3">
- <widget class="QPushButton" name="player8_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="4">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_3">
- <property name="minimumSize">
- <size>
- <width>55</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Player 1</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Player 2</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Player 3</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>Player 4</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Player 5</string>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Player 6</string>
- </property>
- </widget>
- </item>
- <item row="7" column="1">
- <widget class="QLabel" name="label_9">
- <property name="text">
- <string>Player 7</string>
- </property>
- </widget>
- </item>
- <item row="8" column="1">
- <widget class="QLabel" name="label_10">
- <property name="text">
- <string>Player 8</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox_2">
- <property name="title">
- <string>Handheld</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="2">
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>72</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="4">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="3">
- <widget class="QPushButton" name="handheld_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="handheld_connected">
- <property name="text">
- <string>Joycons Docked</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="use_docked_mode">
- <property name="text">
- <string>Use Docked Mode</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox_3">
- <property name="title">
- <string>Other</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="1">
- <widget class="QCheckBox" name="keyboard_enabled">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="text">
- <string>Keyboard</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="debug_enabled">
- <property name="text">
- <string>Debug Controller</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="touchscreen_enabled">
- <property name="text">
- <string>Touchscreen</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="mouse_enabled">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="text">
- <string>Mouse</string>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <spacer name="horizontalSpacer_7">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_8">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>76</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer_6">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="3" column="3">
- <widget class="QPushButton" name="touchscreen_advanced">
- <property name="text">
- <string>Advanced</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="debug_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QPushButton" name="mouse_advanced">
- <property name="text">
- <string>Advanced</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QPushButton" name="restore_defaults_button">
- <property name="text">
- <string>Restore Defaults</string>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tabPlayer1">
+ <property name="accessibleName">
+ <string>Player 1</string>
+ </property>
+ <attribute name="title">
+ <string>Player 1</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer2">
+ <property name="accessibleName">
+ <string>Player 2</string>
+ </property>
+ <attribute name="title">
+ <string>Player 2</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer3">
+ <property name="accessibleName">
+ <string>Player 3</string>
+ </property>
+ <attribute name="title">
+ <string>Player 3</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer4">
+ <property name="accessibleName">
+ <string>Player 4</string>
+ </property>
+ <attribute name="title">
+ <string>Player 4</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer5">
+ <property name="accessibleName">
+ <string>Player 5</string>
+ </property>
+ <attribute name="title">
+ <string>Player 5</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer6">
+ <property name="accessibleName">
+ <string>Player 6</string>
+ </property>
+ <attribute name="title">
+ <string>Player 6</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer7">
+ <property name="accessibleName">
+ <string>Player 7</string>
+ </property>
+ <attribute name="title">
+ <string>Player 7</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer8">
+ <property name="accessibleName">
+ <string>Player 8</string>
+ </property>
+ <attribute name="title">
+ <string>Player 8</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabAdvanced">
+ <property name="accessibleName">
+ <string>Advanced</string>
+ </property>
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QWidget" name="consoleInputSettings" native="true">
+ <layout class="QHBoxLayout" name="buttonsBottomRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>8</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Undocked</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="vibrationButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QWidget" name="connectedControllers" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_9">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </spacer>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <property name="spacing">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QPushButton" name="buttonRestoreDefaults">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QPushButton" name="buttonClearAll">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
<resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigureInput</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>294</x>
- <y>553</y>
- </hint>
- <hint type="destinationlabel">
- <x>191</x>
- <y>287</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ConfigureInput</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>294</x>
- <y>553</y>
- </hint>
- <hint type="destinationlabel">
- <x>191</x>
- <y>287</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
new file mode 100644
index 000000000..abaf03630
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -0,0 +1,171 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QColorDialog>
+#include "core/core.h"
+#include "core/settings.h"
+#include "ui_configure_input_advanced.h"
+#include "yuzu/configuration/configure_input_advanced.h"
+
+ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()) {
+ ui->setupUi(this);
+
+ controllers_color_buttons = {{
+ {
+ ui->player1_left_body_button,
+ ui->player1_left_buttons_button,
+ ui->player1_right_body_button,
+ ui->player1_right_buttons_button,
+ },
+ {
+ ui->player2_left_body_button,
+ ui->player2_left_buttons_button,
+ ui->player2_right_body_button,
+ ui->player2_right_buttons_button,
+ },
+ {
+ ui->player3_left_body_button,
+ ui->player3_left_buttons_button,
+ ui->player3_right_body_button,
+ ui->player3_right_buttons_button,
+ },
+ {
+ ui->player4_left_body_button,
+ ui->player4_left_buttons_button,
+ ui->player4_right_body_button,
+ ui->player4_right_buttons_button,
+ },
+ {
+ ui->player5_left_body_button,
+ ui->player5_left_buttons_button,
+ ui->player5_right_body_button,
+ ui->player5_right_buttons_button,
+ },
+ {
+ ui->player6_left_body_button,
+ ui->player6_left_buttons_button,
+ ui->player6_right_body_button,
+ ui->player6_right_buttons_button,
+ },
+ {
+ ui->player7_left_body_button,
+ ui->player7_left_buttons_button,
+ ui->player7_right_body_button,
+ ui->player7_right_buttons_button,
+ },
+ {
+ ui->player8_left_body_button,
+ ui->player8_left_buttons_button,
+ ui->player8_right_body_button,
+ ui->player8_right_buttons_button,
+ },
+ }};
+
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& color_buttons = controllers_color_buttons[player_idx];
+ for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
+ connect(color_buttons[button_idx], &QPushButton::clicked, this,
+ [this, player_idx, button_idx] {
+ OnControllerButtonClick(player_idx, button_idx);
+ });
+ }
+ }
+
+ connect(ui->mouse_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+ connect(ui->debug_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+ connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+
+ connect(ui->debug_configure, &QPushButton::clicked, this,
+ [this] { CallDebugControllerDialog(); });
+ connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
+ connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
+ [this] { CallTouchscreenConfigDialog(); });
+ connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
+ &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
+
+ LoadConfiguration();
+}
+
+ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
+
+void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx,
+ std::size_t button_idx) {
+ const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
+ if (!new_bg_color.isValid()) {
+ return;
+ }
+ controllers_colors[player_idx][button_idx] = new_bg_color;
+ controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
+ QStringLiteral("background-color: %1; min-width: 60px;")
+ .arg(controllers_colors[player_idx][button_idx].name()));
+}
+
+void ConfigureInputAdvanced::ApplyConfiguration() {
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& player = Settings::values.players.GetValue()[player_idx];
+ std::array<u32, 4> colors{};
+ std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
+ colors.begin(), [](QColor color) { return color.rgb(); });
+
+ player.body_color_left = colors[0];
+ player.button_color_left = colors[1];
+ player.body_color_right = colors[2];
+ player.button_color_right = colors[3];
+ }
+
+ Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
+ Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
+ Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
+ Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
+}
+
+void ConfigureInputAdvanced::LoadConfiguration() {
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& player = Settings::values.players.GetValue()[player_idx];
+ std::array<u32, 4> colors = {
+ player.body_color_left,
+ player.button_color_left,
+ player.body_color_right,
+ player.button_color_right,
+ };
+
+ std::transform(colors.begin(), colors.end(), controllers_colors[player_idx].begin(),
+ [](u32 rgb) { return QColor::fromRgb(rgb); });
+
+ for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
+ controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
+ QStringLiteral("background-color: %1; min-width: 60px;")
+ .arg(controllers_colors[player_idx][button_idx].name()));
+ }
+ }
+
+ ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
+ ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
+ ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
+ ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
+
+ UpdateUIEnabled();
+}
+
+void ConfigureInputAdvanced::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureInputAdvanced::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureInputAdvanced::UpdateUIEnabled() {
+ ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
+ ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
+ ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
+}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
new file mode 100644
index 000000000..3083d55c1
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QWidget>
+
+class QColor;
+class QPushButton;
+
+namespace Ui {
+class ConfigureInputAdvanced;
+}
+
+class ConfigureInputAdvanced : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputAdvanced(QWidget* parent = nullptr);
+ ~ConfigureInputAdvanced() override;
+
+ void ApplyConfiguration();
+
+signals:
+ void CallDebugControllerDialog();
+ void CallMouseConfigDialog();
+ void CallTouchscreenConfigDialog();
+ void CallMotionTouchConfigDialog();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+ void UpdateUIEnabled();
+
+ void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx);
+
+ void LoadConfiguration();
+
+ std::unique_ptr<Ui::ConfigureInputAdvanced> ui;
+
+ std::array<std::array<QColor, 4>, 8> controllers_colors;
+ std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons;
+};
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
new file mode 100644
index 000000000..a880a7c68
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -0,0 +1,2688 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputAdvanced</class>
+ <widget class="QWidget" name="ConfigureInputAdvanced">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>710</width>
+ <height>580</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Input</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainInputAdvanced" native="true">
+ <layout class="QHBoxLayout" name="main" stretch="1,1">
+ <property name="spacing">
+ <number>9</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="leftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="leftLayout" stretch="0">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="joyconColorsGroup">
+ <property name="title">
+ <string>Joycon Colors</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,1">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topLeftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player12Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player1Group">
+ <property name="title">
+ <string>Player 1</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player1LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_14">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_66">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player1_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_67">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player1_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player1RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_14">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_64">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player1_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_65">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player1_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player2Group">
+ <property name="title">
+ <string>Player 2</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player2LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_15">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_70">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player2_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_71">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player2_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player2RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_15">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_68">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player2_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_69">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player2_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player34Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player3Group">
+ <property name="title">
+ <string>Player 3</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player3LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_16">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_74">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player3_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_75">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player3_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player3RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_16">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_72">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player3_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_73">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player3_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player4Group">
+ <property name="title">
+ <string>Player 4</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player4LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_17">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_78">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player4_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_79">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player4_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player4RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_17">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_76">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player4_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_77">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player4_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomLeftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player56Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player5Group">
+ <property name="title">
+ <string>Player 5</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player5LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_10">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_50">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player5_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_51">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player5_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player5RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_10">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_48">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player5_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_49">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player5_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player6Group">
+ <property name="title">
+ <string>Player 6</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player6LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_11">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_54">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player6_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_55">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player6_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player6RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_11">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_52">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player6_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_53">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player6_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player78Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player7Group">
+ <property name="title">
+ <string>Player 7</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player7LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_12">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_58">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player7_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_59">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player7_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player7RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_12">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_56">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player7_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_57">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player7_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player8Group">
+ <property name="title">
+ <string>Player 8</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player8LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_13">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_62">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player8_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_63">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player8_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player8RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_13">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_60">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player8_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_61">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="player8_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="rightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="rightLayout" stretch="1,1">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topRightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox_3">
+ <property name="title">
+ <string>Other</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="keyboard_enabled">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Keyboard</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="touchscreen_advanced">
+ <property name="text">
+ <string>Advanced</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>76</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="mouse_advanced">
+ <property name="text">
+ <string>Advanced</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="touchscreen_enabled">
+ <property name="text">
+ <string>Touchscreen</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="mouse_enabled">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Mouse</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="motion_touch">
+ <property name="text">
+ <string>Motion / Touch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QPushButton" name="buttonMotionTouch">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="debug_enabled">
+ <property name="text">
+ <string>Debug Controller</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="debug_configure">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomRightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="mainVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 15ac30f12..56ab32a35 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -4,19 +4,28 @@
#include <algorithm>
#include <memory>
+#include <thread>
#include <utility>
-#include <QColorDialog>
#include <QGridLayout>
+#include <QInputDialog>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
-#include "common/assert.h"
#include "common/param_package.h"
+#include "core/core.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "input_common/gcadapter/gc_poller.h"
#include "input_common/main.h"
+#include "input_common/udp/udp.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/util/limitable_input_dialog.h"
const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
ConfigureInputPlayer::analog_sub_buttons{{
@@ -24,20 +33,74 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
"down",
"left",
"right",
- "modifier",
}};
-static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) {
- const int index1 = grid->indexOf(item);
- const int index2 = grid->indexOf(onTopOf);
- int row, column, rowSpan, columnSpan;
- grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan);
- grid->takeAt(index1);
- grid->addWidget(item, row, column, rowSpan, columnSpan);
+namespace {
+
+constexpr std::size_t HANDHELD_INDEX = 8;
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+ bool connected) {
+ Core::System& system{Core::System::GetInstance()};
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ auto& npad =
+ sm.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
+}
+
+/// Maps the controller type combobox index to Controller Type enum
+constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
+ switch (index) {
+ case 0:
+ default:
+ return Settings::ControllerType::ProController;
+ case 1:
+ return Settings::ControllerType::DualJoyconDetached;
+ case 2:
+ return Settings::ControllerType::LeftJoycon;
+ case 3:
+ return Settings::ControllerType::RightJoycon;
+ case 4:
+ return Settings::ControllerType::Handheld;
+ }
+}
+
+/// Maps the Controller Type enum to controller type combobox index
+constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
+ switch (type) {
+ case Settings::ControllerType::ProController:
+ default:
+ return 0;
+ case Settings::ControllerType::DualJoyconDetached:
+ return 1;
+ case Settings::ControllerType::LeftJoycon:
+ return 2;
+ case Settings::ControllerType::RightJoycon:
+ return 3;
+ case Settings::ControllerType::Handheld:
+ return 4;
+ }
}
-static QString GetKeyName(int key_code) {
+QString GetKeyName(int key_code) {
switch (key_code) {
+ case Qt::LeftButton:
+ return QObject::tr("Click 0");
+ case Qt::RightButton:
+ return QObject::tr("Click 1");
+ case Qt::MiddleButton:
+ return QObject::tr("Click 2");
+ case Qt::BackButton:
+ return QObject::tr("Click 3");
+ case Qt::ForwardButton:
+ return QObject::tr("Click 4");
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
@@ -51,18 +114,24 @@ static QString GetKeyName(int key_code) {
}
}
-static void SetAnalogButton(const Common::ParamPackage& input_param,
- Common::ParamPackage& analog_param, const std::string& button_name) {
- if (analog_param.Get("engine", "") != "analog_from_button") {
+void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
+ const std::string& button_name) {
+ // The poller returned a complete axis, so set all the buttons
+ if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
+ analog_param = input_param;
+ return;
+ }
+ // Check if the current configuration has either no engine or an axis binding.
+ // Clears out the old binding and adds one with analog_from_button.
+ if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
analog_param = {
{"engine", "analog_from_button"},
- {"modifier_scale", "0.5"},
};
}
analog_param.Set(button_name, input_param.Serialize());
}
-static QString ButtonToText(const Common::ParamPackage& param) {
+QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
@@ -71,6 +140,28 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
+ if (param.Get("engine", "") == "gcpad") {
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
+ }
+ if (param.Has("button")) {
+ const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
+ return QObject::tr("GC Button %1").arg(button_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
+ if (param.Get("engine", "") == "cemuhookudp") {
+ if (param.Has("pad_index")) {
+ const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
+ return QObject::tr("Motion %1").arg(motion_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -98,7 +189,7 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return QObject::tr("[unknown]");
}
-static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
+QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
@@ -127,24 +218,47 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return {};
}
+ if (param.Get("engine", "") == "gcpad") {
+ if (dir == "modifier") {
+ return QObject::tr("[unused]");
+ }
+
+ if (dir == "left" || dir == "right") {
+ const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
+
+ return QObject::tr("GC Axis %1").arg(axis_x_str);
+ }
+
+ if (dir == "up" || dir == "down") {
+ const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
+
+ return QObject::tr("GC Axis %1").arg(axis_y_str);
+ }
+
+ return {};
+ }
return QObject::tr("[unknown]");
}
-
-ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
- debug(debug), timeout_timer(std::make_unique<QTimer>()),
- poll_timer(std::make_unique<QTimer>()) {
+} // namespace
+
+ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
+ QWidget* bottom_row,
+ InputCommon::InputSubsystem* input_subsystem_,
+ InputProfiles* profiles_, bool debug)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
+ debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
+ timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
+ bottom_row(bottom_row) {
ui->setupUi(this);
+
setFocusPolicy(Qt::ClickFocus);
button_map = {
- ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
- ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
- ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
- ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
- ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown,
- ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown,
- ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
+ ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
+ ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
+ ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
+ ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
+ ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
};
analog_map_buttons = {{
@@ -153,219 +267,292 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ui->buttonLStickDown,
ui->buttonLStickLeft,
ui->buttonLStickRight,
- ui->buttonLStickMod,
},
{
ui->buttonRStickUp,
ui->buttonRStickDown,
ui->buttonRStickLeft,
ui->buttonRStickRight,
- ui->buttonRStickMod,
},
}};
- debug_hidden = {
- ui->buttonSL, ui->labelSL,
- ui->buttonSR, ui->labelSR,
- ui->buttonLStick, ui->labelLStickPressed,
- ui->buttonRStick, ui->labelRStickPressed,
- ui->buttonHome, ui->labelHome,
- ui->buttonScreenshot, ui->labelScreenshot,
+ motion_map = {
+ ui->buttonMotionLeft,
+ ui->buttonMotionRight,
};
- auto layout = Settings::values.players[player_index].type;
- if (debug)
- layout = Settings::ControllerType::DualJoycon;
-
- switch (layout) {
- case Settings::ControllerType::ProController:
- case Settings::ControllerType::DualJoycon:
- layout_hidden = {
- ui->buttonSL,
- ui->labelSL,
- ui->buttonSR,
- ui->labelSR,
- };
- break;
- case Settings::ControllerType::LeftJoycon:
- layout_hidden = {
- ui->right_body_button,
- ui->right_buttons_button,
- ui->right_body_label,
- ui->right_buttons_label,
- ui->buttonR,
- ui->labelR,
- ui->buttonZR,
- ui->labelZR,
- ui->labelHome,
- ui->buttonHome,
- ui->buttonPlus,
- ui->labelPlus,
- ui->RStick,
- ui->faceButtons,
- };
- break;
- case Settings::ControllerType::RightJoycon:
- layout_hidden = {
- ui->left_body_button, ui->left_buttons_button,
- ui->left_body_label, ui->left_buttons_label,
- ui->buttonL, ui->labelL,
- ui->buttonZL, ui->labelZL,
- ui->labelScreenshot, ui->buttonScreenshot,
- ui->buttonMinus, ui->labelMinus,
- ui->LStick, ui->Dpad,
- };
- break;
- }
+ analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
+ analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
+ analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
+ analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod};
+ analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
+ analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
+ analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
+ analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
+
+ const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
+ int default_val, InputCommon::Polling::DeviceType type) {
+ connect(button, &QPushButton::clicked, [=, this] {
+ HandleClick(
+ button,
+ [=, this](Common::ParamPackage params) {
+ // Workaround for ZL & ZR for analog triggers like on XBOX
+ // controllers. Analog triggers (from controllers like the XBOX
+ // controller) would not work due to a different range of their
+ // signals (from 0 to 255 on analog triggers instead of -32768 to
+ // 32768 on analog joysticks). The SDL driver misinterprets analog
+ // triggers as analog joysticks.
+ // TODO: reinterpret the signal range for analog triggers to map the
+ // values correctly. This is required for the correct emulation of
+ // the analog triggers of the GameCube controller.
+ if (button == ui->buttonZL || button == ui->buttonZR) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ }
+ *param = std::move(params);
+ },
+ type);
+ });
+ };
- if (debug || layout == Settings::ControllerType::ProController) {
- ui->controller_color->hide();
- } else {
- if (layout == Settings::ControllerType::LeftJoycon ||
- layout == Settings::ControllerType::RightJoycon) {
- ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0});
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+ auto* const button = button_map[button_id];
- LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad);
- LayerGridElements(ui->buttons, ui->misc, ui->RStick);
- LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons);
- LayerGridElements(ui->buttons, ui->RStick, ui->LStick);
+ if (button == nullptr) {
+ continue;
}
- }
- for (auto* widget : layout_hidden)
- widget->setVisible(false);
+ ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
+ Config::default_buttons[button_id],
+ InputCommon::Polling::DeviceType::Button);
- analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
- analog_map_deadzone = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
- analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
+ button->setContextMenuPolicy(Qt::CustomContextMenu);
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- auto* const button = button_map[button_id];
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
+ }
+
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ auto* const button = motion_map[motion_id];
if (button == nullptr) {
continue;
}
+ ConfigureButtonClick(motion_map[motion_id], &motions_param[motion_id],
+ Config::default_motions[motion_id],
+ InputCommon::Polling::DeviceType::Motion);
+
button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::clicked, [=] {
- HandleClick(button_map[button_id],
- [=](Common::ParamPackage params) {
- // Workaround for ZL & ZR for analog triggers like on XBOX controllors.
- // Analog triggers (from controllers like the XBOX controller) would not
- // work due to a different range of their signals (from 0 to 255 on
- // analog triggers instead of -32768 to 32768 on analog joysticks). The
- // SDL driver misinterprets analog triggers as analog joysticks.
- // TODO: reinterpret the signal range for analog triggers to map the
- // values correctly. This is required for the correct emulation of the
- // analog triggers of the GameCube controller.
- if (button_id == Settings::NativeButton::ZL ||
- button_id == Settings::NativeButton::ZR) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- }
- buttons_param[button_id] = std::move(params);
- },
- InputCommon::Polling::DeviceType::Button);
- });
- connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
- QMenu context_menu;
- context_menu.addAction(tr("Clear"), [&] {
- buttons_param[button_id].Clear();
- button_map[button_id]->setText(tr("[not set]"));
- });
- context_menu.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ motions_param[motion_id].Clear();
+ motion_map[motion_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
+ });
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+
if (analog_button == nullptr) {
continue;
}
- analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(analog_button, &QPushButton::clicked, [=]() {
- HandleClick(analog_map_buttons[analog_id][sub_button_id],
- [=](const Common::ParamPackage& params) {
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- },
- InputCommon::Polling::DeviceType::Button);
+ connect(analog_button, &QPushButton::clicked, [=, this] {
+ if (!map_analog_stick_accepted) {
+ map_analog_stick_accepted =
+ QMessageBox::information(
+ this, tr("Map Analog Stick"),
+ tr("After pressing OK, first move your joystick horizontally, and then "
+ "vertically.\nTo invert the axes, first move your joystick "
+ "vertically, and then horizontally."),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
+ if (!map_analog_stick_accepted) {
+ return;
+ }
+ }
+ HandleClick(
+ analog_map_buttons[analog_id][sub_button_id],
+ [=, this](const Common::ParamPackage& params) {
+ SetAnalogParam(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ },
+ InputCommon::Polling::DeviceType::AnalogPreferred);
});
+
+ analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
+
connect(analog_button, &QPushButton::customContextMenuRequested,
- [=](const QPoint& menu_location) {
+ [=, this](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
- analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ analogs_param[analog_id].Clear();
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
- context_menu.addAction(tr("Restore Default"), [&] {
- Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
- Config::default_analogs[analog_id][sub_button_id])};
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
- analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
- });
context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
menu_location));
});
}
- connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
- if (QMessageBox::information(
- this, tr("Information"),
- tr("After pressing OK, first move your joystick horizontally, "
- "and then vertically."),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
- HandleClick(
- analog_map_stick[analog_id],
- [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
- InputCommon::Polling::DeviceType::Analog);
- }
+
+ // Handle clicks for the modifier buttons as well.
+ connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] {
+ HandleClick(
+ analog_map_modifier_button[analog_id],
+ [=, this](const Common::ParamPackage& params) {
+ analogs_param[analog_id].Set("modifier", params.Serialize());
+ },
+ InputCommon::Polling::DeviceType::Button);
});
- connect(analog_map_deadzone[analog_id], &QSlider::valueChanged, [=] {
- const float deadzone = analog_map_deadzone[analog_id]->value() / 100.0f;
- analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1").arg(deadzone));
- analogs_param[analog_id].Set("deadzone", deadzone);
+
+ analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ analogs_param[analog_id].Set("modifier", "");
+ analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(
+ analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
+ });
+
+ connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
+ [=, this] {
+ const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
+ analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
+ });
+
+ connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
+ const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
+ analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
+ analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
+ });
+
+ connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
+ const auto slider_value = analog_map_modifier_slider[analog_id]->value();
+ analog_map_modifier_label[analog_id]->setText(
+ tr("Modifier Range: %1%").arg(slider_value));
+ analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
});
}
- connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
+ // Player Connected checkbox
+ connect(ui->groupConnectedController, &QGroupBox::toggled,
+ [this](bool checked) { emit Connected(checked); });
+
+ // Set up controller type. Only Player 1 can choose Handheld.
+ ui->comboControllerType->clear();
+
+ QStringList controller_types = {
+ tr("Pro Controller"),
+ tr("Dual Joycons"),
+ tr("Left Joycon"),
+ tr("Right Joycon"),
+ };
+
+ if (player_index == 0) {
+ controller_types.append(tr("Handheld"));
+ connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
+ [this](int index) {
+ emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
+ Settings::ControllerType::Handheld);
+ });
+ }
+
+ if (debug || player_index == 9) {
+ ui->groupConnectedController->setCheckable(false);
+ }
+
+ // The Debug Controller can only choose the Pro Controller.
+ if (debug) {
+ ui->buttonScreenshot->setEnabled(false);
+ ui->buttonHome->setEnabled(false);
+ QStringList debug_controller_types = {
+ tr("Pro Controller"),
+ };
+ ui->comboControllerType->addItems(debug_controller_types);
+ } else {
+ ui->comboControllerType->addItems(controller_types);
+ }
+
+ UpdateControllerIcon();
+ UpdateControllerAvailableButtons();
+ UpdateMotionButtons();
+ connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
+ UpdateControllerIcon();
+ UpdateControllerAvailableButtons();
+ UpdateMotionButtons();
+ });
+
+ connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
+ &ConfigureInputPlayer::UpdateMappingWithDefaults);
+
+ ui->comboDevices->setCurrentIndex(-1);
+
+ ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
+ connect(ui->buttonRefreshDevices, &QPushButton::clicked,
+ [this] { emit RefreshInputDevices(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this] {
Common::ParamPackage params;
+ if (input_subsystem->GetGCButtons()->IsPolling()) {
+ params = input_subsystem->GetGCButtons()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetGCAnalogs()->IsPolling()) {
+ params = input_subsystem->GetGCAnalogs()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetUDPMotions()->IsPolling()) {
+ params = input_subsystem->GetUDPMotions()->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
- if (params.Has("engine")) {
+ if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
}
});
- controller_color_buttons = {
- ui->left_body_button,
- ui->left_buttons_button,
- ui->right_body_button,
- ui->right_buttons_button,
- };
+ UpdateInputProfiles();
- for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) {
- connect(controller_color_buttons[i], &QPushButton::clicked, this,
- [this, i] { OnControllerButtonClick(static_cast<int>(i)); });
- }
+ connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::CreateProfile);
+ connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::DeleteProfile);
+ connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
+ &ConfigureInputPlayer::LoadProfile);
+ connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::SaveProfile);
LoadConfiguration();
- resize(0, 0);
// TODO(wwylele): enable this when we actually emulate it
ui->buttonHome->setEnabled(false);
@@ -374,27 +561,72 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
void ConfigureInputPlayer::ApplyConfiguration() {
- auto& buttons =
- debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
- auto& analogs =
- debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs;
+ auto& player = Settings::values.players.GetValue()[player_index];
+ auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
+ auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
- if (debug)
+ if (debug) {
+ return;
+ }
+
+ auto& motions = player.motions;
+
+ std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+
+ const auto controller_type =
+ GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ const auto player_connected = ui->groupConnectedController->isChecked() &&
+ controller_type != Settings::ControllerType::Handheld;
+
+ if (player.controller_type == controller_type && player.connected == player_connected) {
+ // Set vibration devices in the event that the input device has changed.
+ ConfigureVibration::SetVibrationDevices(player_index);
+ return;
+ }
+
+ // Disconnect the controller first.
+ UpdateController(controller_type, player_index, false);
+
+ player.controller_type = controller_type;
+ player.connected = player_connected;
+
+ ConfigureVibration::SetVibrationDevices(player_index);
+
+ // Handheld
+ if (player_index == 0) {
+ auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ if (controller_type == Settings::ControllerType::Handheld) {
+ handheld = player;
+ }
+ handheld.connected = ui->groupConnectedController->isChecked() &&
+ controller_type == Settings::ControllerType::Handheld;
+ UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
+ }
+
+ if (!player.connected) {
return;
+ }
- std::array<u32, 4> colors{};
- std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(),
- [](QColor color) { return color.rgb(); });
+ // This emulates a delay between disconnecting and reconnecting controllers as some games
+ // do not respond to a change in controller type if it was instantaneous.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(20ms);
- Settings::values.players[player_index].body_color_left = colors[0];
- Settings::values.players[player_index].button_color_left = colors[1];
- Settings::values.players[player_index].body_color_right = colors[2];
- Settings::values.players[player_index].button_color_right = colors[3];
+ UpdateController(controller_type, player_index, player_connected);
+}
+
+void ConfigureInputPlayer::showEvent(QShowEvent* event) {
+ if (bottom_row == nullptr) {
+ return;
+ }
+ QWidget::showEvent(event);
+ ui->main->addWidget(bottom_row);
}
void ConfigureInputPlayer::changeEvent(QEvent* event) {
@@ -402,24 +634,16 @@ void ConfigureInputPlayer::changeEvent(QEvent* event) {
RetranslateUI();
}
- QDialog::changeEvent(event);
+ QWidget::changeEvent(event);
}
void ConfigureInputPlayer::RetranslateUI() {
ui->retranslateUi(this);
- UpdateButtonLabels();
-}
-
-void ConfigureInputPlayer::OnControllerButtonClick(int i) {
- const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);
- if (!new_bg_color.isValid())
- return;
- controller_colors[i] = new_bg_color;
- controller_color_buttons[i]->setStyleSheet(
- QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
+ UpdateUI();
}
void ConfigureInputPlayer::LoadConfiguration() {
+ auto& player = Settings::values.players.GetValue()[player_index];
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@@ -428,66 +652,109 @@ void ConfigureInputPlayer::LoadConfiguration() {
Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
} else {
- std::transform(Settings::values.players[player_index].buttons.begin(),
- Settings::values.players[player_index].buttons.end(), buttons_param.begin(),
+ std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(),
+ [](const std::string& str) { return Common::ParamPackage(str); });
+ std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
- std::transform(Settings::values.players[player_index].analogs.begin(),
- Settings::values.players[player_index].analogs.end(), analogs_param.begin(),
+ std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
}
- UpdateButtonLabels();
+ UpdateUI();
+ UpdateInputDeviceCombobox();
- if (debug)
+ if (debug) {
return;
+ }
- std::array<u32, 4> colors = {
- Settings::values.players[player_index].body_color_left,
- Settings::values.players[player_index].button_color_left,
- Settings::values.players[player_index].body_color_right,
- Settings::values.players[player_index].button_color_right,
- };
+ ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
+ ui->groupConnectedController->setChecked(
+ player.connected ||
+ (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
+}
- std::transform(colors.begin(), colors.end(), controller_colors.begin(),
- [](u32 rgb) { return QColor::fromRgb(rgb); });
+void ConfigureInputPlayer::ConnectPlayer(bool connected) {
+ ui->groupConnectedController->setChecked(connected);
+}
- for (std::size_t i = 0; i < colors.size(); ++i) {
- controller_color_buttons[i]->setStyleSheet(
- QStringLiteral("QPushButton { background-color: %1 }")
- .arg(controller_colors[i].name()));
+void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
+ // Skip input device persistence if "Input Devices" is set to "Any".
+ if (ui->comboDevices->currentIndex() == 0) {
+ UpdateInputDevices();
+ return;
}
-}
-void ConfigureInputPlayer::RestoreDefaults() {
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ // Find the first button that isn't empty.
+ const auto button_param =
+ std::find_if(buttons_param.begin(), buttons_param.end(),
+ [](const Common::ParamPackage param) { return param.Has("engine"); });
+ const bool buttons_empty = button_param == buttons_param.end();
+
+ const auto current_engine = button_param->Get("engine", "");
+ const auto current_guid = button_param->Get("guid", "");
+ const auto current_port = button_param->Get("port", "");
+
+ const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
+
+ UpdateInputDevices();
+
+ if (buttons_empty) {
+ return;
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
- Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
- Config::default_analogs[analog_id][sub_button_id])};
- SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ const bool all_one_device =
+ std::all_of(buttons_param.begin(), buttons_param.end(),
+ [current_engine, current_guid, current_port,
+ is_keyboard_mouse](const Common::ParamPackage param) {
+ if (is_keyboard_mouse) {
+ return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
+ param.Get("engine", "") == "mouse";
+ }
+ return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
+ param.Get("guid", "") == current_guid &&
+ param.Get("port", "") == current_port);
+ });
+
+ if (all_one_device) {
+ if (is_keyboard_mouse) {
+ ui->comboDevices->setCurrentIndex(1);
+ return;
}
+ const auto devices_it = std::find_if(
+ input_devices.begin(), input_devices.end(),
+ [current_engine, current_guid, current_port](const Common::ParamPackage param) {
+ return param.Get("class", "") == current_engine &&
+ param.Get("guid", "") == current_guid &&
+ param.Get("port", "") == current_port;
+ });
+ const int device_index =
+ devices_it != input_devices.end()
+ ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+ : 0;
+ ui->comboDevices->setCurrentIndex(device_index);
+ } else {
+ ui->comboDevices->setCurrentIndex(0);
}
- UpdateButtonLabels();
+}
+
+void ConfigureInputPlayer::RestoreDefaults() {
+ UpdateMappingWithDefaults();
}
void ConfigureInputPlayer::ClearAll() {
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
const auto* const button = button_map[button_id];
- if (button == nullptr || !button->isEnabled()) {
+ if (button == nullptr) {
continue;
}
buttons_param[button_id].Clear();
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
- if (analog_button == nullptr || !analog_button->isEnabled()) {
+ if (analog_button == nullptr) {
continue;
}
@@ -495,16 +762,30 @@ void ConfigureInputPlayer::ClearAll() {
}
}
- UpdateButtonLabels();
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ const auto* const motion_button = motion_map[motion_id];
+ if (motion_button == nullptr) {
+ continue;
+ }
+
+ motions_param[motion_id].Clear();
+ }
+
+ UpdateUI();
+ UpdateInputDevices();
}
-void ConfigureInputPlayer::UpdateButtonLabels() {
- for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
+void ConfigureInputPlayer::UpdateUI() {
+ for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
+ }
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
if (analog_button == nullptr) {
@@ -514,80 +795,329 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
analog_button->setText(
AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
- analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
+ analog_map_modifier_button[analog_id]->setText(
+ ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}));
+
+ const auto deadzone_label = analog_map_deadzone_label[analog_id];
+ const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
+ const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
+ const auto modifier_label = analog_map_modifier_label[analog_id];
+ const auto modifier_slider = analog_map_modifier_slider[analog_id];
+ const auto range_groupbox = analog_map_range_groupbox[analog_id];
+ const auto range_spinbox = analog_map_range_spinbox[analog_id];
+
+ int slider_value;
auto& param = analogs_param[analog_id];
- auto* const analog_deadzone_slider = analog_map_deadzone[analog_id];
- auto* const analog_deadzone_label = analog_map_deadzone_label[analog_id];
+ const bool is_controller =
+ param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad";
- if (param.Has("engine") && param.Get("engine", "") == "sdl") {
+ if (is_controller) {
if (!param.Has("deadzone")) {
param.Set("deadzone", 0.1f);
}
+ slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
+ deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
+ deadzone_slider->setValue(slider_value);
+ if (!param.Has("range")) {
+ param.Set("range", 1.0f);
+ }
+ range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
+ } else {
+ if (!param.Has("modifier_scale")) {
+ param.Set("modifier_scale", 0.5f);
+ }
+ slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
+ modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
+ modifier_slider->setValue(slider_value);
+ }
- analog_deadzone_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100));
- analog_deadzone_slider->setVisible(true);
- analog_deadzone_label->setVisible(true);
+ deadzone_label->setVisible(is_controller);
+ deadzone_slider->setVisible(is_controller);
+ modifier_groupbox->setVisible(!is_controller);
+ modifier_label->setVisible(!is_controller);
+ modifier_slider->setVisible(!is_controller);
+ range_groupbox->setVisible(is_controller);
+ }
+}
+
+void ConfigureInputPlayer::UpdateInputDevices() {
+ input_devices = input_subsystem->GetInputDevices();
+ ui->comboDevices->clear();
+ for (auto device : input_devices) {
+ ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
+ }
+}
+
+void ConfigureInputPlayer::UpdateControllerIcon() {
+ // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
+ // "nonstandard" to use an image through the icon support)
+ const QString stylesheet = [this] {
+ switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ return QStringLiteral("image: url(:/controller/pro_controller%0)");
+ case Settings::ControllerType::DualJoyconDetached:
+ return QStringLiteral("image: url(:/controller/dual_joycon%0)");
+ case Settings::ControllerType::LeftJoycon:
+ return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
+ case Settings::ControllerType::RightJoycon:
+ return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
+ case Settings::ControllerType::Handheld:
+ return QStringLiteral("image: url(:/controller/handheld%0)");
+ default:
+ return QString{};
+ }
+ }();
+
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
} else {
- analog_deadzone_slider->setVisible(false);
- analog_deadzone_label->setVisible(false);
+ return QString{};
}
+ }();
+
+ ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
+}
+
+void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ // List of all the widgets that will be hidden by any of the following layouts that need
+ // "unhidden" after the controller type changes
+ const std::array<QWidget*, 9> layout_show = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonShoulderButtonsLeft,
+ ui->buttonMiscButtonsMinusScreenshot,
+ ui->bottomLeft,
+ ui->buttonShoulderButtonsRight,
+ ui->buttonMiscButtonsPlusHome,
+ ui->bottomRight,
+ };
+
+ for (auto* widget : layout_show) {
+ widget->show();
+ }
+
+ std::vector<QWidget*> layout_hidden;
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ layout_hidden = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ };
+ break;
+ case Settings::ControllerType::LeftJoycon:
+ layout_hidden = {
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonShoulderButtonsRight,
+ ui->buttonMiscButtonsPlusHome,
+ ui->bottomRight,
+ };
+ break;
+ case Settings::ControllerType::RightJoycon:
+ layout_hidden = {
+ ui->horizontalSpacerShoulderButtonsWidget,
+ ui->buttonShoulderButtonsLeft,
+ ui->buttonMiscButtonsMinusScreenshot,
+ ui->bottomLeft,
+ };
+ break;
+ }
+
+ for (auto* widget : layout_hidden) {
+ widget->hide();
}
}
+void ConfigureInputPlayer::UpdateMotionButtons() {
+ if (debug) {
+ // Motion isn't used with the debug controller, hide both groupboxes.
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->hide();
+ return;
+ }
+
+ // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
+ switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::Handheld:
+ // Show "Motion 1" and hide "Motion 2".
+ ui->buttonMotionLeftGroup->show();
+ ui->buttonMotionRightGroup->hide();
+ break;
+ case Settings::ControllerType::RightJoycon:
+ // Show "Motion 2" and hide "Motion 1".
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->show();
+ break;
+ case Settings::ControllerType::DualJoyconDetached:
+ default:
+ // Show both "Motion 1/2".
+ ui->buttonMotionLeftGroup->show();
+ ui->buttonMotionRightGroup->show();
+ break;
+ }
+}
+
+void ConfigureInputPlayer::UpdateMappingWithDefaults() {
+ if (ui->comboDevices->currentIndex() == 0) {
+ return;
+ }
+
+ if (ui->comboDevices->currentIndex() == 1) {
+ // Reset keyboard bindings
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+ Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+ Config::default_analogs[analog_id][sub_button_id])};
+ SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ }
+
+ analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
+ Config::default_stick_mod[analog_id]));
+ }
+
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ motions_param[motion_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
+ }
+
+ UpdateUI();
+ return;
+ }
+
+ // Reset controller bindings
+ const auto& device = input_devices[ui->comboDevices->currentIndex()];
+ auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
+ auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
+ for (std::size_t i = 0; i < buttons_param.size(); ++i) {
+ buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
+ }
+ for (std::size_t i = 0; i < analogs_param.size(); ++i) {
+ analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
+ }
+
+ UpdateUI();
+}
+
void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
- button->setText(tr("[press key]"));
+ if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
+ button->setText(tr("Shake!"));
+ } else {
+ button->setText(tr("[waiting]"));
+ }
button->setFocus();
- // Keyboard keys can only be used as button devices
- want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
- if (want_keyboard_keys) {
- const auto iter = std::find(button_map.begin(), button_map.end(), button);
- ASSERT(iter != button_map.end());
- const auto index = std::distance(button_map.begin(), iter);
- ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
- }
+ // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
+ // controller, then they don't want keyboard/mouse input
+ want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
- device_pollers = InputCommon::Polling::GetPollers(type);
+ device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
- grabKeyboard();
- grabMouse();
- timeout_timer->start(5000); // Cancel after 5 seconds
- poll_timer->start(200); // Check for new inputs every 200ms
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
+ if (type == InputCommon::Polling::DeviceType::Button) {
+ input_subsystem->GetGCButtons()->BeginConfiguration();
+ } else {
+ input_subsystem->GetGCAnalogs()->BeginConfiguration();
+ }
+
+ if (type == InputCommon::Polling::DeviceType::Motion) {
+ input_subsystem->GetUDPMotions()->BeginConfiguration();
+ }
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
- releaseKeyboard();
- releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
+ input_subsystem->GetGCButtons()->EndConfiguration();
+ input_subsystem->GetGCAnalogs()->EndConfiguration();
+
+ input_subsystem->GetUDPMotions()->EndConfiguration();
+
if (!abort) {
(*input_setter)(params);
}
- UpdateButtonLabels();
+ UpdateUI();
+ UpdateInputDeviceCombobox();
+
input_setter = std::nullopt;
}
+bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
+ if (ui->comboDevices->currentIndex() == 0) {
+ return true;
+ }
+
+ // Keyboard/Mouse
+ if (ui->comboDevices->currentIndex() == 1) {
+ return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
+ }
+
+ const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
+ return params.Get("engine", "") == current_input_device.Get("class", "") &&
+ params.Get("guid", "") == current_input_device.Get("guid", "") &&
+ params.Get("port", "") == current_input_device.Get("port", "");
+}
+
+void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ if (want_keyboard_mouse) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
+ false);
+ } else {
+ // We don't want any mouse buttons, so don't stop polling
+ return;
+ }
+
+ SetPollingResult({}, true);
+}
+
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
- if (want_keyboard_keys) {
+ if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
@@ -595,5 +1125,105 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
return;
}
}
+
SetPollingResult({}, true);
}
+
+void ConfigureInputPlayer::CreateProfile() {
+ const auto profile_name =
+ LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) {
+ QMessageBox::critical(this, tr("Create Input Profile"),
+ tr("The given profile name is not valid!"));
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Create Input Profile"),
+ tr("Failed to create the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ emit RefreshInputProfiles(player_index);
+
+ ui->comboProfiles->addItem(profile_name);
+ ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
+}
+
+void ConfigureInputPlayer::DeleteProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ if (!profiles->DeleteProfile(profile_name.toStdString())) {
+ QMessageBox::critical(this, tr("Delete Input Profile"),
+ tr("Failed to delete the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ emit RefreshInputProfiles(player_index);
+
+ ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
+ ui->comboProfiles->setCurrentIndex(-1);
+}
+
+void ConfigureInputPlayer::LoadProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Load Input Profile"),
+ tr("Failed to load the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ LoadConfiguration();
+}
+
+void ConfigureInputPlayer::SaveProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Save Input Profile"),
+ tr("Failed to save the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+}
+
+void ConfigureInputPlayer::UpdateInputProfiles() {
+ ui->comboProfiles->clear();
+
+ for (const auto& profile_name : profiles->GetInputProfileNames()) {
+ ui->comboProfiles->addItem(QString::fromStdString(profile_name));
+ }
+
+ ui->comboProfiles->setCurrentIndex(-1);
+}
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 045704e47..23cf6f958 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -10,16 +10,27 @@
#include <optional>
#include <string>
-#include <QDialog>
+#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
#include "ui_configure_input.h"
+class QCheckBox;
class QKeyEvent;
+class QLabel;
class QPushButton;
+class QSlider;
+class QSpinBox;
class QString;
class QTimer;
+class QWidget;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
namespace InputCommon::Polling {
class DevicePoller;
@@ -30,84 +41,165 @@ namespace Ui {
class ConfigureInputPlayer;
}
-class ConfigureInputPlayer : public QDialog {
+class ConfigureInputPlayer : public QWidget {
Q_OBJECT
public:
- explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false);
+ explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
+ InputCommon::InputSubsystem* input_subsystem_,
+ InputProfiles* profiles_, bool debug = false);
~ConfigureInputPlayer() override;
- /// Save all button configurations to settings file
+ /// Save all button configurations to settings file.
void ApplyConfiguration();
-private:
- void changeEvent(QEvent* event) override;
- void RetranslateUI();
+ /// Set the connection state checkbox (used to sync state).
+ void ConnectPlayer(bool connected);
- void OnControllerButtonClick(int i);
+ /// Update the input devices combobox.
+ void UpdateInputDeviceCombobox();
+
+ /// Updates the list of controller profiles.
+ void UpdateInputProfiles();
- /// Load configuration settings.
- void LoadConfiguration();
/// Restore all buttons to their default values.
void RestoreDefaults();
- /// Clear all input configuration
+
+ /// Clear all input configuration.
void ClearAll();
- /// Update UI to reflect current configuration.
- void UpdateButtonLabels();
+signals:
+ /// Emitted when this controller is connected by the user.
+ void Connected(bool connected);
+ /// Emitted when the Handheld mode is selected (undocked with dual joycons attached).
+ void HandheldStateChanged(bool is_handheld);
+ /// Emitted when the input devices combobox is being refreshed.
+ void RefreshInputDevices();
+ /**
+ * Emitted when the input profiles combobox is being refreshed.
+ * The player_index represents the current player's index, and the profile combobox
+ * will not be updated for this index as they are already updated by other mechanisms.
+ */
+ void RefreshInputProfiles(std::size_t player_index);
+
+protected:
+ void showEvent(QShowEvent* event) override;
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ /// Load configuration settings.
+ void LoadConfiguration();
/// Called when the button was pressed.
void HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
- /// Finish polling and configure input using the input_setter
+ /// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort);
+ /// Checks whether a given input can be accepted.
+ bool IsInputAcceptable(const Common::ParamPackage& params) const;
+
+ /// Handle mouse button press events.
+ void mousePressEvent(QMouseEvent* event) override;
+
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
+ /// Update UI to reflect current configuration.
+ void UpdateUI();
+
+ /// Update the available input devices.
+ void UpdateInputDevices();
+
+ /// Update the current controller icon.
+ void UpdateControllerIcon();
+
+ /// Hides and disables controller settings based on the current controller type.
+ void UpdateControllerAvailableButtons();
+
+ /// Shows or hides motion groupboxes based on the current controller type.
+ void UpdateMotionButtons();
+
+ /// Gets the default controller mapping for this device and auto configures the input to match.
+ void UpdateMappingWithDefaults();
+
+ /// Creates a controller profile.
+ void CreateProfile();
+
+ /// Deletes the selected controller profile.
+ void DeleteProfile();
+
+ /// Loads the selected controller profile.
+ void LoadProfile();
+
+ /// Saves the current controller configuration into a selected controller profile.
+ void SaveProfile();
+
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index;
bool debug;
+ InputCommon::InputSubsystem* input_subsystem;
+
+ InputProfiles* profiles;
+
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
+ static constexpr int PLAYER_COUNT = 8;
+ std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
+
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
+ std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
- static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
+ static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
- std::vector<QWidget*> debug_hidden;
- std::vector<QWidget*> layout_hidden;
-
- /// A group of five QPushButtons represent one analog input. The buttons each represent up,
- /// down, left, right, and modifier, respectively.
+ /// A group of four QPushButtons represent one analog input. The buttons each represent up,
+ /// down, left, right, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
- /// Analog inputs are also represented each with a single button, used to configure with an
- /// actual analog stick
- std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick;
- std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone;
+ /// Each motion input is represented by a QPushButton.
+ std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map;
+
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label;
+ std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider;
+ std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox;
+ std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button;
+ std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label;
+ std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider;
+ std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox;
+ std::array<QSpinBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_spinbox;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
+ /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
+ bool map_analog_stick_accepted{};
+
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
- bool want_keyboard_keys = false;
+ bool want_keyboard_mouse{};
+
+ /// List of physical devices users can map with. If a SDL backed device is selected, then you
+ /// can use this device to get a default mapping.
+ std::vector<Common::ParamPackage> input_devices;
- std::array<QPushButton*, 4> controller_color_buttons;
- std::array<QColor, 4> controller_colors;
+ /// Bottom row is where console wide settings are held, and its "owned" by the parent
+ /// ConfigureInput widget. On show, add this widget to the main layout. This will change the
+ /// parent of the widget to this widget (but thats fine).
+ QWidget* bottom_row;
};
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 4b37746a1..1e78b4c10 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -1,1243 +1,3094 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInputPlayer</class>
- <widget class="QDialog" name="ConfigureInputPlayer">
+ <widget class="QWidget" name="ConfigureInputPlayer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>408</width>
- <height>731</height>
+ <width>780</width>
+ <height>487</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Input</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
- <layout class="QGridLayout" name="buttons">
- <item row="1" column="1">
- <widget class="QGroupBox" name="RStick">
- <property name="title">
- <string>Right Stick</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
- </property>
- <property name="flat">
- <bool>false</bool>
+ <layout class="QVBoxLayout" name="main">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="top" stretch="0,1,2">
+ <property name="spacing">
+ <number>3</number>
</property>
- <property name="checkable">
- <bool>false</bool>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <layout class="QGridLayout" name="gridLayout_5">
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupConnectedController">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="title">
+ <string>Connect Controller</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
<item>
- <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout">
+ <widget class="QComboBox" name="comboControllerType">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
+ </property>
<item>
- <widget class="QLabel" name="labelRStickDown">
- <property name="text">
- <string>Down:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickDown">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickRight">
- <property name="text">
- <string>Right:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickRight">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="QPushButton" name="buttonRStickAnalog">
- <property name="text">
- <string>Set Analog Stick</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickLeft">
- <property name="text">
- <string>Left:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickLeft">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickUp">
- <property name="text">
- <string>Up:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickUp">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0">
- <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickPressed">
- <property name="text">
- <string>Pressed:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStick">
- <property name="text">
- <string/>
- </property>
</widget>
</item>
</layout>
- </item>
- <item row="2" column="1">
- <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout">
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="devicesGroup">
+ <property name="title">
+ <string>Input Device</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
<item>
- <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout">
+ <widget class="QComboBox" name="comboDevices">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
+ </property>
<item>
- <widget class="QLabel" name="labelRStickMod">
- <property name="text">
- <string>Modifier:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Any</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickMod">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="4" column="0" colspan="2">
- <layout class="QVBoxLayout" name="sliderRStickDeadzoneVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickDeadzone">
- <property name="text">
- <string>Deadzone: 0</string>
- </property>
- <property name="alignment">
- <enum>Qt::AlignHCenter</enum>
- </property>
- </widget>
+ <property name="text">
+ <string>Keyboard/Mouse</string>
+ </property>
</item>
- </layout>
+ </widget>
</item>
<item>
- <widget class="QSlider" name="sliderRStickDeadzone">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <widget class="QPushButton" name="buttonRefreshDevices">
+ <property name="minimumSize">
+ <size>
+ <width>21</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>21</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
</property>
</widget>
</item>
</layout>
- </item>
- <item row="5" column="0">
- <spacer name="RStick_verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="profilesGroup">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Profile</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,0,0,0">
+ <property name="spacing">
+ <number>3</number>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
</property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QGroupBox" name="Dpad">
- <property name="title">
- <string>Directional Pad</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadUp">
- <property name="text">
- <string>Up:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadUp">
- <property name="text">
- <string/>
+ <widget class="QComboBox" name="comboProfiles">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
</property>
</widget>
</item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadDown">
- <property name="text">
- <string>Down:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadDown">
+ <widget class="QPushButton" name="buttonProfilesSave">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
<property name="text">
- <string/>
+ <string>Save</string>
</property>
</widget>
</item>
- </layout>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadLeft">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Left:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadLeft">
+ <widget class="QPushButton" name="buttonProfilesNew">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
<property name="text">
- <string/>
+ <string>New</string>
</property>
</widget>
</item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadRight">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Right:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadRight">
+ <widget class="QPushButton" name="buttonProfilesDelete">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
<property name="text">
- <string/>
+ <string>Delete</string>
</property>
</widget>
</item>
</layout>
- </item>
- </layout>
- </widget>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="0" column="0">
- <widget class="QGroupBox" name="faceButtons">
- <property name="title">
- <string>Face Buttons</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <item>
+ <widget class="QFrame" name="bottom">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelA">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
+ <layout class="QHBoxLayout" name="_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinimumSize</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="bottomLeft" native="true">
+ <layout class="QVBoxLayout" name="bottomLeftLayout" stretch="0,0,0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="LStick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Left Stick</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
</property>
- <property name="text">
- <string>A:</string>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonA">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelB">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- <property name="text">
- <string>B:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonB">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelX">
- <property name="text">
- <string>X:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonX">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelY">
- <property name="text">
- <string>Y:</string>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonY">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item row="5" column="0" colspan="2">
- <widget class="QGroupBox" name="controller_color">
- <property name="title">
- <string>Controller Color</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0">
- <item row="0" column="0">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="left_body_label">
- <property name="text">
- <string>Left Body</string>
- </property>
- </widget>
- </item>
- <item row="0" column="6">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="left_buttons_label">
- <property name="minimumSize">
- <size>
- <width>90</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Left Buttons</string>
- </property>
- </widget>
- </item>
- <item row="1" column="5">
- <widget class="QPushButton" name="right_buttons_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <widget class="QLabel" name="right_body_label">
- <property name="text">
- <string>Right Body</string>
- </property>
- </widget>
- </item>
- <item row="1" column="4">
- <widget class="QLabel" name="right_buttons_label">
- <property name="minimumSize">
- <size>
- <width>90</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Right Buttons</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="left_buttons_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QPushButton" name="left_body_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="5">
- <widget class="QPushButton" name="right_body_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="LStick">
- <property name="title">
- <string>Left Stick</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickUp">
- <property name="text">
- <string>Up:</string>
+ <item>
+ <widget class="QWidget" name="buttonLStickUpWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_20">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerLStickUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStickUp">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerLStickUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonLStickLeftRightHorizontaLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStickLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStickRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonLStickDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_22">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerLStickDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStickDown">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerLStickDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonLStickPressedModifierHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickPressedGroup">
+ <property name="title">
+ <string>Pressed</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStick">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Pressed</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickModGroup">
+ <property name="title">
+ <string>Modifier</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonLStickMod">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Modifier</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonLStickRangeGroup">
+ <property name="title">
+ <string>Range</string>
+ </property>
+ <layout class="QHBoxLayout" name="buttonLStickRangeGroupHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="spinboxLStickRange">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="sliderLStickDeadzoneModifierRangeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelLStickDeadzone">
+ <property name="text">
+ <string>Deadzone: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderLStickDeadzone">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="sliderLStickModifierRangeHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelLStickModifierRange">
+ <property name="text">
+ <string>Modifier Range: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderLStickModifierRange">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomLeft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="Dpad">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>D-Pad</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickUp">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickRight">
- <property name="text">
- <string>Right:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickRight">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="4" column="1" colspan="2">
- <widget class="QPushButton" name="buttonLStickAnalog">
- <property name="text">
- <string>Set Analog Stick</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2">
- <item>
- <widget class="QLabel" name="labelLStickLeft">
- <property name="text">
- <string>Left:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickLeft">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="2">
- <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickDown">
- <property name="text">
- <string>Down:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickDown">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="2">
- <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickMod">
- <property name="text">
- <string>Modifier:</string>
+ <property name="bottomMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickMod">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ <item>
+ <widget class="QWidget" name="buttonDpadUpWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_23">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerDpadUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonDpadUp">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerDpadUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonDpadLeftRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonDpadLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonDpadRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonDpadDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_24">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerDpadDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonDpadDown">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerDpadDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomLeft_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="3" column="1">
- <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickPressed">
- <property name="text">
- <string>Pressed:</string>
+ <item>
+ <widget class="QWidget" name="bottomMiddle" native="true">
+ <layout class="QVBoxLayout" stretch="0,0,0">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="shoulderButtons">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsLeft" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsLeftVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsButtonLGroup">
+ <property name="title">
+ <string>L</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonL">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>L</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsButtonZLGroup">
+ <property name="title">
+ <string>ZL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonZL">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>ZL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidgetLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerShoulderButtons1">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonMiscButtonsMinusScreenshot" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsMinusScreenshotVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsMinusGroup">
+ <property name="title">
+ <string>Minus</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonMinus">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Minus</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsScreenshotGroup">
+ <property name="title">
+ <string>Capture</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonScreenshot">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Capture</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonMiscButtonsPlusHome" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsPlusHomeVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsPlusGroup">
+ <property name="title">
+ <string>Plus</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonPlus">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Plus</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsHomeGroup">
+ <property name="title">
+ <string>Home</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonHome">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Home</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget3" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget3Layout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerShoulderButtons2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsRight" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsRightVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsRGroup">
+ <property name="title">
+ <string>R</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonR">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsZRGroup">
+ <property name="title">
+ <string>ZR</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonZR">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>ZR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget2" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget2Layout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerShoulderButtons3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsSLSR" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSLGroup">
+ <property name="title">
+ <string>SL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSL">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSRGroup">
+ <property name="title">
+ <string>SR</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSR">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QFrame" name="controllerFrame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">image: url(:/controller/pro);</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStick">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="5" column="1" colspan="2">
- <layout class="QVBoxLayout" name="sliderLStickDeadzoneVerticalLayout">
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickDeadzone">
- <property name="text">
- <string>Deadzone: 0</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="alignment">
- <enum>Qt::AlignHCenter</enum>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QSlider" name="sliderLStickDeadzone">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="6" column="1">
- <spacer name="LStick_verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QGroupBox" name="shoulderButtons">
- <property name="title">
- <string>Shoulder Buttons</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelL">
- <property name="text">
- <string>L:</string>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="miscButtons">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerMiscButtons1">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonMotionLeftGroup">
+ <property name="title">
+ <string>Motion 1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonMotionLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonMotionRightGroup">
+ <property name="title">
+ <string>Motion 2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonMotionRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerMiscButtons4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelR">
- <property name="text">
- <string>R:</string>
+ <item>
+ <widget class="QWidget" name="bottomRight" native="true">
+ <layout class="QVBoxLayout" name="bottomRightLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="faceButtons">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Face Buttons</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelZL">
- <property name="text">
- <string>ZL:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonZL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelZR">
- <property name="text">
- <string>ZR:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonZR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelSL">
- <property name="text">
- <string>SL:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonSL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="2">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelSR">
- <property name="text">
- <string>SR:</string>
+ <property name="bottomMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonSR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QGroupBox" name="misc">
- <property name="title">
- <string>Misc.</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_6">
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelMinus">
- <property name="text">
- <string>Minus:</string>
+ <item>
+ <widget class="QWidget" name="buttonFaceButtonsBWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerBLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsXGroup">
+ <property name="title">
+ <string>X</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonX">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerBRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonFaceButtonsYAHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsYGroup">
+ <property name="title">
+ <string>Y</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonY">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsAGroup">
+ <property name="title">
+ <string>A</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonA">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>A</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonFaceButtonsXWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerXLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsBWidget_2">
+ <property name="title">
+ <string>B</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonB">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>B</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerXRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomRight">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="RStick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Right Stick</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonMinus">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="1">
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelPlus">
- <property name="text">
- <string>Plus:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonPlus">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelHome">
- <property name="text">
- <string>Home:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonHome">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelScreenshot">
- <property name="text">
- <string>Screen Capture:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- <property name="wordWrap">
- <bool>false</bool>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonScreenshot">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>80</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ <item>
+ <widget class="QWidget" name="buttonRStickUpWidget" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerRStickUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStickUp">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerRStickUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRStickLeftRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStickLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStickRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonRStickDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerRStickDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonRStickDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStickDown">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerRStickDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRStickPressedModifierHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupRStickPressed">
+ <property name="title">
+ <string>Pressed</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStick">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Pressed</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickModGroup">
+ <property name="title">
+ <string>Modifier</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRStickMod">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Modifier</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonRStickRangeGroup">
+ <property name="title">
+ <string>Range</string>
+ </property>
+ <layout class="QHBoxLayout" name="buttonRStickRangeGroupHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="spinboxRStickRange">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="sliderRStickDeadzoneModifierRangeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelRStickDeadzone">
+ <property name="text">
+ <string>Deadzone: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderRStickDeadzone">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="sliderRStickModifierRangeHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelRStickModifierRange">
+ <property name="text">
+ <string>Modifier Range: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderRStickModifierRange">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomRight_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout"/>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QPushButton" name="buttonClearAll">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="sizeIncrement">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Clear All</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRestoreDefaults">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="sizeIncrement">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Restore Defaults</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
- <resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigureInputPlayer</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>371</x>
- <y>730</y>
- </hint>
- <hint type="destinationlabel">
- <x>229</x>
- <y>375</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ConfigureInputPlayer</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>371</x>
- <y>730</y>
- </hint>
- <hint type="destinationlabel">
- <x>229</x>
- <y>375</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <resources>
+ <include location="../../../dist/icons/controller/controller.qrc"/>
+ </resources>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp
new file mode 100644
index 000000000..1f5cfa75b
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp
@@ -0,0 +1,37 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+
+ConfigureInputProfileDialog::ConfigureInputProfileDialog(
+ QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()),
+ profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) {
+ ui->setupUi(this);
+
+ ui->controllerLayout->addWidget(profile_widget);
+
+ connect(ui->clear_all_button, &QPushButton::clicked, this,
+ [this] { profile_widget->ClearAll(); });
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ [this] { profile_widget->RestoreDefaults(); });
+
+ RetranslateUI();
+}
+
+ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default;
+
+void ConfigureInputProfileDialog::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureInputProfileDialog::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h
new file mode 100644
index 000000000..e6386bdbb
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.h
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QPushButton;
+
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureInputProfileDialog;
+}
+
+class ConfigureInputProfileDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputProfileDialog(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles);
+ ~ConfigureInputProfileDialog() override;
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureInputProfileDialog> ui;
+
+ ConfigureInputPlayer* profile_widget;
+};
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui
new file mode 100644
index 000000000..726cf6905
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputProfileDialog</class>
+ <widget class="QDialog" name="ConfigureInputProfileDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>70</width>
+ <height>540</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create Input Profile</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="controllerLayout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="clear_all_button">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restore_defaults_button">
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureInputProfileDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp
deleted file mode 100644
index 0e0e8f113..000000000
--- a/src/yuzu/configuration/configure_input_simple.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <tuple>
-
-#include "ui_configure_input_simple.h"
-#include "yuzu/configuration/configure_input.h"
-#include "yuzu/configuration/configure_input_player.h"
-#include "yuzu/configuration/configure_input_simple.h"
-#include "yuzu/uisettings.h"
-
-namespace {
-
-template <typename Dialog, typename... Args>
-void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
- caller->ApplyConfiguration();
- Dialog dialog(caller, std::forward<Args>(args)...);
-
- const auto res = dialog.exec();
- if (res == QDialog::Accepted) {
- dialog.ApplyConfiguration();
- }
-}
-
-// OnProfileSelect functions should (when applicable):
-// - Set controller types
-// - Set controller enabled
-// - Set docked mode
-// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch)
-//
-// OnProfileSelect function should NOT however:
-// - Reset any button mappings
-// - Open any dialogs
-// - Block in any way
-
-constexpr std::size_t PLAYER_0_INDEX = 0;
-constexpr std::size_t HANDHELD_INDEX = 8;
-
-void HandheldOnProfileSelect() {
- Settings::values.players[HANDHELD_INDEX].connected = true;
- Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon;
-
- for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) {
- Settings::values.players[player].connected = false;
- }
-
- Settings::values.use_docked_mode = false;
- Settings::values.keyboard_enabled = false;
- Settings::values.mouse_enabled = false;
- Settings::values.debug_pad_enabled = false;
- Settings::values.touchscreen.enabled = true;
-}
-
-void DualJoyconsDockedOnProfileSelect() {
- Settings::values.players[PLAYER_0_INDEX].connected = true;
- Settings::values.players[PLAYER_0_INDEX].type = Settings::ControllerType::DualJoycon;
-
- for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) {
- Settings::values.players[player].connected = false;
- }
-
- Settings::values.use_docked_mode = true;
- Settings::values.keyboard_enabled = false;
- Settings::values.mouse_enabled = false;
- Settings::values.debug_pad_enabled = false;
- Settings::values.touchscreen.enabled = true;
-}
-
-// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure
-// is clicked)
-using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>;
-
-constexpr std::array<InputProfile, 3> INPUT_PROFILES{{
- {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect,
- [](ConfigureInputSimple* caller) {
- CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false);
- }},
- {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect,
- [](ConfigureInputSimple* caller) {
- CallConfigureDialog<ConfigureInputPlayer>(caller, PLAYER_0_INDEX, false);
- }},
- {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>},
-}};
-
-} // namespace
-
-void ApplyInputProfileConfiguration(int profile_index) {
- std::get<1>(
- INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))();
-}
-
-ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) {
- ui->setupUi(this);
-
- for (const auto& profile : INPUT_PROFILES) {
- const QString label = tr(std::get<0>(profile));
- ui->profile_combobox->addItem(label, label);
- }
-
- connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureInputSimple::OnSelectProfile);
- connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure);
-
- LoadConfiguration();
-}
-
-ConfigureInputSimple::~ConfigureInputSimple() = default;
-
-void ConfigureInputSimple::ApplyConfiguration() {
- auto index = ui->profile_combobox->currentIndex();
- // Make the stored index for "Custom" very large so that if new profiles are added it
- // doesn't change.
- if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) {
- index = std::numeric_limits<int>::max();
- }
-
- UISettings::values.profile_index = index;
-}
-
-void ConfigureInputSimple::changeEvent(QEvent* event) {
- if (event->type() == QEvent::LanguageChange) {
- RetranslateUI();
- }
-
- QWidget::changeEvent(event);
-}
-
-void ConfigureInputSimple::RetranslateUI() {
- ui->retranslateUi(this);
-}
-
-void ConfigureInputSimple::LoadConfiguration() {
- const auto index = UISettings::values.profile_index;
- if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) {
- ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
- } else {
- ui->profile_combobox->setCurrentIndex(index);
- }
-}
-
-void ConfigureInputSimple::OnSelectProfile(int index) {
- const auto old_docked = Settings::values.use_docked_mode;
- ApplyInputProfileConfiguration(index);
- OnDockedModeChanged(old_docked, Settings::values.use_docked_mode);
-}
-
-void ConfigureInputSimple::OnConfigure() {
- std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this);
-}
diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h
deleted file mode 100644
index bb5050224..000000000
--- a/src/yuzu/configuration/configure_input_simple.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-
-#include <QWidget>
-
-class QPushButton;
-class QString;
-class QTimer;
-
-namespace Ui {
-class ConfigureInputSimple;
-}
-
-// Used by configuration loader to apply a profile if the input is invalid.
-void ApplyInputProfileConfiguration(int profile_index);
-
-class ConfigureInputSimple : public QWidget {
- Q_OBJECT
-
-public:
- explicit ConfigureInputSimple(QWidget* parent = nullptr);
- ~ConfigureInputSimple() override;
-
- /// Save all button configurations to settings file
- void ApplyConfiguration();
-
-private:
- void changeEvent(QEvent* event) override;
- void RetranslateUI();
-
- /// Load configuration settings.
- void LoadConfiguration();
-
- void OnSelectProfile(int index);
- void OnConfigure();
-
- std::unique_ptr<Ui::ConfigureInputSimple> ui;
-};
diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui
deleted file mode 100644
index c4889caa9..000000000
--- a/src/yuzu/configuration/configure_input_simple.ui
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ConfigureInputSimple</class>
- <widget class="QWidget" name="ConfigureInputSimple">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>473</width>
- <height>685</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>ConfigureInputSimple</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="gridGroupBox">
- <property name="title">
- <string>Profile</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="2">
- <widget class="QPushButton" name="profile_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="3">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="profile_combobox">
- <property name="minimumSize">
- <size>
- <width>250</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="0" column="1" colspan="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Choose a controller configuration:</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..170574d9b
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,314 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <QCloseEvent>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include "common/logging/log.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "input_common/udp/client.h"
+#include "input_common/udp/udp.h"
+#include "ui_configure_motion_touch.h"
+#include "yuzu/configuration/configure_motion_touch.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+
+CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
+ const std::string& host, u16 port,
+ u8 pad_index, u16 client_id)
+ : QDialog(parent) {
+ layout = new QVBoxLayout;
+ status_label = new QLabel(tr("Communicating with the server..."));
+ cancel_button = new QPushButton(tr("Cancel"));
+ connect(cancel_button, &QPushButton::clicked, this, [this] {
+ if (!completed) {
+ job->Stop();
+ }
+ accept();
+ });
+ layout->addWidget(status_label);
+ layout->addWidget(cancel_button);
+ setLayout(layout);
+
+ using namespace InputCommon::CemuhookUDP;
+ job = std::make_unique<CalibrationConfigurationJob>(
+ host, port, pad_index, client_id,
+ [this](CalibrationConfigurationJob::Status status) {
+ QString text;
+ switch (status) {
+ case CalibrationConfigurationJob::Status::Ready:
+ text = tr("Touch the top left corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Stage1Completed:
+ text = tr("Now touch the bottom right corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Completed:
+ text = tr("Configuration completed!");
+ break;
+ }
+ QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
+ if (status == CalibrationConfigurationJob::Status::Completed) {
+ QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
+ }
+ },
+ [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
+ completed = true;
+ min_x = min_x_;
+ min_y = min_y_;
+ max_x = max_x_;
+ max_y = max_y_;
+ });
+}
+
+CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
+
+void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
+ status_label->setText(text);
+}
+
+void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
+ cancel_button->setText(text);
+}
+
+constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{
+ {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
+ {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
+}};
+
+constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
+ {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
+ {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
+}};
+
+ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), input_subsystem{input_subsystem_},
+ ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
+ ui->setupUi(this);
+ for (const auto& [provider, name] : MotionProviders) {
+ ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
+ }
+ for (const auto& [provider, name] : TouchProviders) {
+ ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
+ }
+
+ ui->udp_learn_more->setOpenExternalLinks(true);
+ ui->udp_learn_more->setText(
+ tr("<a "
+ "href='https://yuzu-emu.org/wiki/"
+ "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
+ "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureMotionTouch::~ConfigureMotionTouch() = default;
+
+void ConfigureMotionTouch::SetConfiguration() {
+ const Common::ParamPackage motion_param(Settings::values.motion_device);
+ const Common::ParamPackage touch_param(Settings::values.touch_device);
+ const std::string motion_engine = motion_param.Get("engine", "motion_emu");
+ const std::string touch_engine = touch_param.Get("engine", "emu_window");
+
+ ui->motion_provider->setCurrentIndex(
+ ui->motion_provider->findData(QString::fromStdString(motion_engine)));
+ ui->touch_provider->setCurrentIndex(
+ ui->touch_provider->findData(QString::fromStdString(touch_engine)));
+ ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
+ touch_from_button_maps = Settings::values.touch_from_button_maps;
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
+ ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
+
+ min_x = touch_param.Get("min_x", 100);
+ min_y = touch_param.Get("min_y", 50);
+ max_x = touch_param.Get("max_x", 1800);
+ max_y = touch_param.Get("max_y", 850);
+
+ ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
+ ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
+ ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
+}
+
+void ConfigureMotionTouch::UpdateUiDisplay() {
+ const QString motion_engine = ui->motion_provider->currentData().toString();
+ const QString touch_engine = ui->touch_provider->currentData().toString();
+ const QString cemuhook_udp = QStringLiteral("cemuhookudp");
+
+ if (motion_engine == QStringLiteral("motion_emu")) {
+ ui->motion_sensitivity_label->setVisible(true);
+ ui->motion_sensitivity->setVisible(true);
+ } else {
+ ui->motion_sensitivity_label->setVisible(false);
+ ui->motion_sensitivity->setVisible(false);
+ }
+
+ if (touch_engine == cemuhook_udp) {
+ ui->touch_calibration->setVisible(true);
+ ui->touch_calibration_config->setVisible(true);
+ ui->touch_calibration_label->setVisible(true);
+ ui->touch_calibration->setText(
+ QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
+ } else {
+ ui->touch_calibration->setVisible(false);
+ ui->touch_calibration_config->setVisible(false);
+ ui->touch_calibration_label->setVisible(false);
+ }
+
+ if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) {
+ ui->udp_config_group_box->setVisible(true);
+ } else {
+ ui->udp_config_group_box->setVisible(false);
+ }
+}
+
+void ConfigureMotionTouch::ConnectEvents() {
+ connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ [this](int index) { UpdateUiDisplay(); });
+ connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ [this](int index) { UpdateUiDisplay(); });
+ connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
+ connect(ui->touch_calibration_config, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchCalibration);
+ connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchFromButton);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
+ if (CanCloseDialog()) {
+ reject();
+ }
+ });
+}
+
+void ConfigureMotionTouch::OnCemuhookUDPTest() {
+ ui->udp_test->setEnabled(false);
+ ui->udp_test->setText(tr("Testing"));
+ udp_test_in_progress = true;
+ InputCommon::CemuhookUDP::TestCommunication(
+ ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
+ static_cast<u32>(ui->udp_pad_index->currentIndex()), 24872,
+ [this] {
+ LOG_INFO(Frontend, "UDP input test success");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
+ },
+ [this] {
+ LOG_ERROR(Frontend, "UDP input test failed");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
+ });
+}
+
+void ConfigureMotionTouch::OnConfigureTouchCalibration() {
+ ui->touch_calibration_config->setEnabled(false);
+ ui->touch_calibration_config->setText(tr("Configuring"));
+ CalibrationConfigurationDialog dialog(
+ this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
+ static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
+ dialog.exec();
+ if (dialog.completed) {
+ min_x = dialog.min_x;
+ min_y = dialog.min_y;
+ max_x = dialog.max_x;
+ max_y = dialog.max_y;
+ LOG_INFO(Frontend,
+ "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
+ min_x, min_y, max_x, max_y);
+ UpdateUiDisplay();
+ } else {
+ LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
+ }
+ ui->touch_calibration_config->setEnabled(true);
+ ui->touch_calibration_config->setText(tr("Configure"));
+}
+
+void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
+ if (CanCloseDialog()) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
+ udp_test_in_progress = false;
+ if (result) {
+ QMessageBox::information(this, tr("Test Successful"),
+ tr("Successfully received data from the server."));
+ } else {
+ QMessageBox::warning(this, tr("Test Failed"),
+ tr("Could not receive valid data from the server.<br>Please verify "
+ "that the server is set up correctly and "
+ "the address and port are correct."));
+ }
+ ui->udp_test->setEnabled(true);
+ ui->udp_test->setText(tr("Test"));
+}
+
+void ConfigureMotionTouch::OnConfigureTouchFromButton() {
+ ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
+ ui->touch_from_button_map->currentIndex()};
+ if (dialog.exec() != QDialog::Accepted) {
+ return;
+ }
+ touch_from_button_maps = dialog.GetMaps();
+
+ while (ui->touch_from_button_map->count() > 0) {
+ ui->touch_from_button_map->removeItem(0);
+ }
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
+}
+
+bool ConfigureMotionTouch::CanCloseDialog() {
+ if (udp_test_in_progress) {
+ QMessageBox::warning(this, tr("Citra"),
+ tr("UDP Test or calibration configuration is in progress.<br>Please "
+ "wait for them to finish."));
+ return false;
+ }
+ return true;
+}
+
+void ConfigureMotionTouch::ApplyConfiguration() {
+ if (!CanCloseDialog()) {
+ return;
+ }
+
+ std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
+ std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
+
+ Common::ParamPackage motion_param{}, touch_param{};
+ motion_param.Set("engine", std::move(motion_engine));
+ touch_param.Set("engine", std::move(touch_engine));
+
+ if (motion_engine == "motion_emu") {
+ motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
+ }
+
+ if (touch_engine == "cemuhookudp") {
+ touch_param.Set("min_x", min_x);
+ touch_param.Set("min_y", min_y);
+ touch_param.Set("max_x", max_x);
+ touch_param.Set("max_y", max_y);
+ }
+
+ Settings::values.motion_device = motion_param.Serialize();
+ Settings::values.touch_device = touch_param.Serialize();
+ Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
+ Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
+ Settings::values.touch_from_button_maps = touch_from_button_maps;
+ Settings::values.udp_input_address = ui->udp_server->text().toStdString();
+ Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
+ Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
+ input_subsystem->ReloadInputDevices();
+
+ accept();
+}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..3d4b5d659
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,90 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include "common/param_package.h"
+
+class QLabel;
+class QPushButton;
+class QVBoxLayout;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::CemuhookUDP {
+class CalibrationConfigurationJob;
+}
+
+namespace Ui {
+class ConfigureMotionTouch;
+}
+
+/// A dialog for touchpad calibration configuration.
+class CalibrationConfigurationDialog : public QDialog {
+ Q_OBJECT
+public:
+ explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
+ u8 pad_index, u16 client_id);
+ ~CalibrationConfigurationDialog() override;
+
+private:
+ Q_INVOKABLE void UpdateLabelText(const QString& text);
+ Q_INVOKABLE void UpdateButtonText(const QString& text);
+
+ QVBoxLayout* layout;
+ QLabel* status_label;
+ QPushButton* cancel_button;
+ std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
+
+ // Configuration results
+ bool completed{};
+ u16 min_x{};
+ u16 min_y{};
+ u16 max_x{};
+ u16 max_y{};
+
+ friend class ConfigureMotionTouch;
+};
+
+class ConfigureMotionTouch : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
+ ~ConfigureMotionTouch() override;
+
+public slots:
+ void ApplyConfiguration();
+
+private slots:
+ void OnCemuhookUDPTest();
+ void OnConfigureTouchCalibration();
+ void OnConfigureTouchFromButton();
+
+private:
+ void closeEvent(QCloseEvent* event) override;
+ Q_INVOKABLE void ShowUDPTestResult(bool result);
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ bool CanCloseDialog();
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<Ui::ConfigureMotionTouch> ui;
+
+ // Coordinate system of the CemuhookUDP touch provider
+ int min_x{};
+ int min_y{};
+ int max_x{};
+ int max_y{};
+
+ bool udp_test_in_progress{};
+
+ std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
+};
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
new file mode 100644
index 000000000..5b78c5a4b
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureMotionTouch</class>
+ <widget class="QDialog" name="ConfigureMotionTouch">
+ <property name="windowTitle">
+ <string>Configure Motion / Touch</string>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>450</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="motion_group_box">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="motion_provider_label">
+ <property name="text">
+ <string>Motion Provider:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="motion_provider"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="motion_sensitivity_label">
+ <property name="text">
+ <string>Sensitivity:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="motion_sensitivity">
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="decimals">
+ <number>4</number>
+ </property>
+ <property name="minimum">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.001000000000000</double>
+ </property>
+ <property name="value">
+ <double>0.010000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="touch_group_box">
+ <property name="title">
+ <string>Touch</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="touch_provider_label">
+ <property name="text">
+ <string>Touch Provider:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="touch_provider"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="touch_calibration_label">
+ <property name="text">
+ <string>Calibration:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="touch_calibration">
+ <property name="text">
+ <string>(100, 50) - (1800, 850)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_calibration_config">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QCheckBox" name="touch_from_button_checkbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Use button mapping:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="touch_from_button_map"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_from_button_config_btn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="udp_config_group_box">
+ <property name="title">
+ <string>CemuhookUDP Config</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_help">
+ <property name="text">
+ <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_server_label">
+ <property name="text">
+ <string>Server:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_server">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_port_label">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_port">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_pad_index_label">
+ <property name="text">
+ <string>Pad:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="udp_pad_index">
+ <item>
+ <property name="text">
+ <string>Pad 1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 4</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_learn_more">
+ <property name="text">
+ <string>Learn More</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="udp_test">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>167</width>
+ <height>55</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureMotionTouch</receiver>
+ <slot>ApplyConfiguration()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index e0647ea5b..2af3afda8 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -18,6 +18,16 @@
static QString GetKeyName(int key_code) {
switch (key_code) {
+ case Qt::LeftButton:
+ return QObject::tr("Click 0");
+ case Qt::RightButton:
+ return QObject::tr("Click 1");
+ case Qt::MiddleButton:
+ return QObject::tr("Click 2");
+ case Qt::BackButton:
+ return QObject::tr("Click 3");
+ case Qt::ForwardButton:
+ return QObject::tr("Click 4");
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
@@ -66,8 +76,10 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return QObject::tr("[unknown]");
}
-ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()),
+ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent),
+ ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@@ -83,25 +95,29 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
}
button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::clicked, [=] {
+ connect(button, &QPushButton::clicked, [=, this] {
HandleClick(
button_map[button_id],
- [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
+ [=, this](const Common::ParamPackage& params) {
+ buttons_param[button_id] = params;
+ },
InputCommon::Polling::DeviceType::Button);
});
- connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
- QMenu context_menu;
- context_menu.addAction(tr("Clear"), [&] {
- buttons_param[button_id].Clear();
- button_map[button_id]->setText(tr("[not set]"));
- });
- context_menu.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] =
+ Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+ Config::default_mouse_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
}
connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
@@ -184,9 +200,9 @@ void ConfigureMouseAdvanced::HandleClick(
button->setText(tr("[press key]"));
button->setFocus();
- // Keyboard keys can only be used as button devices
- want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
- if (want_keyboard_keys) {
+ // Keyboard keys or mouse buttons can only be used as button devices
+ want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
+ if (want_keyboard_mouse) {
const auto iter = std::find(button_map.begin(), button_map.end(), button);
ASSERT(iter != button_map.end());
const auto index = std::distance(button_map.begin(), iter);
@@ -195,27 +211,29 @@ void ConfigureMouseAdvanced::HandleClick(
input_setter = new_input_setter;
- device_pollers = InputCommon::Polling::GetPollers(type);
+ device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
- grabKeyboard();
- grabMouse();
- timeout_timer->start(5000); // Cancel after 5 seconds
- poll_timer->start(200); // Check for new inputs every 200ms
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
- releaseKeyboard();
- releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
if (!abort) {
(*input_setter)(params);
}
@@ -224,13 +242,29 @@ void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params
input_setter = std::nullopt;
}
+void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ if (want_keyboard_mouse) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
+ false);
+ } else {
+ // We don't want any mouse buttons, so don't stop polling
+ return;
+ }
+
+ SetPollingResult({}, true);
+}
+
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
- if (want_keyboard_keys) {
+ if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h
index 342b82412..65b6fca9a 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.h
+++ b/src/yuzu/configuration/configure_mouse_advanced.h
@@ -8,12 +8,14 @@
#include <optional>
#include <QDialog>
-#include "core/settings.h"
-
class QCheckBox;
class QPushButton;
class QTimer;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureMouseAdvanced;
}
@@ -22,7 +24,7 @@ class ConfigureMouseAdvanced : public QDialog {
Q_OBJECT
public:
- explicit ConfigureMouseAdvanced(QWidget* parent);
+ explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
~ConfigureMouseAdvanced() override;
void ApplyConfiguration();
@@ -49,11 +51,16 @@ private:
/// Finish polling and configure input using the input_setter
void SetPollingResult(const Common::ParamPackage& params, bool abort);
+ /// Handle mouse button press events.
+ void mousePressEvent(QMouseEvent* event) override;
+
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
std::unique_ptr<Ui::ConfigureMouseAdvanced> ui;
+ InputCommon::InputSubsystem* input_subsystem;
+
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
@@ -67,5 +74,5 @@ private:
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
- bool want_keyboard_keys = false;
+ bool want_keyboard_mouse = false;
};
diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui
index 08245ecf0..5b99e1c37 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.ui
+++ b/src/yuzu/configuration/configure_mouse_advanced.ui
@@ -6,13 +6,18 @@
<rect>
<x>0</x>
<y>0</y>
- <width>250</width>
- <height>261</height>
+ <width>310</width>
+ <height>193</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Mouse</string>
</property>
+ <property name="styleSheet">
+ <string notr="true">QPushButton {
+ min-width: 60px;
+}</string>
+ </property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
@@ -20,81 +25,33 @@
<string>Mouse Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="4">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="3">
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item row="3" column="5">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="label_5">
<property name="text">
- <string>Right:</string>
+ <string>Forward:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QPushButton" name="right_button">
+ <widget class="QPushButton" name="forward_button">
<property name="minimumSize">
<size>
- <width>75</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
- <property name="text">
- <string/>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Middle:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="middle_button">
<property name="text">
<string/>
</property>
@@ -123,6 +80,12 @@
</item>
<item>
<widget class="QPushButton" name="back_button">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
@@ -147,7 +110,7 @@
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
- <width>75</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
@@ -158,21 +121,99 @@
</item>
</layout>
</item>
- <item row="3" column="3">
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item row="0" column="3">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
- <widget class="QLabel" name="label_5">
+ <widget class="QLabel" name="label_2">
<property name="text">
- <string>Forward:</string>
+ <string>Middle:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QPushButton" name="forward_button">
+ <widget class="QPushButton" name="middle_button">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="6">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="5">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Right:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="right_button">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
@@ -180,6 +221,32 @@
</item>
</layout>
</item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="4">
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
</item>
@@ -187,15 +254,39 @@
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="buttonClearAll">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
- <string>Clear All</string>
+ <string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
- <string>Restore Defaults</string>
+ <string>Defaults</string>
</property>
</widget>
</item>
@@ -206,21 +297,24 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
+ <width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
</layout>
</widget>
<resources/>
@@ -230,32 +324,12 @@
<signal>accepted()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>124</x>
- <y>266</y>
- </hint>
- <hint type="destinationlabel">
- <x>124</x>
- <y>143</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>124</x>
- <y>266</y>
- </hint>
- <hint type="destinationlabel">
- <x>124</x>
- <y>143</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
new file mode 100644
index 000000000..8eac3bd9d
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -0,0 +1,144 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include <QCheckBox>
+#include <QHeaderView>
+#include <QMenu>
+#include <QStandardItemModel>
+#include <QString>
+#include <QTimer>
+#include <QTreeView>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/core.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/xts_archive.h"
+#include "core/loader/loader.h"
+#include "ui_configure_per_game.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_per_game.h"
+#include "yuzu/uisettings.h"
+#include "yuzu/util/util.h"
+
+ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
+ game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
+ Config::ConfigType::PerGameConfig);
+
+ Settings::SetConfiguringGlobal(false);
+
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+ setWindowTitle(tr("Properties"));
+
+ ui->addonsTab->SetTitleId(title_id);
+
+ scene = new QGraphicsScene;
+ ui->icon_view->setScene(scene);
+
+ LoadConfiguration();
+}
+
+ConfigurePerGame::~ConfigurePerGame() = default;
+
+void ConfigurePerGame::ApplyConfiguration() {
+ ui->addonsTab->ApplyConfiguration();
+ ui->generalTab->ApplyConfiguration();
+ ui->systemTab->ApplyConfiguration();
+ ui->graphicsTab->ApplyConfiguration();
+ ui->graphicsAdvancedTab->ApplyConfiguration();
+ ui->audioTab->ApplyConfiguration();
+
+ Settings::Apply();
+ Settings::LogSettings();
+
+ game_config->Save();
+}
+
+void ConfigurePerGame::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigurePerGame::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) {
+ this->file = std::move(file);
+ LoadConfiguration();
+}
+
+void ConfigurePerGame::LoadConfiguration() {
+ if (file == nullptr) {
+ return;
+ }
+
+ ui->addonsTab->LoadFromFile(file);
+
+ ui->display_title_id->setText(
+ QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
+
+ auto& system = Core::System::GetInstance();
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto loader = Loader::GetLoader(system, file);
+
+ if (control.first != nullptr) {
+ ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
+ ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
+ ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
+ } else {
+ std::string title;
+ if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
+ ui->display_name->setText(QString::fromStdString(title));
+
+ FileSys::NACP nacp;
+ if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
+ ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
+
+ ui->display_version->setText(QStringLiteral("1.0.0"));
+ }
+
+ if (control.second != nullptr) {
+ scene->clear();
+
+ QPixmap map;
+ const auto bytes = control.second->ReadAllBytes();
+ map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
+
+ scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
+ Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ } else {
+ std::vector<u8> bytes;
+ if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
+ scene->clear();
+
+ QPixmap map;
+ map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
+
+ scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
+ Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ }
+ }
+
+ ui->display_filename->setText(QString::fromStdString(file->GetName()));
+
+ ui->display_format->setText(
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
+
+ const auto valueText = ReadableByteSize(file->GetSize());
+ ui->display_size->setText(valueText);
+}
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
new file mode 100644
index 000000000..5f9a08cef
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -0,0 +1,51 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <QDialog>
+#include <QList>
+
+#include "core/file_sys/vfs_types.h"
+#include "yuzu/configuration/config.h"
+
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Ui {
+class ConfigurePerGame;
+}
+
+class ConfigurePerGame : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigurePerGame(QWidget* parent, u64 title_id);
+ ~ConfigurePerGame() override;
+
+ /// Save all button configurations to settings file
+ void ApplyConfiguration();
+
+ void LoadFromFile(FileSys::VirtualFile file);
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void LoadConfiguration();
+
+ std::unique_ptr<Ui::ConfigurePerGame> ui;
+ FileSys::VirtualFile file;
+ u64 title_id;
+
+ QGraphicsScene* scene;
+
+ std::unique_ptr<Config> game_config;
+};
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
new file mode 100644
index 000000000..25975b3b9
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigurePerGame</class>
+ <widget class="QDialog" name="ConfigurePerGame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Info</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGraphicsView" name="icon_view">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>256</width>
+ <height>256</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>256</width>
+ <height>256</height>
+ </size>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="interactive">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="display_size">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="display_version">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Title ID</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="display_title_id">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLineEdit" name="display_filename">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLineEdit" name="display_format">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Filename</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="display_name">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="display_developer">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Format</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Version</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Developer</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="VerticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2"/>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <property name="usesScrollButtons">
+ <bool>true</bool>
+ </property>
+ <property name="documentMode">
+ <bool>false</bool>
+ </property>
+ <property name="tabsClosable">
+ <bool>false</bool>
+ </property>
+ <widget class="ConfigurePerGameAddons" name="addonsTab">
+ <attribute name="title">
+ <string>Add-Ons</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureGeneral" name="generalTab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureSystem" name="systemTab">
+ <attribute name="title">
+ <string>System</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureGraphics" name="graphicsTab">
+ <attribute name="title">
+ <string>Graphics</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
+ <attribute name="title">
+ <string>Adv. Graphics</string>
+ </attribute>
+ </widget>
+ <widget class="ConfigureAudio" name="audioTab">
+ <attribute name="title">
+ <string>Audio</string>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ConfigureGeneral</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_general.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureSystem</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_system.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureAudio</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_audio.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureGraphics</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_graphics.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigureGraphicsAdvanced</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_graphics_advanced.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ConfigurePerGameAddons</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_per_game_addons.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigurePerGame</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigurePerGame</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
new file mode 100644
index 000000000..cdeeec01c
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -0,0 +1,144 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include <QHeaderView>
+#include <QMenu>
+#include <QStandardItemModel>
+#include <QString>
+#include <QTimer>
+#include <QTreeView>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/core.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/xts_archive.h"
+#include "core/loader/loader.h"
+#include "ui_configure_per_game_addons.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_per_game_addons.h"
+#include "yuzu/uisettings.h"
+#include "yuzu/util/util.h"
+
+ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent)
+ : QWidget(parent), ui(new Ui::ConfigurePerGameAddons) {
+ ui->setupUi(this);
+
+ layout = new QVBoxLayout;
+ tree_view = new QTreeView;
+ item_model = new QStandardItemModel(tree_view);
+ tree_view->setModel(item_model);
+ tree_view->setAlternatingRowColors(true);
+ tree_view->setSelectionMode(QHeaderView::SingleSelection);
+ tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+ tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setSortingEnabled(true);
+ tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+ tree_view->setUniformRowHeights(true);
+ tree_view->setContextMenuPolicy(Qt::NoContextMenu);
+
+ item_model->insertColumns(0, 2);
+ item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name"));
+ item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));
+
+ // We must register all custom types with the Qt Automoc system so that we are able to use it
+ // with signals/slots. In this case, QList falls under the umbrella of custom types.
+ qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tree_view);
+
+ ui->scrollArea->setLayout(layout);
+
+ ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+
+ connect(item_model, &QStandardItemModel::itemChanged,
+ [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
+}
+
+ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
+
+void ConfigurePerGameAddons::ApplyConfiguration() {
+ std::vector<std::string> disabled_addons;
+
+ for (const auto& item : list_items) {
+ const auto disabled = item.front()->checkState() == Qt::Unchecked;
+ if (disabled)
+ disabled_addons.push_back(item.front()->text().toStdString());
+ }
+
+ auto current = Settings::values.disabled_addons[title_id];
+ std::sort(disabled_addons.begin(), disabled_addons.end());
+ std::sort(current.begin(), current.end());
+ if (disabled_addons != current) {
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
+ }
+
+ Settings::values.disabled_addons[title_id] = disabled_addons;
+}
+
+void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) {
+ this->file = std::move(file);
+ LoadConfiguration();
+}
+
+void ConfigurePerGameAddons::SetTitleId(u64 id) {
+ this->title_id = id;
+}
+
+void ConfigurePerGameAddons::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigurePerGameAddons::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigurePerGameAddons::LoadConfiguration() {
+ if (file == nullptr) {
+ return;
+ }
+
+ auto& system = Core::System::GetInstance();
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto loader = Loader::GetLoader(system, file);
+
+ FileSys::VirtualFile update_raw;
+ loader->ReadUpdateRaw(update_raw);
+
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+
+ for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
+ const auto name =
+ QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
+
+ auto* const first_item = new QStandardItem;
+ first_item->setText(name);
+ first_item->setCheckable(true);
+
+ const auto patch_disabled =
+ std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
+
+ first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
+
+ list_items.push_back(QList<QStandardItem*>{
+ first_item, new QStandardItem{QString::fromStdString(patch.second)}});
+ item_model->appendRow(list_items.back());
+ }
+
+ tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
+}
diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h
new file mode 100644
index 000000000..a00ec3539
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game_addons.h
@@ -0,0 +1,53 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <QList>
+
+#include "core/file_sys/vfs_types.h"
+
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Ui {
+class ConfigurePerGameAddons;
+}
+
+class ConfigurePerGameAddons : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigurePerGameAddons(QWidget* parent = nullptr);
+ ~ConfigurePerGameAddons() override;
+
+ /// Save all button configurations to settings file
+ void ApplyConfiguration();
+
+ void LoadFromFile(FileSys::VirtualFile file);
+
+ void SetTitleId(u64 id);
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void LoadConfiguration();
+
+ std::unique_ptr<Ui::ConfigurePerGameAddons> ui;
+ FileSys::VirtualFile file;
+ u64 title_id;
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+
+ std::vector<QList<QStandardItem*>> list_items;
+};
diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui
new file mode 100644
index 000000000..aefdebfcd
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game_addons.ui
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigurePerGameAddons</class>
+ <widget class="QWidget" name="ConfigurePerGameAddons">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>380</width>
+ <height>280</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
deleted file mode 100644
index d7f259f12..000000000
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <memory>
-#include <utility>
-
-#include <QHeaderView>
-#include <QMenu>
-#include <QStandardItemModel>
-#include <QString>
-#include <QTimer>
-#include <QTreeView>
-
-#include "common/common_paths.h"
-#include "common/file_util.h"
-#include "core/file_sys/control_metadata.h"
-#include "core/file_sys/patch_manager.h"
-#include "core/file_sys/xts_archive.h"
-#include "core/loader/loader.h"
-#include "ui_configure_per_general.h"
-#include "yuzu/configuration/config.h"
-#include "yuzu/configuration/configure_input.h"
-#include "yuzu/configuration/configure_per_general.h"
-#include "yuzu/uisettings.h"
-#include "yuzu/util/util.h"
-
-ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
-
- ui->setupUi(this);
- setFocusPolicy(Qt::ClickFocus);
- setWindowTitle(tr("Properties"));
-
- layout = new QVBoxLayout;
- tree_view = new QTreeView;
- item_model = new QStandardItemModel(tree_view);
- tree_view->setModel(item_model);
- tree_view->setAlternatingRowColors(true);
- tree_view->setSelectionMode(QHeaderView::SingleSelection);
- tree_view->setSelectionBehavior(QHeaderView::SelectRows);
- tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setSortingEnabled(true);
- tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
- tree_view->setUniformRowHeights(true);
- tree_view->setContextMenuPolicy(Qt::NoContextMenu);
-
- item_model->insertColumns(0, 2);
- item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name"));
- item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));
-
- // We must register all custom types with the Qt Automoc system so that we are able to use it
- // with signals/slots. In this case, QList falls under the umbrells of custom types.
- qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
-
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(0);
- layout->addWidget(tree_view);
-
- ui->scrollArea->setLayout(layout);
-
- scene = new QGraphicsScene;
- ui->icon_view->setScene(scene);
-
- connect(item_model, &QStandardItemModel::itemChanged,
- [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
-
- LoadConfiguration();
-}
-
-ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
-
-void ConfigurePerGameGeneral::ApplyConfiguration() {
- std::vector<std::string> disabled_addons;
-
- for (const auto& item : list_items) {
- const auto disabled = item.front()->checkState() == Qt::Unchecked;
- if (disabled)
- disabled_addons.push_back(item.front()->text().toStdString());
- }
-
- auto current = Settings::values.disabled_addons[title_id];
- std::sort(disabled_addons.begin(), disabled_addons.end());
- std::sort(current.begin(), current.end());
- if (disabled_addons != current) {
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
- "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
- }
-
- Settings::values.disabled_addons[title_id] = disabled_addons;
-}
-
-void ConfigurePerGameGeneral::changeEvent(QEvent* event) {
- if (event->type() == QEvent::LanguageChange) {
- RetranslateUI();
- }
-
- QDialog::changeEvent(event);
-}
-
-void ConfigurePerGameGeneral::RetranslateUI() {
- ui->retranslateUi(this);
-}
-
-void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) {
- this->file = std::move(file);
- LoadConfiguration();
-}
-
-void ConfigurePerGameGeneral::LoadConfiguration() {
- if (file == nullptr) {
- return;
- }
-
- ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id)));
-
- FileSys::PatchManager pm{title_id};
- const auto control = pm.GetControlMetadata();
- const auto loader = Loader::GetLoader(file);
-
- if (control.first != nullptr) {
- ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
- ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
- ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
- } else {
- std::string title;
- if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
- ui->display_name->setText(QString::fromStdString(title));
-
- FileSys::NACP nacp;
- if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
- ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
-
- ui->display_version->setText(QStringLiteral("1.0.0"));
- }
-
- if (control.second != nullptr) {
- scene->clear();
-
- QPixmap map;
- const auto bytes = control.second->ReadAllBytes();
- map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
-
- scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
- Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
- } else {
- std::vector<u8> bytes;
- if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
- scene->clear();
-
- QPixmap map;
- map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
-
- scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
- Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
- }
- }
-
- FileSys::VirtualFile update_raw;
- loader->ReadUpdateRaw(update_raw);
-
- const auto& disabled = Settings::values.disabled_addons[title_id];
-
- for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
- const auto name =
- QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
-
- auto* const first_item = new QStandardItem;
- first_item->setText(name);
- first_item->setCheckable(true);
-
- const auto patch_disabled =
- std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
-
- first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
-
- list_items.push_back(QList<QStandardItem*>{
- first_item, new QStandardItem{QString::fromStdString(patch.second)}});
- item_model->appendRow(list_items.back());
- }
-
- tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
-
- ui->display_filename->setText(QString::fromStdString(file->GetName()));
-
- ui->display_format->setText(
- QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
-
- const auto valueText = ReadableByteSize(file->GetSize());
- ui->display_size->setText(valueText);
-}
diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h
deleted file mode 100644
index a3b2cdeff..000000000
--- a/src/yuzu/configuration/configure_per_general.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include <QDialog>
-#include <QList>
-
-#include "core/file_sys/vfs_types.h"
-
-class QGraphicsScene;
-class QStandardItem;
-class QStandardItemModel;
-class QTreeView;
-class QVBoxLayout;
-
-namespace Ui {
-class ConfigurePerGameGeneral;
-}
-
-class ConfigurePerGameGeneral : public QDialog {
- Q_OBJECT
-
-public:
- explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
- ~ConfigurePerGameGeneral() override;
-
- /// Save all button configurations to settings file
- void ApplyConfiguration();
-
- void LoadFromFile(FileSys::VirtualFile file);
-
-private:
- void changeEvent(QEvent* event) override;
- void RetranslateUI();
-
- void LoadConfiguration();
-
- std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
- FileSys::VirtualFile file;
- u64 title_id;
-
- QVBoxLayout* layout;
- QTreeView* tree_view;
- QStandardItemModel* item_model;
- QGraphicsScene* scene;
-
- std::vector<QList<QStandardItem*>> list_items;
-};
diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui
deleted file mode 100644
index 8fdd96fa4..000000000
--- a/src/yuzu/configuration/configure_per_general.ui
+++ /dev/null
@@ -1,276 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ConfigurePerGameGeneral</class>
- <widget class="QDialog" name="ConfigurePerGameGeneral">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>520</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>ConfigurePerGameGeneral</string>
- </property>
- <layout class="QHBoxLayout" name="HorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="VerticalLayout">
- <item>
- <widget class="QGroupBox" name="GeneralGroupBox">
- <property name="title">
- <string>Info</string>
- </property>
- <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
- <item>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="6" column="1" colspan="2">
- <widget class="QLineEdit" name="display_filename">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="display_name">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Developer</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1" colspan="2">
- <widget class="QLineEdit" name="display_size">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Name</string>
- </property>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Filename</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Version</string>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Format</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="display_version">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QLineEdit" name="display_format">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>Size</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="display_developer">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Title ID</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLineEdit" name="display_title_id">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="2" rowspan="5">
- <widget class="QGraphicsView" name="icon_view">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>128</width>
- <height>128</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>128</width>
- <height>128</height>
- </size>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="sizeAdjustPolicy">
- <enum>QAbstractScrollArea::AdjustToContents</enum>
- </property>
- <property name="interactive">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="PerformanceGroupBox">
- <property name="title">
- <string>Add-Ons</string>
- </property>
- <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
- <item>
- <widget class="QScrollArea" name="scrollArea">
- <property name="widgetResizable">
- <bool>true</bool>
- </property>
- <widget class="QWidget" name="scrollAreaWidgetContents">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>350</width>
- <height>169</height>
- </rect>
- </property>
- </widget>
- </widget>
- </item>
- <item>
- <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigurePerGameGeneral</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>269</x>
- <y>567</y>
- </hint>
- <hint type="destinationlabel">
- <x>269</x>
- <y>294</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ConfigurePerGameGeneral</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>269</x>
- <y>567</y>
- </hint>
- <hint type="destinationlabel">
- <x>269</x>
- <y>294</y>
- </hint>
- </hints>
- </connection>
- </connections>
-</ui>
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index f53423440..6334c4c50 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -34,7 +34,7 @@ constexpr std::array<u8, 107> backup_jpeg{
};
QString GetImagePath(Common::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
@@ -282,7 +282,7 @@ void ConfigureProfileManager::SetUserImage() {
}
const auto raw_path = QString::fromStdString(
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
index 06566e981..0de7a4f0b 100644
--- a/src/yuzu/configuration/configure_service.cpp
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -68,6 +68,7 @@ void ConfigureService::SetConfiguration() {
}
std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
+#ifdef YUZU_ENABLE_BOXCAT
std::optional<std::string> global;
std::map<std::string, Service::BCAT::EventStatus> map;
const auto res = Service::BCAT::Boxcat::GetStatus(global, map);
@@ -105,7 +106,10 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
.arg(QString::fromStdString(key))
.arg(FormatEventStatusString(value));
}
- return {QStringLiteral("Current Boxcat Events"), std::move(out)};
+ return {tr("Current Boxcat Events"), std::move(out)};
+#else
+ return {tr("Current Boxcat Events"), tr("There are currently no events on boxcat.")};
+#endif
}
void ConfigureService::OnBCATImplChanged() {
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index f49cd4c8f..59a58d92c 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -12,8 +12,10 @@
#include "common/assert.h"
#include "common/file_util.h"
#include "core/core.h"
+#include "core/hle/service/time/time.h"
#include "core/settings.h"
#include "ui_configure_system.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
@@ -21,20 +23,25 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
- connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
- ui->rng_seed_edit->setEnabled(checked);
- if (!checked) {
+ connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
+ ui->rng_seed_edit->setEnabled(state == Qt::Checked);
+ if (state != Qt::Checked) {
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
}
});
- connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
- ui->custom_rtc_edit->setEnabled(checked);
- if (!checked) {
+ connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
+ ui->custom_rtc_edit->setEnabled(state == Qt::Checked);
+ if (state != Qt::Checked) {
ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
}
});
+ ui->label_console_id->setVisible(Settings::IsConfiguringGlobal());
+ ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal());
+
+ SetupPerGameUI();
+
SetConfiguration();
}
@@ -54,49 +61,140 @@ void ConfigureSystem::RetranslateUI() {
void ConfigureSystem::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
+ const auto rng_seed =
+ QStringLiteral("%1")
+ .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'})
+ .toUpper();
+ const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or(
+ std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
- ui->combo_language->setCurrentIndex(Settings::values.language_index);
- ui->combo_region->setCurrentIndex(Settings::values.region_index);
- ui->combo_sound->setCurrentIndex(Settings::values.sound_index);
-
- ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
- ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
-
- const auto rng_seed = QStringLiteral("%1")
- .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'})
- .toUpper();
+ ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value());
+ ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() &&
+ Settings::values.rng_seed.UsingGlobal());
ui->rng_seed_edit->setText(rng_seed);
- ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
- ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
-
- const auto rtc_time = Settings::values.custom_rtc.value_or(
- std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
+ ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value());
+ ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() &&
+ Settings::values.rng_seed.UsingGlobal());
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
+
+ if (Settings::IsConfiguringGlobal()) {
+ ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
+ ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
+ ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
+ ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue());
+ } else {
+ ConfigurationShared::SetPerGameSetting(ui->combo_language,
+ &Settings::values.language_index);
+ ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index);
+ ConfigurationShared::SetPerGameSetting(ui->combo_time_zone,
+ &Settings::values.time_zone_index);
+ ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
+
+ ConfigurationShared::SetHighlight(ui->label_language,
+ !Settings::values.language_index.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->label_region,
+ !Settings::values.region_index.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->label_timezone,
+ !Settings::values.time_zone_index.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->label_sound,
+ !Settings::values.sound_index.UsingGlobal());
+ }
}
void ConfigureSystem::ReadSystemSettings() {}
void ConfigureSystem::ApplyConfiguration() {
+ // Allow setting custom RTC even if system is powered on, to allow in-game time to be fast
+ // forwared
+ if (Settings::values.custom_rtc.UsingGlobal()) {
+ if (ui->custom_rtc_checkbox->isChecked()) {
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() +
+ Service::Time::TimeManager::GetExternalTimeZoneOffset()};
+ Core::System::GetInstance().GetTimeManager().UpdateLocalSystemClockTime(posix_time);
+ }
+ } else {
+ Settings::values.custom_rtc.SetValue(std::nullopt);
+ }
+ }
+
if (!enabled) {
return;
}
- Settings::values.language_index = ui->combo_language->currentIndex();
- Settings::values.region_index = ui->combo_region->currentIndex();
- Settings::values.sound_index = ui->combo_sound->currentIndex();
+ if (Settings::IsConfiguringGlobal()) {
+ // Guard if during game and set to game-specific value
+ if (Settings::values.language_index.UsingGlobal()) {
+ Settings::values.language_index.SetValue(ui->combo_language->currentIndex());
+ }
+ if (Settings::values.region_index.UsingGlobal()) {
+ Settings::values.region_index.SetValue(ui->combo_region->currentIndex());
+ }
+ if (Settings::values.time_zone_index.UsingGlobal()) {
+ Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex());
+ }
+ if (Settings::values.sound_index.UsingGlobal()) {
+ Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex());
+ }
- if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16);
+ if (Settings::values.rng_seed.UsingGlobal()) {
+ if (ui->rng_seed_checkbox->isChecked()) {
+ Settings::values.rng_seed.SetValue(
+ ui->rng_seed_edit->text().toULongLong(nullptr, 16));
+ } else {
+ Settings::values.rng_seed.SetValue(std::nullopt);
+ }
+ }
} else {
- Settings::values.rng_seed = std::nullopt;
- }
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index,
+ ui->combo_language);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
+ ui->combo_time_zone);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound);
+
+ switch (use_rng_seed) {
+ case ConfigurationShared::CheckState::On:
+ case ConfigurationShared::CheckState::Off:
+ Settings::values.rng_seed.SetGlobal(false);
+ if (ui->rng_seed_checkbox->isChecked()) {
+ Settings::values.rng_seed.SetValue(
+ ui->rng_seed_edit->text().toULongLong(nullptr, 16));
+ } else {
+ Settings::values.rng_seed.SetValue(std::nullopt);
+ }
+ break;
+ case ConfigurationShared::CheckState::Global:
+ Settings::values.rng_seed.SetGlobal(false);
+ Settings::values.rng_seed.SetValue(std::nullopt);
+ Settings::values.rng_seed.SetGlobal(true);
+ break;
+ case ConfigurationShared::CheckState::Count:
+ break;
+ }
- if (ui->custom_rtc_checkbox->isChecked()) {
- Settings::values.custom_rtc =
- std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
- } else {
- Settings::values.custom_rtc = std::nullopt;
+ switch (use_custom_rtc) {
+ case ConfigurationShared::CheckState::On:
+ case ConfigurationShared::CheckState::Off:
+ Settings::values.custom_rtc.SetGlobal(false);
+ if (ui->custom_rtc_checkbox->isChecked()) {
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
+ } else {
+ Settings::values.custom_rtc.SetValue(std::nullopt);
+ }
+ break;
+ case ConfigurationShared::CheckState::Global:
+ Settings::values.custom_rtc.SetGlobal(false);
+ Settings::values.custom_rtc.SetValue(std::nullopt);
+ Settings::values.custom_rtc.SetGlobal(true);
+ break;
+ case ConfigurationShared::CheckState::Count:
+ break;
+ }
}
Settings::Apply();
@@ -118,3 +216,36 @@ void ConfigureSystem::RefreshConsoleID() {
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
+
+void ConfigureSystem::SetupPerGameUI() {
+ if (Settings::IsConfiguringGlobal()) {
+ ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
+ ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
+ ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
+ ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal());
+ ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal());
+ ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal());
+ ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal());
+ ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal());
+
+ return;
+ }
+
+ ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
+ Settings::values.language_index.GetValue(true));
+ ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
+ Settings::values.region_index.GetValue(true));
+ ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
+ Settings::values.time_zone_index.GetValue(true));
+ ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound,
+ Settings::values.sound_index.GetValue(true));
+
+ ConfigurationShared::SetColoredTristate(
+ ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
+ Settings::values.rng_seed.GetValue().has_value(),
+ Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
+ ConfigurationShared::SetColoredTristate(
+ ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(),
+ Settings::values.custom_rtc.GetValue().has_value(),
+ Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc);
+}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index d8fa2d2cc..fc5cd2945 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -9,6 +9,10 @@
#include <QList>
#include <QWidget>
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
namespace Ui {
class ConfigureSystem;
}
@@ -32,10 +36,16 @@ private:
void RefreshConsoleID();
+ void SetupPerGameUI();
+
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
int language_index = 0;
int region_index = 0;
+ int time_zone_index = 0;
int sound_index = 0;
+
+ ConfigurationShared::CheckState use_rng_seed;
+ ConfigurationShared::CheckState use_custom_rtc;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 4e2c7e76e..53b95658b 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -21,249 +21,494 @@
<property name="title">
<string>System Settings</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0">
- <widget class="QLabel" name="label_sound">
- <property name="text">
- <string>Sound output mode</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_console_id">
- <property name="text">
- <string>Console ID:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="combo_language">
- <property name="toolTip">
- <string>Note: this can be overridden when region setting is auto-select</string>
- </property>
- <item>
- <property name="text">
- <string>Japanese (日本語)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>French (français)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>German (Deutsch)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Italian (italiano)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Spanish (español)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Chinese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Korean (한국어)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Dutch (Nederlands)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Portuguese (português)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Russian (Русский)</string>
- </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_region">
+ <property name="text">
+ <string>Region:</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Taiwanese</string>
- </property>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="combo_time_zone">
+ <item>
+ <property name="text">
+ <string>Auto</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Default</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>CET</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>CST6CDT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Cuba</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>EET</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Egypt</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Eire</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>EST</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>EST5EDT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GB-Eire</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GMT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GMT+0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GMT-0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>GMT0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Greenwich</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Hongkong</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>HST</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Iceland</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Iran</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Israel</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Jamaica</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Japan</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Kwajalein</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Libya</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>MET</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>MST</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>MST7MDT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Navajo</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NZ</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>NZ-CHAT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Poland</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Portugal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>PRC</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>PST8PDT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>ROC</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>ROK</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Singapore</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Turkey</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>UCT</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Universal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>UTC</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>W-SU</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>WET</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Zulu</string>
+ </property>
+ </item>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>British English</string>
- </property>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="combo_region">
+ <item>
+ <property name="text">
+ <string>Japan</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>USA</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Europe</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Australia</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>China</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Korea</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Taiwan</string>
+ </property>
+ </item>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Canadian French</string>
- </property>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_timezone">
+ <property name="text">
+ <string>Time Zone:</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Latin American Spanish</string>
- </property>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="combo_language">
+ <property name="toolTip">
+ <string>Note: this can be overridden when region setting is auto-select</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>Japanese (日本語)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>English</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>French (français)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>German (Deutsch)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Italian (italiano)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Spanish (español)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Chinese</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Korean (한국어)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dutch (Nederlands)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Portuguese (português)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Russian (Русский)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Taiwanese</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>British English</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Canadian French</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Latin American Spanish</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Simplified Chinese</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Traditional Chinese (正體中文)</string>
+ </property>
+ </item>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Simplified Chinese</string>
- </property>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="custom_rtc_checkbox">
+ <property name="text">
+ <string>Custom RTC</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Traditional Chinese (正體中文)</string>
- </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_language">
+ <property name="text">
+ <string>Language</string>
+ </property>
+ </widget>
</item>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_region">
- <property name="text">
- <string>Region:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="combo_region">
- <item>
- <property name="text">
- <string>Japan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>USA</string>
- </property>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="rng_seed_checkbox">
+ <property name="text">
+ <string>RNG Seed</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Europe</string>
- </property>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="combo_sound">
+ <item>
+ <property name="text">
+ <string>Mono</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Stereo</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Surround</string>
+ </property>
+ </item>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Australia</string>
- </property>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_console_id">
+ <property name="text">
+ <string>Console ID:</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>China</string>
- </property>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_sound">
+ <property name="text">
+ <string>Sound output mode</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Korea</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Taiwan</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
- <property name="text">
- <string>RNG Seed</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="combo_sound">
- <item>
- <property name="text">
- <string>Mono</string>
- </property>
+ <item row="5" column="1">
+ <widget class="QDateTimeEdit" name="custom_rtc_edit">
+ <property name="minimumDate">
+ <date>
+ <year>1970</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
+ <property name="displayFormat">
+ <string>d MMM yyyy h:mm:ss AP</string>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Stereo</string>
- </property>
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="rng_seed_edit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>Lucida Console</family>
+ </font>
+ </property>
+ <property name="inputMask">
+ <string notr="true">HHHHHHHH</string>
+ </property>
+ <property name="maxLength">
+ <number>8</number>
+ </property>
+ </widget>
</item>
- <item>
- <property name="text">
- <string>Surround</string>
- </property>
+ <item row="4" column="1">
+ <widget class="QPushButton" name="button_regenerate_console_id">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="text">
+ <string>Regenerate</string>
+ </property>
+ </widget>
</item>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_language">
- <property name="text">
- <string>Language</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QPushButton" name="button_regenerate_console_id">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="layoutDirection">
- <enum>Qt::RightToLeft</enum>
- </property>
- <property name="text">
- <string>Regenerate</string>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="custom_rtc_checkbox">
- <property name="text">
- <string>Custom RTC</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QDateTimeEdit" name="custom_rtc_edit">
- <property name="minimumDate">
- <date>
- <year>1970</year>
- <month>1</month>
- <day>1</day>
- </date>
- </property>
- <property name="displayFormat">
- <string>d MMM yyyy h:mm:ss AP</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLineEdit" name="rng_seed_edit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>Lucida Console</family>
- </font>
- </property>
- <property name="inputMask">
- <string notr="true">HHHHHHHH</string>
- </property>
- <property name="maxLength">
- <number>8</number>
- </property>
- </widget>
+ </layout>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
new file mode 100644
index 000000000..15557e4b8
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,623 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QInputDialog>
+#include <QKeyEvent>
+#include <QMessageBox>
+#include <QMouseEvent>
+#include <QResizeEvent>
+#include <QStandardItemModel>
+#include <QTimer>
+#include "common/param_package.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "ui_configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_widget.h"
+
+static QString GetKeyName(int key_code) {
+ switch (key_code) {
+ case Qt::Key_Shift:
+ return QObject::tr("Shift");
+ case Qt::Key_Control:
+ return QObject::tr("Ctrl");
+ case Qt::Key_Alt:
+ return QObject::tr("Alt");
+ case Qt::Key_Meta:
+ return QString{};
+ default:
+ return QKeySequence(key_code).toString();
+ }
+}
+
+static QString ButtonToText(const Common::ParamPackage& param) {
+ if (!param.Has("engine")) {
+ return QObject::tr("[not set]");
+ }
+
+ if (param.Get("engine", "") == "keyboard") {
+ return GetKeyName(param.Get("code", 0));
+ }
+
+ if (param.Get("engine", "") == "sdl") {
+ if (param.Has("hat")) {
+ const QString hat_str = QString::fromStdString(param.Get("hat", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
+ }
+
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
+ }
+
+ if (param.Has("button")) {
+ const QString button_str = QString::fromStdString(param.Get("button", ""));
+
+ return QObject::tr("Button %1").arg(button_str);
+ }
+
+ return {};
+ }
+
+ return QObject::tr("[unknown]");
+}
+
+ConfigureTouchFromButton::ConfigureTouchFromButton(
+ QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_, const int default_index)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
+ touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
+ timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
+ ui->setupUi(this);
+ binding_list_model = new QStandardItemModel(0, 3, this);
+ binding_list_model->setHorizontalHeaderLabels(
+ {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
+ ui->binding_list->setModel(binding_list_model);
+ ui->bottom_screen->SetCoordLabel(ui->coord_label);
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
+
+void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
+ QWidget::showEvent(ev);
+
+ // width values are not valid in the constructor
+ const int w =
+ ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
+ if (w <= 0) {
+ return;
+ }
+ ui->binding_list->setColumnWidth(0, w);
+ ui->binding_list->setColumnWidth(1, w);
+ ui->binding_list->setColumnWidth(2, w);
+}
+
+void ConfigureTouchFromButton::SetConfiguration() {
+ for (const auto& touch_map : touch_maps) {
+ ui->mapping->addItem(QString::fromStdString(touch_map.name));
+ }
+
+ ui->mapping->setCurrentIndex(selected_index);
+}
+
+void ConfigureTouchFromButton::UpdateUiDisplay() {
+ ui->button_delete->setEnabled(touch_maps.size() > 1);
+ ui->button_delete_bind->setEnabled(false);
+
+ binding_list_model->removeRows(0, binding_list_model->rowCount());
+
+ for (const auto& button_str : touch_maps[selected_index].buttons) {
+ Common::ParamPackage package{button_str};
+ QStandardItem* button = new QStandardItem(ButtonToText(package));
+ button->setData(QString::fromStdString(button_str));
+ button->setEditable(false);
+ QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
+ QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
+ binding_list_model->appendRow({button, xcoord, ycoord});
+
+ const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
+ button->setData(dot, DataRoleDot);
+ }
+}
+
+void ConfigureTouchFromButton::ConnectEvents() {
+ connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
+ SaveCurrentMapping();
+ selected_index = index;
+ UpdateUiDisplay();
+ });
+ connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
+ connect(ui->button_delete, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteMapping);
+ connect(ui->button_rename, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::RenameMapping);
+ connect(ui->button_delete_bind, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteBinding);
+ connect(ui->binding_list, &QTreeView::doubleClicked, this,
+ &ConfigureTouchFromButton::EditBinding);
+ connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
+ &ConfigureTouchFromButton::OnBindingSelection);
+ connect(binding_list_model, &QStandardItemModel::itemChanged, this,
+ &ConfigureTouchFromButton::OnBindingChanged);
+ connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
+ &ConfigureTouchFromButton::OnBindingDeleted);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
+ &ConfigureTouchFromButton::NewBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
+ &ConfigureTouchFromButton::SetActiveBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
+ &ConfigureTouchFromButton::SetCoordinates);
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &ConfigureTouchFromButton::ApplyConfiguration);
+
+ connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
+
+ connect(poll_timer.get(), &QTimer::timeout, [this]() {
+ Common::ParamPackage params;
+ for (auto& poller : device_pollers) {
+ params = poller->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ });
+}
+
+void ConfigureTouchFromButton::SaveCurrentMapping() {
+ auto& map = touch_maps[selected_index];
+ map.buttons.clear();
+ for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
+ const auto bind_str = binding_list_model->index(i, 0)
+ .data(Qt::ItemDataRole::UserRole + 1)
+ .toString()
+ .toStdString();
+ if (bind_str.empty()) {
+ continue;
+ }
+ Common::ParamPackage params{bind_str};
+ if (!params.Has("engine")) {
+ continue;
+ }
+ params.Set("x", binding_list_model->index(i, 1).data().toInt());
+ params.Set("y", binding_list_model->index(i, 2).data().toInt());
+ map.buttons.emplace_back(params.Serialize());
+ }
+}
+
+void ConfigureTouchFromButton::NewMapping() {
+ const QString name =
+ QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
+ if (name.isEmpty()) {
+ return;
+ }
+ touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
+ ui->mapping->addItem(name);
+ ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
+}
+
+void ConfigureTouchFromButton::DeleteMapping() {
+ const auto answer = QMessageBox::question(
+ this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
+ if (answer != QMessageBox::Yes) {
+ return;
+ }
+ const bool blocked = ui->mapping->blockSignals(true);
+ ui->mapping->removeItem(selected_index);
+ ui->mapping->blockSignals(blocked);
+ touch_maps.erase(touch_maps.begin() + selected_index);
+ selected_index = ui->mapping->currentIndex();
+ UpdateUiDisplay();
+}
+
+void ConfigureTouchFromButton::RenameMapping() {
+ const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
+ if (new_name.isEmpty()) {
+ return;
+ }
+ ui->mapping->setItemText(selected_index, new_name);
+ touch_maps[selected_index].name = new_name.toStdString();
+}
+
+void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
+ binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
+
+ input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
+ const bool cancel) {
+ auto* cell = binding_list_model->item(row_index, 0);
+ if (cancel) {
+ if (is_new) {
+ binding_list_model->removeRow(row_index);
+ } else {
+ cell->setText(
+ ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
+ }
+ } else {
+ cell->setText(ButtonToText(params));
+ cell->setData(QString::fromStdString(params.Serialize()));
+ }
+ };
+
+ device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
+
+ for (auto& poller : device_pollers) {
+ poller->Start();
+ }
+
+ grabKeyboard();
+ grabMouse();
+ qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
+ timeout_timer->start(5000); // Cancel after 5 seconds
+ poll_timer->start(200); // Check for new inputs every 200ms
+}
+
+void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
+ auto* button = new QStandardItem();
+ button->setEditable(false);
+ auto* x_coord = new QStandardItem(QString::number(pos.x()));
+ auto* y_coord = new QStandardItem(QString::number(pos.y()));
+
+ const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
+ button->setData(dot_id, DataRoleDot);
+
+ binding_list_model->appendRow({button, x_coord, y_coord});
+ ui->binding_list->setFocus();
+ ui->binding_list->setCurrentIndex(button->index());
+
+ GetButtonInput(binding_list_model->rowCount() - 1, true);
+}
+
+void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
+ if (qi.row() >= 0 && qi.column() == 0) {
+ GetButtonInput(qi.row(), false);
+ }
+}
+
+void ConfigureTouchFromButton::DeleteBinding() {
+ const int row_index = ui->binding_list->currentIndex().row();
+ if (row_index < 0) {
+ return;
+ }
+ ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
+ binding_list_model->removeRow(row_index);
+}
+
+void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
+ const QItemSelection& deselected) {
+ ui->button_delete_bind->setEnabled(!selected.isEmpty());
+ if (!selected.isEmpty()) {
+ const auto dot_data = selected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt());
+ }
+ }
+ if (!deselected.isEmpty()) {
+ const auto dot_data = deselected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
+ }
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
+ if (item->column() == 0) {
+ return;
+ }
+
+ const bool blocked = binding_list_model->blockSignals(true);
+ item->setText(QString::number(
+ std::clamp(item->text().toInt(), 0,
+ static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
+ : Layout::ScreenUndocked::Height) -
+ 1))));
+ binding_list_model->blockSignals(blocked);
+
+ const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->MoveDot(dot_data.toInt(),
+ binding_list_model->item(item->row(), 1)->text().toInt(),
+ binding_list_model->item(item->row(), 2)->text().toInt());
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
+ for (int i = first; i <= last; ++i) {
+ const auto ix = binding_list_model->index(i, 0);
+ if (!ix.isValid()) {
+ return;
+ }
+ const auto dot_data = ix.data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->RemoveDot(dot_data.toInt());
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
+ ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
+ ui->binding_list->setFocus();
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
+ binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
+ binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
+ const bool cancel) {
+ releaseKeyboard();
+ releaseMouse();
+ qApp->restoreOverrideCursor();
+ timeout_timer->stop();
+ poll_timer->stop();
+ for (auto& poller : device_pollers) {
+ poller->Stop();
+ }
+ if (input_setter) {
+ (*input_setter)(params, cancel);
+ input_setter.reset();
+ }
+}
+
+void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
+ if (!input_setter && event->key() == Qt::Key_Delete) {
+ DeleteBinding();
+ return;
+ }
+
+ if (!input_setter) {
+ return QDialog::keyPressEvent(event);
+ }
+
+ if (event->key() != Qt::Key_Escape) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ false);
+ } else {
+ SetPollingResult({}, true);
+ }
+}
+
+void ConfigureTouchFromButton::ApplyConfiguration() {
+ SaveCurrentMapping();
+ accept();
+}
+
+int ConfigureTouchFromButton::GetSelectedIndex() const {
+ return selected_index;
+}
+
+std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
+ return touch_maps;
+}
+
+TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
+ setBackgroundRole(QPalette::ColorRole::Base);
+}
+
+TouchScreenPreview::~TouchScreenPreview() = default;
+
+void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
+ coord_label = label;
+}
+
+int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
+ QFont dot_font{QStringLiteral("monospace")};
+ dot_font.setStyleHint(QFont::Monospace);
+ dot_font.setPointSize(20);
+
+ auto* dot = new QLabel(this);
+ dot->setAttribute(Qt::WA_TranslucentBackground);
+ dot->setFont(dot_font);
+ dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
+ dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
+ dot->setProperty(PropId, ++max_dot_id);
+ dot->setProperty(PropX, device_x);
+ dot->setProperty(PropY, device_y);
+ dot->setCursor(Qt::CursorShape::PointingHandCursor);
+ dot->setMouseTracking(true);
+ dot->installEventFilter(this);
+ dot->show();
+ PositionDot(dot, device_x, device_y);
+ dots.emplace_back(max_dot_id, dot);
+ return max_dot_id;
+}
+
+void TouchScreenPreview::RemoveDot(const int id) {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->deleteLater();
+ dots.erase(iter);
+}
+
+void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
+ for (const auto& dot : dots) {
+ if (dot.first == id) {
+ // use color property from the stylesheet, or fall back to the default palette
+ if (dot_highlight_color.isValid()) {
+ dot.second->setStyleSheet(
+ active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
+ : QString{});
+ } else {
+ dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
+ : QPalette::ColorRole::NoRole);
+ }
+ if (active) {
+ dot.second->raise();
+ }
+ return;
+ }
+ }
+}
+
+void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->setProperty(PropX, device_x);
+ iter->second->setProperty(PropY, device_y);
+ PositionDot(iter->second, device_x, device_y);
+}
+
+void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
+ if (ignore_resize) {
+ return;
+ }
+
+ const int target_width = std::min(width(), height() * 4 / 3);
+ const int target_height = std::min(height(), width() * 3 / 4);
+ if (target_width == width() && target_height == height()) {
+ return;
+ }
+ ignore_resize = true;
+ setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
+ target_height);
+ ignore_resize = false;
+
+ if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
+ for (const auto& dot : dots) {
+ PositionDot(dot.second);
+ }
+ }
+}
+
+void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
+ if (!coord_label) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
+ } else {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::leaveEvent(QEvent* event) {
+ if (coord_label) {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
+ if (event->button() != Qt::MouseButton::LeftButton) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ emit DotAdded(*pos);
+ }
+}
+
+bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::Type::MouseButtonPress: {
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (mouse_event->button() != Qt::MouseButton::LeftButton) {
+ break;
+ }
+ emit DotSelected(obj->property(PropId).toInt());
+
+ drag_state.dot = qobject_cast<QLabel*>(obj);
+ drag_state.start_pos = mouse_event->globalPos();
+ return true;
+ }
+ case QEvent::Type::MouseMove: {
+ if (!drag_state.dot) {
+ break;
+ }
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (!drag_state.active) {
+ drag_state.active =
+ (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
+ QApplication::startDragDistance();
+ if (!drag_state.active) {
+ break;
+ }
+ }
+ auto current_pos = mapFromGlobal(mouse_event->globalPos());
+ current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
+ contentsMargins().left() + contentsRect().width() - 1));
+ current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
+ contentsMargins().top() + contentsRect().height() - 1));
+ const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
+ if (device_coord) {
+ drag_state.dot->setProperty(PropX, device_coord->x());
+ drag_state.dot->setProperty(PropY, device_coord->y());
+ PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
+ emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
+ if (coord_label) {
+ coord_label->setText(
+ QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
+ }
+ }
+ return true;
+ }
+ case QEvent::Type::MouseButtonRelease: {
+ drag_state.dot.clear();
+ drag_state.active = false;
+ return true;
+ }
+ default:
+ break;
+ }
+ return obj->eventFilter(obj, event);
+}
+
+std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
+ const int screen_y) const {
+ const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
+ (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
+ const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
+ (Layout::ScreenUndocked::Height - 1) /
+ (contentsRect().height() - 1);
+ if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
+ t_y < Layout::ScreenUndocked::Height) {
+
+ return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
+ }
+ return std::nullopt;
+}
+
+void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
+ const int device_y) const {
+ const float device_coord_x =
+ static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
+ int x_coord = static_cast<int>(
+ device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
+ contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
+
+ const float device_coord_y =
+ static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
+ const int y_coord = static_cast<int>(
+ device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
+ contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
+
+ dot->move(x_coord, y_coord);
+}
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
new file mode 100644
index 000000000..d9513e3bc
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,92 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <optional>
+#include <vector>
+#include <QDialog>
+
+class QItemSelection;
+class QModelIndex;
+class QStandardItemModel;
+class QStandardItem;
+class QTimer;
+
+namespace Common {
+class ParamPackage;
+}
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::Polling {
+class DevicePoller;
+}
+
+namespace Settings {
+struct TouchFromButtonMap;
+}
+
+namespace Ui {
+class ConfigureTouchFromButton;
+}
+
+class ConfigureTouchFromButton : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureTouchFromButton(QWidget* parent,
+ const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_,
+ int default_index = 0);
+ ~ConfigureTouchFromButton() override;
+
+ int GetSelectedIndex() const;
+ std::vector<Settings::TouchFromButtonMap> GetMaps() const;
+
+public slots:
+ void ApplyConfiguration();
+ void NewBinding(const QPoint& pos);
+ void SetActiveBinding(int dot_id);
+ void SetCoordinates(int dot_id, const QPoint& pos);
+
+protected:
+ void showEvent(QShowEvent* ev) override;
+ void keyPressEvent(QKeyEvent* event) override;
+
+private slots:
+ void NewMapping();
+ void DeleteMapping();
+ void RenameMapping();
+ void EditBinding(const QModelIndex& qi);
+ void DeleteBinding();
+ void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
+ void OnBindingChanged(QStandardItem* item);
+ void OnBindingDeleted(const QModelIndex& parent, int first, int last);
+
+private:
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ void GetButtonInput(int row_index, bool is_new);
+ void SetPollingResult(const Common::ParamPackage& params, bool cancel);
+ void SaveCurrentMapping();
+
+ std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
+ std::vector<Settings::TouchFromButtonMap> touch_maps;
+ QStandardItemModel* binding_list_model;
+ InputCommon::InputSubsystem* input_subsystem;
+ int selected_index;
+
+ std::unique_ptr<QTimer> timeout_timer;
+ std::unique_ptr<QTimer> poll_timer;
+ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
+ std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
+
+ static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
+};
diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui
new file mode 100644
index 000000000..757219d54
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.ui
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureTouchFromButton</class>
+ <widget class="QDialog" name="ConfigureTouchFromButton">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Touchscreen Mappings</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Mapping:</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="mapping">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_new">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_rename">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete_bind">
+ <property name="text">
+ <string>Delete Point</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="binding_list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="TouchScreenPreview" name="bottom_screen">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>120</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>CrossCursor</cursorShape>
+ </property>
+ <property name="mouseTracking">
+ <bool>true</bool>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="coord_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>TouchScreenPreview</class>
+ <extends>QFrame</extends>
+ <header>yuzu/configuration/configure_touch_widget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureTouchFromButton</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
new file mode 100644
index 000000000..347b46583
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,62 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <utility>
+#include <vector>
+#include <QFrame>
+#include <QPointer>
+
+class QLabel;
+
+// Widget for representing touchscreen coordinates
+class TouchScreenPreview : public QFrame {
+ Q_OBJECT
+ Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
+
+public:
+ explicit TouchScreenPreview(QWidget* parent);
+ ~TouchScreenPreview() override;
+
+ void SetCoordLabel(QLabel*);
+ int AddDot(int device_x, int device_y);
+ void RemoveDot(int id);
+ void HighlightDot(int id, bool active = true) const;
+ void MoveDot(int id, int device_x, int device_y) const;
+
+signals:
+ void DotAdded(const QPoint& pos);
+ void DotSelected(int dot_id);
+ void DotMoved(int dot_id, const QPoint& pos);
+
+protected:
+ void resizeEvent(QResizeEvent*) override;
+ void mouseMoveEvent(QMouseEvent*) override;
+ void leaveEvent(QEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ bool eventFilter(QObject*, QEvent*) override;
+
+private:
+ std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
+ void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
+
+ bool ignore_resize = false;
+ QPointer<QLabel> coord_label;
+
+ std::vector<std::pair<int, QLabel*>> dots;
+ int max_dot_id = 0;
+ QColor dot_highlight_color;
+ static constexpr char PropId[] = "dot_id";
+ static constexpr char PropX[] = "device_x";
+ static constexpr char PropY[] = "device_y";
+
+ struct DragState {
+ bool active = false;
+ QPointer<QLabel> dot;
+ QPoint start_pos;
+ };
+ DragState drag_state;
+};
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui
index 1171c2dd1..30ceccddb 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.ui
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui
@@ -168,32 +168,12 @@
<signal>accepted()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
- </connection>
+ </connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 94424ee44..dbe3f78c8 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,8 +4,11 @@
#include <array>
#include <utility>
+#include <QFileDialog>
+#include <QDirIterator>
#include "common/common_types.h"
+#include "common/file_util.h"
#include "core/settings.h"
#include "ui_configure_ui.h"
#include "yuzu/configuration/configure_ui.h"
@@ -29,6 +32,8 @@ constexpr std::array row_text_names{
ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
ui->setupUi(this);
+ InitializeLanguageComboBox();
+
for (const auto& theme : UISettings::themes) {
ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
QString::fromUtf8(theme.second));
@@ -49,9 +54,21 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur
// Update text ComboBoxes after user interaction.
connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
- [=]() { ConfigureUi::UpdateSecondRowComboBox(); });
+ [this] { ConfigureUi::UpdateSecondRowComboBox(); });
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
- [=]() { ConfigureUi::UpdateFirstRowComboBox(); });
+ [this] { ConfigureUi::UpdateFirstRowComboBox(); });
+
+ // Set screenshot path to user specification.
+ connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] {
+ const QString& filename =
+ QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."),
+ QString::fromStdString(Common::FS::GetUserPath(
+ Common::FS::UserPath::ScreenshotsDir))) +
+ QDir::separator();
+ if (!filename.isEmpty()) {
+ ui->screenshot_path_edit->setText(filename);
+ }
+ });
}
ConfigureUi::~ConfigureUi() = default;
@@ -63,6 +80,10 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
+
+ UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
+ Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir,
+ ui->screenshot_path_edit->text().toStdString());
Settings::Apply();
}
@@ -72,9 +93,15 @@ void ConfigureUi::RequestGameListUpdate() {
void ConfigureUi::SetConfiguration() {
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
+ ui->language_combobox->setCurrentIndex(
+ ui->language_combobox->findData(UISettings::values.language));
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
ui->icon_size_combobox->setCurrentIndex(
ui->icon_size_combobox->findData(UISettings::values.icon_size));
+
+ ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as);
+ ui->screenshot_path_edit->setText(
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)));
}
void ConfigureUi::changeEvent(QEvent* event) {
@@ -100,6 +127,25 @@ void ConfigureUi::RetranslateUI() {
}
}
+void ConfigureUi::InitializeLanguageComboBox() {
+ ui->language_combobox->addItem(tr("<System>"), QString{});
+ ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
+ QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
+ while (it.hasNext()) {
+ QString locale = it.next();
+ locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
+ locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
+ const QString lang = QLocale::languageToString(QLocale(locale).language());
+ ui->language_combobox->addItem(lang, locale);
+ }
+
+ // Unlike other configuration changes, interface language changes need to be reflected on the
+ // interface immediately. This is done by passing a signal to the main window, and then
+ // retranslating when passing back.
+ connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+ &ConfigureUi::OnLanguageChanged);
+}
+
void ConfigureUi::InitializeIconSizeComboBox() {
for (const auto& size : default_icon_sizes) {
ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
@@ -147,3 +193,10 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) {
ui->row_2_text_combobox->removeItem(
ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));
}
+
+void ConfigureUi::OnLanguageChanged(int index) {
+ if (index == -1)
+ return;
+
+ emit LanguageChanged(ui->language_combobox->itemData(index).toString());
+}
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index d471afe99..c30bcf6ff 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -20,6 +20,12 @@ public:
void ApplyConfiguration();
+private slots:
+ void OnLanguageChanged(int index);
+
+signals:
+ void LanguageChanged(const QString& locale);
+
private:
void RequestGameListUpdate();
@@ -28,6 +34,7 @@ private:
void changeEvent(QEvent*) override;
void RetranslateUI();
+ void InitializeLanguageComboBox();
void InitializeIconSizeComboBox();
void InitializeRowComboBoxes();
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index bd5c5d3c2..d895b799f 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -6,119 +6,180 @@
<rect>
<x>0</x>
<y>0</y>
- <width>300</width>
- <height>377</height>
+ <width>363</width>
+ <height>391</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QHBoxLayout" name="HorizontalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout">
<item>
- <layout class="QVBoxLayout" name="VerticalLayout">
- <item>
- <widget class="QGroupBox" name="GeneralGroupBox">
- <property name="title">
- <string>General</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <widget class="QGroupBox" name="general_groupBox">
+ <property name="title">
+ <string>General</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <widget class="QLabel" name="label_change_language_info">
+ <property name="text">
+ <string>Note: Changing language will apply your configuration.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="language_label">
+ <property name="text">
+ <string>Interface language:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="language_combobox"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="theme_label">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QLabel" name="theme_label">
- <property name="text">
- <string>Theme:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="theme_combobox"/>
- </item>
- </layout>
+ <widget class="QComboBox" name="theme_combobox"/>
</item>
</layout>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="GameListGroupBox">
- <property name="title">
- <string>Game List</string>
- </property>
- <layout class="QHBoxLayout" name="GameListHorizontalLayout">
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="GameListGroupBox">
+ <property name="title">
+ <string>Game List</string>
+ </property>
+ <layout class="QHBoxLayout" name="GameListHorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
- <layout class="QVBoxLayout" name="GeneralVerticalLayout">
+ <widget class="QCheckBox" name="show_add_ons">
+ <property name="text">
+ <string>Show Add-Ons Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
<item>
- <widget class="QCheckBox" name="show_add_ons">
+ <widget class="QLabel" name="icon_size_label">
<property name="text">
- <string>Show Add-Ons Column</string>
+ <string>Icon Size:</string>
</property>
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
- <item>
- <widget class="QLabel" name="icon_size_label">
- <property name="text">
- <string>Icon Size:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="icon_size_combobox"/>
- </item>
- </layout>
+ <widget class="QComboBox" name="icon_size_combobox"/>
</item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="row_1_qhbox_layout">
<item>
- <layout class="QHBoxLayout" name="row_1_qhbox_layout">
- <item>
- <widget class="QLabel" name="row_1_label">
- <property name="text">
- <string>Row 1 Text:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="row_1_text_combobox"/>
- </item>
- </layout>
+ <widget class="QLabel" name="row_1_label">
+ <property name="text">
+ <string>Row 1 Text:</string>
+ </property>
+ </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="row_2_qhbox_layout">
- <item>
- <widget class="QLabel" name="row_2_label">
- <property name="text">
- <string>Row 2 Text:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="row_2_text_combobox"/>
- </item>
- </layout>
+ <widget class="QComboBox" name="row_1_text_combobox"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="row_2_qhbox_layout">
+ <item>
+ <widget class="QLabel" name="row_2_label">
+ <property name="text">
+ <string>Row 2 Text:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="row_2_text_combobox"/>
</item>
</layout>
</item>
</layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="screenshots_GroupBox">
+ <property name="title">
+ <string>Screenshots</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QCheckBox" name="enable_screenshot_save_as">
+ <property name="text">
+ <string>Ask Where To Save Screenshots (Windows Only)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Screenshots Path: </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="screenshot_path_edit"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="screenshot_path_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp
new file mode 100644
index 000000000..7dcb2c5b9
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.cpp
@@ -0,0 +1,146 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <unordered_map>
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "core/settings.h"
+#include "ui_configure_vibration.h"
+#include "yuzu/configuration/configure_vibration.h"
+
+ConfigureVibration::ConfigureVibration(QWidget* parent)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) {
+ ui->setupUi(this);
+
+ vibration_groupboxes = {
+ ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3,
+ ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6,
+ ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8,
+ };
+
+ vibration_spinboxes = {
+ ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3,
+ ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6,
+ ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8,
+ };
+
+ const auto& players = Settings::values.players.GetValue();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ vibration_groupboxes[i]->setChecked(players[i].vibration_enabled);
+ vibration_spinboxes[i]->setValue(players[i].vibration_strength);
+ }
+
+ ui->checkBoxAccurateVibration->setChecked(
+ Settings::values.enable_accurate_vibrations.GetValue());
+
+ if (!Settings::IsConfiguringGlobal()) {
+ ui->checkBoxAccurateVibration->setDisabled(true);
+ }
+
+ RetranslateUI();
+}
+
+ConfigureVibration::~ConfigureVibration() = default;
+
+void ConfigureVibration::ApplyConfiguration() {
+ auto& players = Settings::values.players.GetValue();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ players[i].vibration_enabled = vibration_groupboxes[i]->isChecked();
+ players[i].vibration_strength = vibration_spinboxes[i]->value();
+ }
+
+ Settings::values.enable_accurate_vibrations.SetValue(
+ ui->checkBoxAccurateVibration->isChecked());
+}
+
+void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
+ using namespace Settings::NativeButton;
+ static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{
+ {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons
+ {A, B, X, Y, R, ZR}, // Right Buttons
+ }};
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) {
+ std::unordered_map<std::string, int> params_count;
+
+ for (const auto button_index : buttons[device_idx]) {
+ const auto& player_button = player.buttons[button_index];
+
+ if (params_count.find(player_button) != params_count.end()) {
+ ++params_count[player_button];
+ continue;
+ }
+
+ params_count.insert_or_assign(player_button, 1);
+ }
+
+ const auto it = std::max_element(
+ params_count.begin(), params_count.end(),
+ [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
+
+ auto& vibration_param_str = player.vibrations[device_idx];
+ vibration_param_str.clear();
+
+ if (it->first.empty()) {
+ continue;
+ }
+
+ const auto param = Common::ParamPackage(it->first);
+
+ const auto engine = param.Get("engine", "");
+ const auto guid = param.Get("guid", "");
+ const auto port = param.Get("port", "");
+
+ if (engine.empty() || engine == "keyboard" || engine == "mouse") {
+ continue;
+ }
+
+ vibration_param_str += fmt::format("engine:{}", engine);
+
+ if (!port.empty()) {
+ vibration_param_str += fmt::format(",port:{}", port);
+ }
+ if (!guid.empty()) {
+ vibration_param_str += fmt::format(",guid:{}", guid);
+ }
+ }
+
+ if (player.vibrations[0] != player.vibrations[1]) {
+ return;
+ }
+
+ if (!player.vibrations[0].empty() &&
+ player.controller_type != Settings::ControllerType::RightJoycon) {
+ player.vibrations[1].clear();
+ } else if (!player.vibrations[1].empty() &&
+ player.controller_type == Settings::ControllerType::RightJoycon) {
+ player.vibrations[0].clear();
+ }
+}
+
+void ConfigureVibration::SetAllVibrationDevices() {
+ // Set vibration devices for all player indices including handheld
+ for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) {
+ SetVibrationDevices(player_idx);
+ }
+}
+
+void ConfigureVibration::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureVibration::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h
new file mode 100644
index 000000000..07411a86f
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+
+class QGroupBox;
+class QSpinBox;
+
+namespace Ui {
+class ConfigureVibration;
+}
+
+class ConfigureVibration : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureVibration(QWidget* parent);
+ ~ConfigureVibration() override;
+
+ void ApplyConfiguration();
+
+ static void SetVibrationDevices(std::size_t player_index);
+ static void SetAllVibrationDevices();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureVibration> ui;
+
+ static constexpr std::size_t NUM_PLAYERS = 8;
+
+ // Groupboxes encapsulating the vibration strength spinbox.
+ std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes;
+
+ // Spinboxes representing the vibration strength percentage.
+ std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes;
+};
diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui
new file mode 100644
index 000000000..efdf317a9
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.ui
@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureVibration</class>
+ <widget class="QDialog" name="ConfigureVibration">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>364</width>
+ <height>242</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Vibration</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="vibrationStrengthGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player14Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer1">
+ <property name="title">
+ <string>Player 1</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer1">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer2">
+ <property name="title">
+ <string>Player 2</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer2">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer3">
+ <property name="title">
+ <string>Player 3</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer3">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer4">
+ <property name="title">
+ <string>Player 4</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer4">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player58Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer7">
+ <property name="title">
+ <string>Player 5</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer7">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer8">
+ <property name="title">
+ <string>Player 6</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer8">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer5">
+ <property name="title">
+ <string>Player 7</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer5">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer6">
+ <property name="title">
+ <string>Player 8</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer6">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationSettingsGroup">
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="checkBoxAccurateVibration">
+ <property name="text">
+ <string>Enable Accurate Vibration</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerVibration">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>167</width>
+ <height>55</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBoxVibration">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBoxVibration</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureVibration</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBoxVibration</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureVibration</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
new file mode 100644
index 000000000..e87aededb
--- /dev/null
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -0,0 +1,131 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/input_profiles.h"
+
+namespace FS = Common::FS;
+
+namespace {
+
+bool ProfileExistsInFilesystem(std::string_view profile_name) {
+ return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), profile_name));
+}
+
+bool IsINI(std::string_view filename) {
+ const std::size_t index = filename.rfind('.');
+
+ if (index == std::string::npos) {
+ return false;
+ }
+
+ return filename.substr(index) == ".ini";
+}
+
+std::string GetNameWithoutExtension(const std::string& filename) {
+ const std::size_t index = filename.rfind('.');
+
+ if (index == std::string::npos) {
+ return filename;
+ }
+
+ return filename.substr(0, index);
+}
+
+} // namespace
+
+InputProfiles::InputProfiles() {
+ const std::string input_profile_loc =
+ fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir));
+
+ FS::ForeachDirectoryEntry(
+ nullptr, input_profile_loc,
+ [this](u64* entries_out, const std::string& directory, const std::string& filename) {
+ if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) {
+ map_profiles.insert_or_assign(
+ GetNameWithoutExtension(filename),
+ std::make_unique<Config>(GetNameWithoutExtension(filename),
+ Config::ConfigType::InputProfile));
+ }
+ return true;
+ });
+}
+
+InputProfiles::~InputProfiles() = default;
+
+std::vector<std::string> InputProfiles::GetInputProfileNames() {
+ std::vector<std::string> profile_names;
+ profile_names.reserve(map_profiles.size());
+
+ for (const auto& [profile_name, config] : map_profiles) {
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ DeleteProfile(profile_name);
+ continue;
+ }
+
+ profile_names.push_back(profile_name);
+ }
+
+ return profile_names;
+}
+
+bool InputProfiles::IsProfileNameValid(std::string_view profile_name) {
+ return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
+}
+
+bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) {
+ if (ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ map_profiles.insert_or_assign(
+ profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
+
+ return SaveProfile(profile_name, player_index);
+}
+
+bool InputProfiles::DeleteProfile(const std::string& profile_name) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name) ||
+ FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) {
+ map_profiles.erase(profile_name);
+ }
+
+ return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
+}
+
+bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ map_profiles.erase(profile_name);
+ return false;
+ }
+
+ map_profiles[profile_name]->ReadControlPlayerValue(player_index);
+ return true;
+}
+
+bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ map_profiles[profile_name]->SaveControlPlayerValue(player_index);
+ return true;
+}
+
+bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const {
+ return map_profiles.find(profile_name) != map_profiles.end();
+}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
new file mode 100644
index 000000000..cb41fd9be
--- /dev/null
+++ b/src/yuzu/configuration/input_profiles.h
@@ -0,0 +1,32 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+class Config;
+
+class InputProfiles {
+
+public:
+ explicit InputProfiles();
+ virtual ~InputProfiles();
+
+ std::vector<std::string> GetInputProfileNames();
+
+ static bool IsProfileNameValid(std::string_view profile_name);
+
+ bool CreateProfile(const std::string& profile_name, std::size_t player_index);
+ bool DeleteProfile(const std::string& profile_name);
+ bool LoadProfile(const std::string& profile_name, std::size_t player_index);
+ bool SaveProfile(const std::string& profile_name, std::size_t player_index);
+
+private:
+ bool ProfileExistsInMap(const std::string& profile_name) const;
+
+ std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
+};
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index f594ef076..0e26f765b 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -51,7 +51,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Di
setWindowTitle(tr("MicroProfile"));
resize(1000, 600);
// Remove the "?" button from the titlebar and enable the maximize button
- setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint);
+ setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) |
+ Qt::WindowMaximizeButtonHint);
#if MICROPROFILE_ENABLED
@@ -108,8 +109,7 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileSetDisplayMode(1); // Timers screen
MicroProfileInitUI();
- connect(&update_timer, &QTimer::timeout, this,
- static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update));
+ connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update));
}
void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index c1ea25fb8..3439cb333 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -2,10 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <fmt/format.h>
+
#include "yuzu/debugger/wait_tree.h"
+#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
#include "common/assert.h"
+#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/mutex.h"
@@ -16,11 +21,40 @@
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
+namespace {
+
+constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
+ {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
+ {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
+ {Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan},
+ {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
+ {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
+ {Qt::GlobalColor::darkRed, Qt::GlobalColor::red},
+ {Qt::GlobalColor::darkYellow, Qt::GlobalColor::yellow},
+ {Qt::GlobalColor::red, Qt::GlobalColor::red},
+ {Qt::GlobalColor::darkCyan, Qt::GlobalColor::cyan},
+ {Qt::GlobalColor::gray, Qt::GlobalColor::gray},
+}};
+
+bool IsDarkTheme() {
+ const auto& theme = UISettings::values.theme;
+ return theme == QStringLiteral("qdarkstyle") ||
+ theme == QStringLiteral("qdarkstyle_midnight_blue") ||
+ theme == QStringLiteral("colorful_dark") ||
+ theme == QStringLiteral("colorful_midnight_blue");
+}
+
+} // namespace
+
WaitTreeItem::WaitTreeItem() = default;
WaitTreeItem::~WaitTreeItem() = default;
QColor WaitTreeItem::GetColor() const {
- return QColor(Qt::GlobalColor::black);
+ if (IsDarkTheme()) {
+ return QColor(Qt::GlobalColor::white);
+ } else {
+ return QColor(Qt::GlobalColor::black);
+ }
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const {
@@ -59,8 +93,10 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
std::size_t row = 0;
auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::Thread>>& threads) {
for (std::size_t i = 0; i < threads.size(); ++i) {
- item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
- item_list.back()->row = row;
+ if (!threads[i]->IsHLEThread()) {
+ item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
+ item_list.back()->row = row;
+ }
++row;
}
};
@@ -114,20 +150,21 @@ QString WaitTreeCallstack::GetText() const {
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list;
- constexpr std::size_t BaseRegister = 29;
- auto& memory = Core::System::GetInstance().Memory();
- u64 base_pointer = thread.GetContext64().cpu_registers[BaseRegister];
+ if (thread.IsHLEThread()) {
+ return list;
+ }
- while (base_pointer != 0) {
- const u64 lr = memory.Read64(base_pointer + sizeof(u64));
- if (lr == 0) {
- break;
- }
+ if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) {
+ return list;
+ }
- list.push_back(std::make_unique<WaitTreeText>(
- tr("0x%1").arg(lr - sizeof(u32), 16, 16, QLatin1Char{'0'})));
+ auto backtrace = Core::ARM_Interface::GetBacktraceFromContext(Core::System::GetInstance(),
+ thread.GetContext64());
- base_pointer = memory.Read64(base_pointer);
+ for (auto& entry : backtrace) {
+ std::string s = fmt::format("{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
+ entry.original_address, entry.offset, entry.name);
+ list.push_back(std::make_unique<WaitTreeText>(QString::fromStdString(s)));
}
return list;
@@ -206,7 +243,15 @@ QString WaitTreeThread::GetText() const {
status = tr("running");
break;
case Kernel::ThreadStatus::Ready:
- status = tr("ready");
+ if (!thread.IsPaused()) {
+ if (thread.WasRunning()) {
+ status = tr("running");
+ } else {
+ status = tr("ready");
+ }
+ } else {
+ status = tr("paused");
+ }
break;
case Kernel::ThreadStatus::Paused:
status = tr("paused");
@@ -249,28 +294,38 @@ QString WaitTreeThread::GetText() const {
}
QColor WaitTreeThread::GetColor() const {
+ const std::size_t color_index = IsDarkTheme() ? 1 : 0;
+
const auto& thread = static_cast<const Kernel::Thread&>(object);
switch (thread.GetStatus()) {
case Kernel::ThreadStatus::Running:
- return QColor(Qt::GlobalColor::darkGreen);
+ return QColor(WaitTreeColors[0][color_index]);
case Kernel::ThreadStatus::Ready:
- return QColor(Qt::GlobalColor::darkBlue);
+ if (!thread.IsPaused()) {
+ if (thread.WasRunning()) {
+ return QColor(WaitTreeColors[1][color_index]);
+ } else {
+ return QColor(WaitTreeColors[2][color_index]);
+ }
+ } else {
+ return QColor(WaitTreeColors[3][color_index]);
+ }
case Kernel::ThreadStatus::Paused:
- return QColor(Qt::GlobalColor::lightGray);
+ return QColor(WaitTreeColors[4][color_index]);
case Kernel::ThreadStatus::WaitHLEEvent:
case Kernel::ThreadStatus::WaitIPC:
- return QColor(Qt::GlobalColor::darkRed);
+ return QColor(WaitTreeColors[5][color_index]);
case Kernel::ThreadStatus::WaitSleep:
- return QColor(Qt::GlobalColor::darkYellow);
+ return QColor(WaitTreeColors[6][color_index]);
case Kernel::ThreadStatus::WaitSynch:
case Kernel::ThreadStatus::WaitMutex:
case Kernel::ThreadStatus::WaitCondVar:
case Kernel::ThreadStatus::WaitArb:
- return QColor(Qt::GlobalColor::red);
+ return QColor(WaitTreeColors[7][color_index]);
case Kernel::ThreadStatus::Dormant:
- return QColor(Qt::GlobalColor::darkCyan);
+ return QColor(WaitTreeColors[8][color_index]);
case Kernel::ThreadStatus::Dead:
- return QColor(Qt::GlobalColor::gray);
+ return QColor(WaitTreeColors[9][color_index]);
default:
return WaitTreeItem::GetColor();
}
@@ -319,7 +374,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetSynchronizationObjects(),
- thread.IsSleepingOnWait()));
+ thread.IsWaitingSync()));
}
list.push_back(std::make_unique<WaitTreeCallstack>(thread));
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
index ea0079353..a93733b26 100644
--- a/src/yuzu/discord_impl.cpp
+++ b/src/yuzu/discord_impl.cpp
@@ -18,7 +18,7 @@ DiscordImpl::DiscordImpl() {
// The number is the client ID for yuzu, it's used for images and the
// application name
- Discord_Initialize("471872241299226636", &handlers, 1, nullptr);
+ Discord_Initialize("712465656758665259", &handlers, 1, nullptr);
}
DiscordImpl::~DiscordImpl() {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index dccbabcbf..70d865112 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -25,7 +25,8 @@
#include "yuzu/main.h"
#include "yuzu/uisettings.h"
-GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
+GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
+ : QObject(parent), gamelist{gamelist} {}
// EventFilter in order to process systemkeys while editing the searchfield
bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) {
@@ -56,7 +57,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
case Qt::Key_Return:
case Qt::Key_Enter: {
if (gamelist->search_field->visible == 1) {
- QString file_path = gamelist->getLastFilterResultItem();
+ const QString file_path = gamelist->GetLastFilterResultItem();
// To avoid loading error dialog loops while confirming them using enter
// Also users usually want to run a different game after closing one
@@ -83,22 +84,25 @@ void GameListSearchField::setFilterResult(int visible, int total) {
label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
}
-QString GameList::getLastFilterResultItem() const {
- QStandardItem* folder;
- QStandardItem* child;
+QString GameList::GetLastFilterResultItem() const {
QString file_path;
const int folder_count = item_model->rowCount();
+
for (int i = 0; i < folder_count; ++i) {
- folder = item_model->item(i, 0);
+ const QStandardItem* folder = item_model->item(i, 0);
const QModelIndex folder_index = folder->index();
const int children_count = folder->rowCount();
+
for (int j = 0; j < children_count; ++j) {
- if (!tree_view->isRowHidden(j, folder_index)) {
- child = folder->child(j, 0);
- file_path = child->data(GameListItemPath::FullPathRole).toString();
+ if (tree_view->isRowHidden(j, folder_index)) {
+ continue;
}
+
+ const QStandardItem* child = folder->child(j, 0);
+ file_path = child->data(GameListItemPath::FullPathRole).toString();
}
}
+
return file_path;
}
@@ -113,7 +117,7 @@ void GameListSearchField::setFocus() {
}
GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
- auto* const key_release_eater = new KeyReleaseEater(parent);
+ auto* const key_release_eater = new KeyReleaseEater(parent, this);
layout_filter = new QHBoxLayout;
layout_filter->setMargin(8);
label_filter = new QLabel;
@@ -123,7 +127,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
edit_filter->installEventFilter(key_release_eater);
edit_filter->setClearButtonEnabled(true);
- connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);
+ connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
label_filter_result = new QLabel;
button_filter_close = new QToolButton(this);
button_filter_close->setText(QStringLiteral("X"));
@@ -133,7 +137,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
"#000000; font-weight: bold; background: #F0F0F0; }"
"QToolButton:hover{ border: none; padding: 0px; color: "
"#EEEEEE; font-weight: bold; background: #E81123}"));
- connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked);
+ connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked);
layout_filter->setSpacing(10);
layout_filter->addWidget(label_filter);
layout_filter->addWidget(edit_filter);
@@ -159,16 +163,22 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
}
// Syncs the expanded state of Game Directories with settings to persist across sessions
-void GameList::onItemExpanded(const QModelIndex& item) {
+void GameList::OnItemExpanded(const QModelIndex& item) {
const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
- if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
- type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)
- item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded =
- tree_view->isExpanded(item);
+ const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
+ type == GameListItemType::UserNandDir ||
+ type == GameListItemType::SysNandDir;
+
+ if (!is_dir) {
+ return;
+ }
+
+ auto* game_dir = item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+ game_dir->expanded = tree_view->isExpanded(item);
}
// Event in order to filter the gamelist after editing the searchfield
-void GameList::onTextChanged(const QString& new_text) {
+void GameList::OnTextChanged(const QString& new_text) {
const int folder_count = tree_view->model()->rowCount();
QString edit_filter_text = new_text.toLower();
QStandardItem* folder;
@@ -224,7 +234,7 @@ void GameList::onTextChanged(const QString& new_text) {
}
}
-void GameList::onUpdateThemedIcons() {
+void GameList::OnUpdateThemedIcons() {
for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
QStandardItem* child = item_model->invisibleRootItem()->child(i);
@@ -276,7 +286,7 @@ void GameList::onUpdateThemedIcons() {
}
}
-void GameList::onFilterCloseClicked() {
+void GameList::OnFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
@@ -317,11 +327,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
}
item_model->setSortRole(GameListItemPath::SortRole);
- connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons);
+ connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
- connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded);
- connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded);
+ connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
+ connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
// We must register all custom types with the Qt Automoc system so that we are able to use
// it with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -338,17 +348,17 @@ GameList::~GameList() {
emit ShouldCancelWorker();
}
-void GameList::setFilterFocus() {
+void GameList::SetFilterFocus() {
if (tree_view->model()->rowCount() > 0) {
search_field->setFocus();
}
}
-void GameList::setFilterVisible(bool visibility) {
+void GameList::SetFilterVisible(bool visibility) {
search_field->setVisible(visibility);
}
-void GameList::clearFilter() {
+void GameList::ClearFilter() {
search_field->clear();
}
@@ -397,22 +407,24 @@ void GameList::ValidateEntry(const QModelIndex& item) {
}
}
-bool GameList::isEmpty() const {
+bool GameList::IsEmpty() const {
for (int i = 0; i < item_model->rowCount(); i++) {
const QStandardItem* child = item_model->invisibleRootItem()->child(i);
const auto type = static_cast<GameListItemType>(child->type());
+
if (!child->hasChildren() &&
(type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
type == GameListItemType::SysNandDir)) {
item_model->invisibleRootItem()->removeRow(child->row());
i--;
- };
+ }
}
+
return !item_model->invisibleRootItem()->hasChildren();
}
-void GameList::DonePopulating(QStringList watch_list) {
- emit ShowList(!isEmpty());
+void GameList::DonePopulating(const QStringList& watch_list) {
+ emit ShowList(!IsEmpty());
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
@@ -472,30 +484,58 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
-void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
+void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
- QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
+ QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
QAction* open_transferable_shader_cache =
context_menu.addAction(tr("Open Transferable Shader Cache"));
context_menu.addSeparator();
+ QMenu* remove_menu = context_menu.addMenu(tr("Remove"));
+ QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
+ QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
+ QAction* remove_shader_cache = remove_menu->addAction(tr("Remove Shader Cache"));
+ QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
+ remove_menu->addSeparator();
+ QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents"));
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
- open_save_location->setEnabled(program_id != 0);
+ open_save_location->setVisible(program_id != 0);
+ open_mod_location->setVisible(program_id != 0);
+ open_transferable_shader_cache->setVisible(program_id != 0);
+ remove_update->setVisible(program_id != 0);
+ remove_dlc->setVisible(program_id != 0);
+ remove_shader_cache->setVisible(program_id != 0);
+ remove_all_content->setVisible(program_id != 0);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
- connect(open_save_location, &QAction::triggered, [this, program_id]() {
- emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData);
+ connect(open_save_location, &QAction::triggered, [this, program_id, path]() {
+ emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
});
- connect(open_lfs_location, &QAction::triggered, [this, program_id]() {
- emit OpenFolderRequested(program_id, GameListOpenTarget::ModData);
+ connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
+ emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
});
connect(open_transferable_shader_cache, &QAction::triggered,
[this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
+ connect(remove_all_content, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game);
+ });
+ connect(remove_update, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update);
+ });
+ connect(remove_dlc, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent);
+ });
+ connect(remove_shader_cache, &QAction::triggered, [this, program_id]() {
+ emit RemoveFileRequested(program_id, GameListRemoveTarget::ShaderCache);
+ });
+ connect(remove_custom_config, &QAction::triggered, [this, program_id]() {
+ emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration);
+ });
connect(dump_romfs, &QAction::triggered,
[this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
connect(copy_tid, &QAction::triggered,
@@ -531,8 +571,8 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
UISettings::GameDir& game_dir =
*selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
- QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up"));
- QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down "));
+ QAction* move_up = context_menu.addAction(tr("\u25B2 Move Up"));
+ QAction* move_down = context_menu.addAction(tr("\u25bc Move Down"));
QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location"));
const int row = selected.row();
@@ -662,12 +702,15 @@ void GameList::SaveInterfaceLayout() {
}
void GameList::LoadInterfaceLayout() {
- auto header = tree_view->header();
- if (!header->restoreState(UISettings::values.gamelist_header_state)) {
- // We are using the name column to display icons and titles
- // so make it as large as possible as default.
- header->resizeSection(COLUMN_NAME, header->width());
+ auto* header = tree_view->header();
+
+ if (header->restoreState(UISettings::values.gamelist_header_state)) {
+ return;
}
+
+ // We are using the name column to display icons and titles
+ // so make it as large as possible as default.
+ header->resizeSection(COLUMN_NAME, header->width());
}
const QStringList GameList::supported_file_extensions = {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 878d94413..58059a3c4 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -39,6 +39,17 @@ enum class GameListOpenTarget {
ModData,
};
+enum class GameListRemoveTarget {
+ ShaderCache,
+ CustomConfiguration,
+};
+
+enum class InstalledEntryType {
+ Game,
+ Update,
+ AddOnContent,
+};
+
class GameList : public QWidget {
Q_OBJECT
@@ -56,11 +67,11 @@ public:
FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
- QString getLastFilterResultItem() const;
- void clearFilter();
- void setFilterFocus();
- void setFilterVisible(bool visibility);
- bool isEmpty() const;
+ QString GetLastFilterResultItem() const;
+ void ClearFilter();
+ void SetFilterFocus();
+ void SetFilterVisible(bool visibility);
+ bool IsEmpty() const;
void LoadCompatibilityList();
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
@@ -71,10 +82,13 @@ public:
static const QStringList supported_file_extensions;
signals:
- void GameChosen(QString game_path);
+ void GameChosen(const QString& game_path);
void ShouldCancelWorker();
- void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
+ void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path);
void OpenTransferableShaderCacheRequested(u64 program_id);
+ void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
+ void RemoveFileRequested(u64 program_id, GameListRemoveTarget target);
void DumpRomFSRequested(u64 program_id, const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
@@ -85,21 +99,21 @@ signals:
void ShowList(bool show);
private slots:
- void onItemExpanded(const QModelIndex& item);
- void onTextChanged(const QString& new_text);
- void onFilterCloseClicked();
- void onUpdateThemedIcons();
+ void OnItemExpanded(const QModelIndex& item);
+ void OnTextChanged(const QString& new_text);
+ void OnFilterCloseClicked();
+ void OnUpdateThemedIcons();
private:
void AddDirEntry(GameListDir* entry_items);
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
void ValidateEntry(const QModelIndex& item);
- void DonePopulating(QStringList watch_list);
+ void DonePopulating(const QStringList& watch_list);
void RefreshGameDirectory();
void PopupContextMenu(const QPoint& menu_location);
- void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path);
+ void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
@@ -117,8 +131,6 @@ private:
friend class GameListSearchField;
};
-Q_DECLARE_METATYPE(GameListOpenTarget);
-
class GameListPlaceholder : public QWidget {
Q_OBJECT
public:
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 3e6d5a7cd..248855aff 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -49,10 +49,10 @@ class GameListItem : public QStandardItem {
public:
// used to access type from item index
- static const int TypeRole = Qt::UserRole + 1;
- static const int SortRole = Qt::UserRole + 2;
+ static constexpr int TypeRole = Qt::UserRole + 1;
+ static constexpr int SortRole = Qt::UserRole + 2;
GameListItem() = default;
- GameListItem(const QString& string) : QStandardItem(string) {
+ explicit GameListItem(const QString& string) : QStandardItem(string) {
setData(string, SortRole);
}
};
@@ -65,10 +65,10 @@ public:
*/
class GameListItemPath : public GameListItem {
public:
- static const int TitleRole = SortRole + 1;
- static const int FullPathRole = SortRole + 2;
- static const int ProgramIdRole = SortRole + 3;
- static const int FileTypeRole = SortRole + 4;
+ static constexpr int TitleRole = SortRole + 1;
+ static constexpr int FullPathRole = SortRole + 2;
+ static constexpr int ProgramIdRole = SortRole + 3;
+ static constexpr int FileTypeRole = SortRole + 4;
GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
@@ -110,35 +110,32 @@ public:
const auto& row1 = row_data.at(UISettings::values.row_1_text_id);
const int row2_id = UISettings::values.row_2_text_id;
- if (role == SortRole)
+ if (role == SortRole) {
return row1.toLower();
+ }
- if (row2_id == 4) // None
+ // None
+ if (row2_id == 4) {
return row1;
+ }
const auto& row2 = row_data.at(row2_id);
- if (row1 == row2)
+ if (row1 == row2) {
return row1;
+ }
- return QString(row1 + QStringLiteral("\n ") + row2);
+ return QStringLiteral("%1\n %2").arg(row1, row2);
}
return GameListItem::data(role);
}
-
- /**
- * Override to prevent automatic sorting.
- */
- bool operator<(const QStandardItem& other) const override {
- return false;
- }
};
class GameListItemCompat : public GameListItem {
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
public:
- static const int CompatNumberRole = SortRole;
+ static constexpr int CompatNumberRole = SortRole;
GameListItemCompat() = default;
explicit GameListItemCompat(const QString& compatibility) {
setData(type(), TypeRole);
@@ -188,7 +185,7 @@ public:
*/
class GameListItemSize : public GameListItem {
public:
- static const int SizeRole = SortRole;
+ static constexpr int SizeRole = SortRole;
GameListItemSize() = default;
explicit GameListItemSize(const qulonglong size_bytes) {
@@ -224,7 +221,7 @@ public:
class GameListDir : public GameListItem {
public:
- static const int GameDirRole = Qt::UserRole + 2;
+ static constexpr int GameDirRole = Qt::UserRole + 2;
explicit GameListDir(UISettings::GameDir& directory,
GameListItemType dir_type = GameListItemType::CustomDir)
@@ -279,6 +276,13 @@ public:
return static_cast<int>(dir_type);
}
+ /**
+ * Override to prevent automatic sorting between folders and the addDir button.
+ */
+ bool operator<(const QStandardItem& other) const override {
+ return false;
+ }
+
private:
GameListItemType dir_type;
};
@@ -326,7 +330,7 @@ public:
private:
class KeyReleaseEater : public QObject {
public:
- explicit KeyReleaseEater(GameList* gamelist);
+ explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr);
private:
GameList* gamelist = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index da2c27aa2..23643aea2 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -39,12 +39,12 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
return generator();
}
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + '.' + ext;
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + '.' + ext;
- FileUtil::CreateFullPath(path);
+ Common::FS::CreateFullPath(path);
- if (!FileUtil::Exists(path)) {
+ if (!Common::FS::Exists(path)) {
const auto str = generator();
QFile file{QString::fromStdString(path)};
@@ -70,14 +70,14 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
return generator();
}
- const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + ".jpeg";
- const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + ".appname.txt";
+ const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + ".jpeg";
+ const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + ".appname.txt";
- FileUtil::CreateFullPath(path1);
+ Common::FS::CreateFullPath(path1);
- if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
+ if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) {
const auto [icon, nacp] = generator();
QFile file1{QString::fromStdString(path1)};
@@ -91,7 +91,8 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
return generator();
}
- if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) != icon.size()) {
+ if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) !=
+ s64(icon.size())) {
LOG_ERROR(Frontend, "Failed to write data to cache file.");
return generator();
}
@@ -207,7 +208,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
- new GameListItemSize(FileUtil::GetSize(path)),
+ new GameListItemSize(Common::FS::GetSize(path)),
};
if (UISettings::values.show_add_ons) {
@@ -234,12 +235,11 @@ GameListWorker::~GameListWorker() = default;
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
using namespace FileSys;
- const auto& cache =
- dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider());
+ auto& system = Core::System::GetInstance();
+ const auto& cache = dynamic_cast<ContentProviderUnion&>(system.GetContentProvider());
- std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games;
- installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
- ContentRecordType::Program);
+ auto installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
+ ContentRecordType::Program);
if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) {
installed_games = cache.ListEntriesFilterOrigin(
@@ -253,23 +253,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
}
for (const auto& [slot, game] : installed_games) {
- if (slot == ContentProviderUnionSlot::FrontendManual)
+ if (slot == ContentProviderUnionSlot::FrontendManual) {
continue;
+ }
const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
- std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
- if (!loader)
+ std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(system, file);
+ if (!loader) {
continue;
+ }
std::vector<u8> icon;
std::string name;
u64 program_id = 0;
loader->ReadProgramId(program_id);
- const PatchManager patch{program_id};
+ const PatchManager patch{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
- if (control != nullptr)
+ if (control != nullptr) {
GetMetadataFromControlNCA(patch, *control, icon, name);
+ }
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch),
@@ -279,20 +283,22 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
unsigned int recursion, GameListDir* parent_dir) {
- const auto callback = [this, target, recursion,
- parent_dir](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+ auto& system = Core::System::GetInstance();
+
+ const auto callback = [this, target, recursion, parent_dir,
+ &system](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
}
const std::string physical_name = directory + DIR_SEP + virtual_name;
- const bool is_dir = FileUtil::IsDirectory(physical_name);
+ const bool is_dir = Common::FS::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
- auto loader = Loader::GetLoader(file);
+ auto loader = Loader::GetLoader(system, file);
if (!loader) {
return true;
}
@@ -330,7 +336,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
std::string name = " ";
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
- const FileSys::PatchManager patch{program_id};
+ const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
compatibility_list, patch),
@@ -344,11 +351,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
return true;
};
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
+ Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback);
}
void GameListWorker::run() {
stop_processing = false;
+ provider->ClearAllEntries();
for (UISettings::GameDir& game_dir : game_dirs) {
if (game_dir.path == QStringLiteral("SDMC")) {
@@ -367,13 +375,12 @@ void GameListWorker::run() {
watch_list.append(game_dir.path);
auto* const game_list_dir = new GameListDir(game_dir);
emit DirEntryReady(game_list_dir);
- provider->ClearAllEntries();
- ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
- game_list_dir);
+ ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
+ game_dir.deep_scan ? 256 : 0, game_list_dir);
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
game_dir.deep_scan ? 256 : 0, game_list_dir);
}
- };
+ }
emit Finished(watch_list);
}
diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp
new file mode 100644
index 000000000..06b0b1874
--- /dev/null
+++ b/src/yuzu/install_dialog.cpp
@@ -0,0 +1,72 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QCheckBox>
+#include <QDialogButtonBox>
+#include <QFileInfo>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QListWidget>
+#include <QVBoxLayout>
+#include "yuzu/install_dialog.h"
+#include "yuzu/uisettings.h"
+
+InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) {
+ file_list = new QListWidget(this);
+
+ for (const QString& file : files) {
+ QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), file_list);
+ item->setData(Qt::UserRole, file);
+ item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+ item->setCheckState(Qt::Checked);
+ }
+
+ file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 11) / 10);
+
+ vbox_layout = new QVBoxLayout;
+
+ hbox_layout = new QHBoxLayout;
+
+ description = new QLabel(tr("Please confirm these are the files you wish to install."));
+
+ update_description =
+ new QLabel(tr("Installing an Update or DLC will overwrite the previously installed one."));
+
+ buttons = new QDialogButtonBox;
+ buttons->addButton(QDialogButtonBox::Cancel);
+ buttons->addButton(tr("Install"), QDialogButtonBox::AcceptRole);
+
+ connect(buttons, &QDialogButtonBox::accepted, this, &InstallDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &InstallDialog::reject);
+
+ hbox_layout->addWidget(buttons);
+
+ vbox_layout->addWidget(description);
+ vbox_layout->addWidget(update_description);
+ vbox_layout->addWidget(file_list);
+ vbox_layout->addLayout(hbox_layout);
+
+ setLayout(vbox_layout);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ setWindowTitle(tr("Install Files to NAND"));
+}
+
+InstallDialog::~InstallDialog() = default;
+
+QStringList InstallDialog::GetFiles() const {
+ QStringList files;
+
+ for (int i = 0; i < file_list->count(); ++i) {
+ const QListWidgetItem* item = file_list->item(i);
+ if (item->checkState() == Qt::Checked) {
+ files.append(item->data(Qt::UserRole).toString());
+ }
+ }
+
+ return files;
+}
+
+int InstallDialog::GetMinimumWidth() const {
+ return file_list->width();
+}
diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h
new file mode 100644
index 000000000..68e03fe4e
--- /dev/null
+++ b/src/yuzu/install_dialog.h
@@ -0,0 +1,35 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+class QCheckBox;
+class QDialogButtonBox;
+class QHBoxLayout;
+class QLabel;
+class QListWidget;
+class QVBoxLayout;
+
+class InstallDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit InstallDialog(QWidget* parent, const QStringList& files);
+ ~InstallDialog() override;
+
+ [[nodiscard]] QStringList GetFiles() const;
+ [[nodiscard]] int GetMinimumWidth() const;
+
+private:
+ QListWidget* file_list;
+
+ QVBoxLayout* vbox_layout;
+ QHBoxLayout* hbox_layout;
+
+ QLabel* description;
+ QLabel* update_description;
+ QDialogButtonBox* buttons;
+};
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index 2a6483370..ae842306c 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -19,6 +19,7 @@
#include <QTime>
#include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h"
+#include "core/frontend/framebuffer_layout.h"
#include "core/loader/loader.h"
#include "ui_loading_screen.h"
#include "video_core/rasterizer_interface.h"
@@ -61,7 +62,7 @@ LoadingScreen::LoadingScreen(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::LoadingScreen>()),
previous_stage(VideoCore::LoadCallbackStage::Complete) {
ui->setupUi(this);
- setMinimumSize(1280, 720);
+ setMinimumSize(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
// Create a fade out effect to hide this loading screen widget.
// When fading opacity, it will fade to the parent widgets background color, which is why we
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1717e06f9..e704cc656 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -11,15 +11,19 @@
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/controller.h"
#include "applets/error.h"
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
#include "applets/web_browser.h"
#include "configuration/configure_input.h"
-#include "configuration/configure_per_general.h"
+#include "configuration/configure_per_game.h"
+#include "configuration/configure_vibration.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
+#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
@@ -47,15 +51,18 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialogButtonBox>
+#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QProgressBar>
#include <QProgressDialog>
+#include <QPushButton>
#include <QShortcut>
#include <QStatusBar>
#include <QSysInfo>
+#include <QUrl>
#include <QtConcurrent/QtConcurrent>
#include <fmt/format.h>
@@ -65,6 +72,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
+#include "common/memory_detect.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
@@ -82,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
-#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -92,6 +99,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "input_common/main.h"
+#include "video_core/gpu.h"
+#include "video_core/shader_notify.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
#include "yuzu/compatdb.h"
@@ -105,6 +115,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
#include "yuzu/uisettings.h"
@@ -135,6 +146,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
+constexpr int default_mouse_timeout = 2500;
+
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
/**
@@ -170,8 +183,8 @@ static void InitializeLogging() {
log_filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(log_filter);
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -179,11 +192,13 @@ static void InitializeLogging() {
}
GMainWindow::GMainWindow()
- : config(new Config()), emu_thread(nullptr),
- vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
- provider(std::make_unique<FileSys::ManualContentProvider>()) {
+ : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
+ config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
+ provider{std::make_unique<FileSys::ManualContentProvider>()} {
InitializeLogging();
+ LoadTranslation();
+
setAcceptDrops(true);
ui.setupUi(this);
statusBar()->hide();
@@ -214,9 +229,26 @@ GMainWindow::GMainWindow()
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", yuzu_build_version, Common::g_scm_branch,
Common::g_scm_desc);
#ifdef ARCHITECTURE_x86_64
- LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string);
+ const auto& caps = Common::GetCPUCaps();
+ std::string cpu_string = caps.cpu_string;
+ if (caps.avx || caps.avx2 || caps.avx512) {
+ cpu_string += " | AVX";
+ if (caps.avx512) {
+ cpu_string += "512";
+ } else if (caps.avx2) {
+ cpu_string += '2';
+ }
+ if (caps.fma || caps.fma4) {
+ cpu_string += " | FMA";
+ }
+ }
+ LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
#endif
LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
+ LOG_INFO(Frontend, "Host RAM: {:.2f} GB",
+ Common::GetMemInfo().TotalPhysicalMemory / 1024.0f / 1024 / 1024);
+ LOG_INFO(Frontend, "Host Swap: {:.2f} GB",
+ Common::GetMemInfo().TotalSwapMemory / 1024.0f / 1024 / 1024);
UpdateWindowTitle();
show();
@@ -236,10 +268,20 @@ GMainWindow::GMainWindow()
// Show one-time "callout" messages to the user
ShowTelemetryCallout();
+ // make sure menubar has the arrow cursor instead of inheriting from this
+ ui.menubar->setCursor(QCursor());
+ statusBar()->setCursor(QCursor());
+
+ mouse_hide_timer.setInterval(default_mouse_timeout);
+ connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
+ connect(ui.menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
+
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
BootGame(args[1]);
}
+
+ MigrateConfigFiles();
}
GMainWindow::~GMainWindow() {
@@ -248,17 +290,36 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
+void GMainWindow::ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) {
+ QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ emit ControllerSelectorReconfigureFinished();
+
+ // Don't forget to apply settings.
+ Settings::Apply();
+ config->Save();
+
+ UpdateStatusButtons();
+}
+
void GMainWindow::ProfileSelectorSelectProfile() {
QtProfileSelectionDialog dialog(this);
- dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
- Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
+ Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
- Service::Account::ProfileManager manager;
+ const Service::Account::ProfileManager manager;
const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex()));
if (!uuid.has_value()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
@@ -271,8 +332,9 @@ void GMainWindow::ProfileSelectorSelectProfile() {
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
- dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
- Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
+ Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
@@ -435,7 +497,7 @@ void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
#endif
- render_window = new GRenderWindow(this, emu_thread.get());
+ render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem);
render_window->hide();
game_list = new GameList(vfs, provider.get(), this);
@@ -464,6 +526,8 @@ void GMainWindow::InitializeWidgets() {
message_label->setAlignment(Qt::AlignLeft);
statusBar()->addPermanentWidget(message_label, 1);
+ shader_building_label = new QLabel();
+ shader_building_label->setToolTip(tr("The amount of shaders currently being built"));
emu_speed_label = new QLabel();
emu_speed_label->setToolTip(
tr("Current emulation speed. Values higher or lower than 100% "
@@ -476,7 +540,8 @@ void GMainWindow::InitializeWidgets() {
tr("Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For "
"full-speed emulation this should be at most 16.67 ms."));
- for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) {
+ for (auto& label :
+ {shader_building_label, emu_speed_label, game_fps_label, emu_frametime_label}) {
label->setVisible(false);
label->setFrameStyle(QFrame::NoFrame);
label->setContentsMargins(4, 0, 4, 0);
@@ -488,13 +553,14 @@ void GMainWindow::InitializeWidgets() {
dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
dock_status_button->setFocusPolicy(Qt::NoFocus);
connect(dock_status_button, &QPushButton::clicked, [&] {
- Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
- dock_status_button->setChecked(Settings::values.use_docked_mode);
- OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode);
+ Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue());
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+ OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+ Settings::values.use_docked_mode.GetValue());
});
dock_status_button->setText(tr("DOCK"));
dock_status_button->setCheckable(true);
- dock_status_button->setChecked(Settings::values.use_docked_mode);
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
statusBar()->insertPermanentWidget(0, dock_status_button);
// Setup ASync button
@@ -505,14 +571,36 @@ void GMainWindow::InitializeWidgets() {
if (emulation_running) {
return;
}
- Settings::values.use_asynchronous_gpu_emulation =
- !Settings::values.use_asynchronous_gpu_emulation;
- async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
+ bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
+ Settings::values.use_multi_core.GetValue();
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
+ async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
Settings::Apply();
});
async_status_button->setText(tr("ASYNC"));
async_status_button->setCheckable(true);
- async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
+ async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
+
+ // Setup Multicore button
+ multicore_status_button = new QPushButton();
+ multicore_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
+ multicore_status_button->setFocusPolicy(Qt::NoFocus);
+ connect(multicore_status_button, &QPushButton::clicked, [&] {
+ if (emulation_running) {
+ return;
+ }
+ Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue());
+ bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
+ Settings::values.use_multi_core.GetValue();
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
+ async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
+ multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
+ Settings::Apply();
+ });
+ multicore_status_button->setText(tr("MULTICORE"));
+ multicore_status_button->setCheckable(true);
+ multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
+ statusBar()->insertPermanentWidget(0, multicore_status_button);
statusBar()->insertPermanentWidget(0, async_status_button);
// Setup Renderer API button
@@ -520,7 +608,7 @@ void GMainWindow::InitializeWidgets() {
renderer_status_button->setObjectName(QStringLiteral("RendererStatusBarButton"));
renderer_status_button->setCheckable(true);
renderer_status_button->setFocusPolicy(Qt::NoFocus);
- connect(renderer_status_button, &QPushButton::toggled, [=](bool checked) {
+ connect(renderer_status_button, &QPushButton::toggled, [this](bool checked) {
renderer_status_button->setText(checked ? tr("VULKAN") : tr("OPENGL"));
});
renderer_status_button->toggle();
@@ -530,16 +618,16 @@ void GMainWindow::InitializeWidgets() {
renderer_status_button->setCheckable(false);
renderer_status_button->setDisabled(true);
#else
- renderer_status_button->setChecked(Settings::values.renderer_backend ==
+ renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
- connect(renderer_status_button, &QPushButton::clicked, [=] {
+ connect(renderer_status_button, &QPushButton::clicked, [this] {
if (emulation_running) {
return;
}
if (renderer_status_button->isChecked()) {
- Settings::values.renderer_backend = Settings::RendererBackend::Vulkan;
+ Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan);
} else {
- Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+ Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL);
}
Settings::Apply();
@@ -638,6 +726,11 @@ void GMainWindow::InitializeHotkeys() {
ui.action_Capture_Screenshot->setShortcutContext(
hotkey_registry.GetShortcutContext(main_window, capture_screenshot));
+ ui.action_Fullscreen->setShortcut(
+ hotkey_registry.GetHotkey(main_window, fullscreen, this)->key());
+ ui.action_Fullscreen->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, fullscreen));
+
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this),
&QShortcut::activated, this, &GMainWindow::OnMenuLoadFile);
connect(
@@ -671,24 +764,24 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this),
&QShortcut::activated, this, [&] {
- Settings::values.use_frame_limit = !Settings::values.use_frame_limit;
+ Settings::values.use_frame_limit.SetValue(
+ !Settings::values.use_frame_limit.GetValue());
UpdateStatusBar();
});
- // TODO: Remove this comment/static whenever the next major release of
- // MSVC occurs and we make it a requirement (see:
- // https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html)
- static constexpr u16 SPEED_LIMIT_STEP = 5;
+ constexpr u16 SPEED_LIMIT_STEP = 5;
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),
&QShortcut::activated, this, [&] {
- if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {
- Settings::values.frame_limit += SPEED_LIMIT_STEP;
+ if (Settings::values.frame_limit.GetValue() < 9999 - SPEED_LIMIT_STEP) {
+ Settings::values.frame_limit.SetValue(SPEED_LIMIT_STEP +
+ Settings::values.frame_limit.GetValue());
UpdateStatusBar();
}
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),
&QShortcut::activated, this, [&] {
- if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
- Settings::values.frame_limit -= SPEED_LIMIT_STEP;
+ if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) {
+ Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() -
+ SPEED_LIMIT_STEP);
UpdateStatusBar();
}
});
@@ -700,27 +793,31 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
&QShortcut::activated, this, [&] {
- if (emu_thread->IsRunning()) {
+ if (emu_thread != nullptr && emu_thread->IsRunning()) {
OnCaptureScreenshot();
}
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this),
&QShortcut::activated, this, [&] {
- Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
- OnDockedModeChanged(!Settings::values.use_docked_mode,
- Settings::values.use_docked_mode);
- dock_status_button->setChecked(Settings::values.use_docked_mode);
+ Settings::values.use_docked_mode.SetValue(
+ !Settings::values.use_docked_mode.GetValue());
+ OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+ Settings::values.use_docked_mode.GetValue());
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
});
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
+ &QShortcut::activated, this,
+ [] { Settings::values.audio_muted = !Settings::values.audio_muted; });
}
void GMainWindow::SetDefaultUIGeometry() {
- // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
+ // geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
const QRect screenRect = QApplication::desktop()->screenGeometry(this);
const int w = screenRect.width() * 2 / 3;
- const int h = screenRect.height() / 2;
+ const int h = screenRect.height() * 2 / 3;
const int x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
- const int y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
+ const int y = (screenRect.y() + screenRect.height()) / 2 - h * 53 / 100;
setGeometry(x, y, w, h);
}
@@ -745,7 +842,7 @@ void GMainWindow::RestoreUIState() {
OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked());
ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
- game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
+ game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
@@ -776,6 +873,9 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
&GMainWindow::OnTransferableShaderCacheOpenFile);
+ connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
+ &GMainWindow::OnGameListRemoveInstalledEntry);
+ connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
@@ -788,6 +888,9 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
+ connect(this, &GMainWindow::UpdateInstallProgress, this,
+ &GMainWindow::IncrementInstallProgress);
+
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, render_window,
@@ -802,10 +905,6 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
connect(ui.action_Install_File_NAND, &QAction::triggered, this,
&GMainWindow::OnMenuInstallToNAND);
- connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
- [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
- connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
- [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo);
@@ -815,8 +914,14 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
connect(ui.action_Report_Compatibility, &QAction::triggered, this,
&GMainWindow::OnMenuReportCompatibility);
+ connect(ui.action_Open_Mods_Page, &QAction::triggered, this, &GMainWindow::OnOpenModsPage);
+ connect(ui.action_Open_Quickstart_Guide, &QAction::triggered, this,
+ &GMainWindow::OnOpenQuickstartGuide);
+ connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ);
connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
+ connect(ui.action_Configure_Current_Game, &QAction::triggered, this,
+ &GMainWindow::OnConfigurePerGame);
// View
connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
@@ -825,12 +930,9 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::OnDisplayTitleBars);
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
+ connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize);
// Fullscreen
- ui.action_Fullscreen->setShortcut(
- hotkey_registry
- .GetHotkey(QStringLiteral("Main Window"), QStringLiteral("Fullscreen"), this)
- ->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Movie
@@ -889,15 +991,18 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetFilesystem(vfs);
system.SetAppletFrontendSet({
- nullptr, // Parental Controls
- std::make_unique<QtErrorDisplay>(*this), //
- nullptr, // Photo Viewer
- std::make_unique<QtProfileSelector>(*this), //
- std::make_unique<QtSoftwareKeyboard>(*this), //
- std::make_unique<QtWebBrowser>(*this), //
- nullptr, // E-Commerce
+ std::make_unique<QtControllerSelector>(*this), // Controller Selector
+ nullptr, // E-Commerce
+ std::make_unique<QtErrorDisplay>(*this), // Error Display
+ nullptr, // Parental Controls
+ nullptr, // Photo Viewer
+ std::make_unique<QtProfileSelector>(*this), // Profile Selector
+ std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
+ std::make_unique<QtWebBrowser>(*this), // Web Browser
});
+ system.RegisterHostThread();
+
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
const auto drd_callout =
@@ -940,16 +1045,18 @@ bool GMainWindow::LoadROM(const QString& filename) {
default:
if (static_cast<u32>(result) >
static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) {
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader);
const u16 error_id = static_cast<u16>(result) - loader_id;
+ const std::string error_code = fmt::format("({:04X}-{:04X})", loader_id, error_id);
+ LOG_CRITICAL(Frontend, "Failed to load ROM! {}", error_code);
QMessageBox::critical(
- this, tr("Error while loading ROM!"),
+ this,
+ tr("Error while loading ROM! ").append(QString::fromStdString(error_code)),
QString::fromStdString(fmt::format(
- "While attempting to load the ROM requested, an error occured. Please "
- "refer to the yuzu wiki for more information or the yuzu discord for "
- "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
- loader_id, error_id, static_cast<Loader::ResultStatus>(error_id))));
+ "{}<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the "
+ "yuzu quickstart guide</a> to redump your files.<br>You can refer "
+ "to the yuzu wiki</a> or the yuzu Discord</a> for help.",
+ static_cast<Loader::ResultStatus>(error_id))));
} else {
QMessageBox::critical(
this, tr("Error while loading ROM!"),
@@ -961,7 +1068,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
game_path = filename;
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
@@ -982,6 +1089,19 @@ void GMainWindow::BootGame(const QString& filename) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
+ u64 title_id{0};
+ auto& system = Core::System::GetInstance();
+ const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
+ const auto loader = Loader::GetLoader(system, v_file);
+ if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
+ // Load per game settings
+ Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig);
+ }
+
+ ConfigureVibration::SetAllVibrationDevices();
+
+ Settings::LogSettings();
+
if (UISettings::values.select_user_on_boot) {
SelectAndSetCurrentUser();
}
@@ -1006,30 +1126,42 @@ void GMainWindow::BootGame(const QString& filename) {
&LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
// Update the GUI
+ UpdateStatusButtons();
if (ui.action_Single_Window_Mode->isChecked()) {
game_list->hide();
game_list_placeholder->hide();
}
status_bar_update_timer.start(2000);
async_status_button->setDisabled(true);
+ multicore_status_button->setDisabled(true);
renderer_status_button->setDisabled(true);
- const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ if (UISettings::values.hide_mouse) {
+ mouse_hide_timer.start();
+ setMouseTracking(true);
+ ui.centralwidget->setMouseTracking(true);
+ }
std::string title_name;
- const auto res = Core::System::GetInstance().GetGameName(title_name);
- if (res != Loader::ResultStatus::Success) {
- const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata();
- if (nacp != nullptr)
- title_name = nacp->GetApplicationName();
+ std::string title_version;
+ const auto res = system.GetGameName(title_name);
- if (title_name.empty())
- title_name = FileUtil::GetFilename(filename.toStdString());
+ const auto metadata = [&system, title_id] {
+ const FileSys::PatchManager pm(title_id, system.GetFileSystemController(),
+ system.GetContentProvider());
+ return pm.GetControlMetadata();
+ }();
+ if (metadata.first != nullptr) {
+ title_version = metadata.first->GetVersionString();
+ title_name = metadata.first->GetApplicationName();
+ }
+ if (res != Loader::ResultStatus::Success || title_name.empty()) {
+ title_name = Common::FS::GetFilename(filename.toStdString());
}
- LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name);
- UpdateWindowTitle(QString::fromStdString(title_name));
+ LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
+ UpdateWindowTitle(title_name, title_version);
- loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
+ loading_screen->Prepare(system.GetAppLoader());
loading_screen->show();
emulation_running = true;
@@ -1070,26 +1202,33 @@ void GMainWindow::ShutdownGame() {
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
+ ui.action_Configure_Current_Game->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
ui.action_Capture_Screenshot->setEnabled(false);
render_window->hide();
loading_screen->hide();
loading_screen->Clear();
- if (game_list->isEmpty())
+ if (game_list->IsEmpty()) {
game_list_placeholder->show();
- else
+ } else {
game_list->show();
- game_list->setFilterFocus();
+ }
+ game_list->SetFilterFocus();
+
+ setMouseTracking(false);
+ ui.centralwidget->setMouseTracking(false);
UpdateWindowTitle();
// Disable status bar updates
status_bar_update_timer.stop();
+ shader_building_label->setVisible(false);
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false);
async_status_button->setEnabled(true);
+ multicore_status_button->setEnabled(true);
#ifdef HAS_VULKAN
renderer_status_button->setEnabled(true);
#endif
@@ -1137,50 +1276,82 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
BootGame(game_path);
}
-void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {
+void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path) {
std::string path;
QString open_target;
+ auto& system = Core::System::GetInstance();
+
+ const auto [user_save_size, device_save_size] = [this, &game_path, &program_id, &system] {
+ const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto control = pm.GetControlMetadata().first;
+ if (control != nullptr) {
+ return std::make_pair(control->GetDefaultNormalSaveSize(),
+ control->GetDeviceSaveDataSize());
+ } else {
+ const auto file = Core::GetGameFileFromPath(vfs, game_path);
+ const auto loader = Loader::GetLoader(system, file);
+
+ FileSys::NACP nacp{};
+ loader->ReadControlData(nacp);
+ return std::make_pair(nacp.GetDefaultNormalSaveSize(), nacp.GetDeviceSaveDataSize());
+ }
+ }();
+
+ const bool has_user_save{user_save_size > 0};
+ const bool has_device_save{device_save_size > 0};
+
+ ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?");
+
switch (target) {
case GameListOpenTarget::SaveData: {
open_target = tr("Save Data");
- const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
- ASSERT(program_id != 0);
+ const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
+
+ if (has_user_save) {
+ // User save data
+ const auto select_profile = [this] {
+ QtProfileSelectionDialog dialog(this);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowModality(Qt::WindowModal);
+
+ if (dialog.exec() == QDialog::Rejected) {
+ return -1;
+ }
- const auto select_profile = [this] {
- QtProfileSelectionDialog dialog(this);
- dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
- Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
- dialog.setWindowModality(Qt::WindowModal);
+ return dialog.GetIndex();
+ };
- if (dialog.exec() == QDialog::Rejected) {
- return -1;
+ const auto index = select_profile();
+ if (index == -1) {
+ return;
}
- return dialog.GetIndex();
- };
-
- const auto index = select_profile();
- if (index == -1) {
- return;
+ Service::Account::ProfileManager manager;
+ const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
+ ASSERT(user_id);
+ path = nand_dir + FileSys::SaveDataFactory::GetFullPath(
+ FileSys::SaveDataSpaceId::NandUser,
+ FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0);
+ } else {
+ // Device save data
+ path = nand_dir + FileSys::SaveDataFactory::GetFullPath(
+ FileSys::SaveDataSpaceId::NandUser,
+ FileSys::SaveDataType::SaveData, program_id, {}, 0);
}
- Service::Account::ProfileManager manager;
- const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
- ASSERT(user_id);
- path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
- FileSys::SaveDataType::SaveData,
- program_id, user_id->uuid, 0);
-
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path);
- FileUtil::CreateDir(path);
+ if (!Common::FS::Exists(path)) {
+ Common::FS::CreateFullPath(path);
+ Common::FS::CreateDir(path);
}
break;
}
case GameListOpenTarget::ModData: {
open_target = tr("Mod Data");
- const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
+ const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir);
path = fmt::format("{}{:016X}", load_dir, program_id);
break;
}
@@ -1201,14 +1372,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
}
void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
- ASSERT(program_id != 0);
-
const QString shader_dir =
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir));
- const QString tranferable_shader_cache_folder_path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
+ const QString transferable_shader_cache_folder_path =
shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
const QString transferable_shader_cache_file_path =
- tranferable_shader_cache_folder_path + QDir::separator() +
+ transferable_shader_cache_folder_path + QDir::separator() +
QString::fromStdString(fmt::format("{:016X}.bin", program_id));
if (!QFile::exists(transferable_shader_cache_file_path)) {
@@ -1229,7 +1398,7 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
param << QDir::toNativeSeparators(transferable_shader_cache_file_path);
QProcess::startDetached(explorer, param);
#else
- QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path));
+ QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path));
#endif
}
@@ -1273,6 +1442,175 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
return true;
}
+void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
+ const QString entry_type = [this, type] {
+ switch (type) {
+ case InstalledEntryType::Game:
+ return tr("Contents");
+ case InstalledEntryType::Update:
+ return tr("Update");
+ case InstalledEntryType::AddOnContent:
+ return tr("DLC");
+ default:
+ return QString{};
+ }
+ }();
+
+ if (QMessageBox::question(
+ this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ switch (type) {
+ case InstalledEntryType::Game:
+ RemoveBaseContent(program_id, entry_type);
+ [[fallthrough]];
+ case InstalledEntryType::Update:
+ RemoveUpdateContent(program_id, entry_type);
+ if (type != InstalledEntryType::Game) {
+ break;
+ }
+ [[fallthrough]];
+ case InstalledEntryType::AddOnContent:
+ RemoveAddOnContent(program_id, entry_type);
+ break;
+ }
+ Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ DIR_SEP + "game_list");
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+}
+
+void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) {
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
+
+ if (res) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the installed base game."));
+ } else {
+ QMessageBox::warning(
+ this, tr("Error Removing %1").arg(entry_type),
+ tr("The base game is not installed in the NAND and cannot be removed."));
+ }
+}
+
+void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) {
+ const auto update_id = program_id | 0x800;
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
+
+ if (res) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the installed update."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ tr("There is no update installed for this title."));
+ }
+}
+
+void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) {
+ u32 count{};
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter(
+ FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+
+ for (const auto& entry : dlc_entries) {
+ if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) {
+ const auto res =
+ fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id);
+ if (res) {
+ ++count;
+ }
+ }
+ }
+
+ if (count == 0) {
+ QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ tr("There are no DLC installed for this title."));
+ return;
+ }
+
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed %1 installed DLC.").arg(count));
+}
+
+void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) {
+ const QString question = [this, target] {
+ switch (target) {
+ case GameListRemoveTarget::ShaderCache:
+ return tr("Delete Transferable Shader Cache?");
+ case GameListRemoveTarget::CustomConfiguration:
+ return tr("Remove Custom Game Configuration?");
+ default:
+ return QString{};
+ }
+ }();
+
+ if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ switch (target) {
+ case GameListRemoveTarget::ShaderCache:
+ RemoveTransferableShaderCache(program_id);
+ break;
+ case GameListRemoveTarget::CustomConfiguration:
+ RemoveCustomConfiguration(program_id);
+ break;
+ }
+}
+
+void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
+ const QString shader_dir =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
+ const QString transferable_shader_cache_folder_path =
+ shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
+ const QString transferable_shader_cache_file_path =
+ transferable_shader_cache_folder_path + QDir::separator() +
+ QString::fromStdString(fmt::format("{:016X}.bin", program_id));
+
+ if (!QFile::exists(transferable_shader_cache_file_path)) {
+ QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+
+ if (QFile::remove(transferable_shader_cache_file_path)) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader cache."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"),
+ tr("Failed to remove the transferable shader cache."));
+ }
+}
+
+void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
+ const QString config_dir =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
+ const QString custom_config_file_path =
+ config_dir + QStringLiteral("custom") + QDir::separator() +
+ QString::fromStdString(fmt::format("{:016X}.ini", program_id));
+
+ if (!QFile::exists(custom_config_file_path)) {
+ QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
+ tr("A custom configuration for this title does not exist."));
+ return;
+ }
+
+ if (QFile::remove(custom_config_file_path)) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the custom game configuration."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
+ tr("Failed to remove the custom game configuration."));
+ }
+}
+
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
const auto failed = [this] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
@@ -1280,7 +1618,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
"cancelled the operation."));
};
- const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
+ auto& system = Core::System::GetInstance();
+ const auto loader = Loader::GetLoader(system, vfs->OpenFile(game_path, FileSys::Mode::Read));
if (loader == nullptr) {
failed();
return;
@@ -1292,7 +1631,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto& installed = Core::System::GetInstance().GetContentProvider();
+ const auto& installed = system.GetContentProvider();
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
if (!romfs_title_id) {
@@ -1301,12 +1640,14 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
}
const auto path = fmt::format(
- "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
+ "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id);
FileSys::VirtualFile romfs;
if (*romfs_title_id == program_id) {
- romfs = file;
+ const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
+ const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed};
+ romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program);
} else {
romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
}
@@ -1379,13 +1720,13 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("SDMC")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
"Nintendo/Contents/registered");
} else if (directory == QStringLiteral("UserNAND")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"user/Contents/registered");
} else if (directory == QStringLiteral("SysNAND")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"system/Contents/registered");
} else {
path = directory;
@@ -1399,8 +1740,10 @@ void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
void GMainWindow::OnGameListAddDirectory() {
const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
- if (dir_path.isEmpty())
+ if (dir_path.isEmpty()) {
return;
+ }
+
UISettings::GameDir game_dir{dir_path, false, true};
if (!UISettings::values.game_dirs.contains(game_dir)) {
UISettings::values.game_dirs.append(game_dir);
@@ -1420,26 +1763,15 @@ void GMainWindow::OnGameListShowList(bool show) {
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
u64 title_id{};
const auto v_file = Core::GetGameFileFromPath(vfs, file);
- const auto loader = Loader::GetLoader(v_file);
+ const auto loader = Loader::GetLoader(Core::System::GetInstance(), v_file);
+
if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"),
tr("The game properties could not be loaded."));
return;
}
- ConfigurePerGameGeneral dialog(this, title_id);
- dialog.LoadFromFile(v_file);
- auto result = dialog.exec();
- if (result == QDialog::Accepted) {
- dialog.ApplyConfiguration();
-
- const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
- if (reload) {
- game_list->PopulateAsync(UISettings::values.game_dirs);
- }
-
- config->Save();
- }
+ OpenPerGameConfiguration(title_id, file);
}
void GMainWindow::OnMenuLoadFile() {
@@ -1479,209 +1811,255 @@ void GMainWindow::OnMenuLoadFolder() {
}
}
+void GMainWindow::IncrementInstallProgress() {
+ install_progress->setValue(install_progress->value() + 1);
+}
+
void GMainWindow::OnMenuInstallToNAND() {
const QString file_filter =
tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
- "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge "
+ "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge "
"Image (*.xci)");
- QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
- UISettings::values.roms_path, file_filter);
- if (filename.isEmpty()) {
+ QStringList filenames = QFileDialog::getOpenFileNames(
+ this, tr("Install Files"), UISettings::values.roms_path, file_filter);
+
+ if (filenames.isEmpty()) {
+ return;
+ }
+
+ InstallDialog installDialog(this, filenames);
+ if (installDialog.exec() == QDialog::Rejected) {
+ return;
+ }
+
+ const QStringList files = installDialog.GetFiles();
+
+ if (files.isEmpty()) {
return;
}
+ int remaining = filenames.size();
+
+ // This would only overflow above 2^43 bytes (8.796 TB)
+ int total_size = 0;
+ for (const QString& file : files) {
+ total_size += static_cast<int>(QFile(file).size() / 0x1000);
+ }
+ if (total_size < 0) {
+ LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting.");
+ return;
+ }
+
+ QStringList new_files{}; // Newly installed files that do not yet exist in the NAND
+ QStringList overwritten_files{}; // Files that overwrote those existing in the NAND
+ QStringList failed_files{}; // Files that failed to install due to errors
+
+ ui.action_Install_File_NAND->setEnabled(false);
+
+ install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this);
+ install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint &
+ ~Qt::WindowMaximizeButtonHint);
+ install_progress->setAttribute(Qt::WA_DeleteOnClose, true);
+ install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40);
+ install_progress->show();
+
+ for (const QString& file : files) {
+ install_progress->setWindowTitle(tr("%n file(s) remaining", "", remaining));
+ install_progress->setLabelText(
+ tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName()));
+
+ QFuture<InstallResult> future;
+ InstallResult result;
+
+ if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
+ file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
+
+ future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); });
+
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+
+ result = future.result();
+
+ } else {
+ result = InstallNCA(file);
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+ switch (result) {
+ case InstallResult::Success:
+ new_files.append(QFileInfo(file).fileName());
+ break;
+ case InstallResult::Overwrite:
+ overwritten_files.append(QFileInfo(file).fileName());
+ break;
+ case InstallResult::Failure:
+ failed_files.append(QFileInfo(file).fileName());
+ break;
+ }
+
+ --remaining;
+ }
+
+ install_progress->close();
+
+ const QString install_results =
+ (new_files.isEmpty() ? QString{}
+ : tr("%n file(s) were newly installed\n", "", new_files.size())) +
+ (overwritten_files.isEmpty()
+ ? QString{}
+ : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) +
+ (failed_files.isEmpty() ? QString{}
+ : tr("%n file(s) failed to install\n", "", failed_files.size()));
+
+ QMessageBox::information(this, tr("Install Results"), install_results);
+ Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ DIR_SEP + "game_list");
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ ui.action_Install_File_NAND->setEnabled(true);
+}
+
+InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
const FileSys::VirtualFile& dest, std::size_t block_size) {
- if (src == nullptr || dest == nullptr)
+ if (src == nullptr || dest == nullptr) {
return false;
- if (!dest->Resize(src->GetSize()))
+ }
+ if (!dest->Resize(src->GetSize())) {
return false;
+ }
std::array<u8, 0x1000> buffer{};
- const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
-
- QProgressDialog progress(
- tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
- tr("Cancel"), 0, progress_maximum, this);
- progress.setWindowModality(Qt::WindowModal);
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- if (progress.wasCanceled()) {
+ if (install_progress->wasCanceled()) {
dest->Resize(0);
return false;
}
- const int progress_value = static_cast<int>(i / buffer.size());
- progress.setValue(progress_value);
+ emit UpdateInstallProgress();
const auto read = src->Read(buffer.data(), buffer.size(), i);
dest->Write(buffer.data(), read, i);
}
-
return true;
};
- const auto success = [this]() {
- QMessageBox::information(this, tr("Successfully Installed"),
- tr("The file was successfully installed."));
- game_list->PopulateAsync(UISettings::values.game_dirs);
- FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
- DIR_SEP + "game_list");
- };
-
- const auto failed = [this]() {
- QMessageBox::warning(
- this, tr("Failed to Install"),
- tr("There was an error while attempting to install the provided file. It "
- "could have an incorrect format or be missing metadata. Please "
- "double-check your file and try again."));
- };
-
- const auto overwrite = [this]() {
- return QMessageBox::question(this, tr("Failed to Install"),
- tr("The file you are attempting to install already exists "
- "in the cache. Would you like to overwrite it?")) ==
- QMessageBox::Yes;
- };
-
- if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
- filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- std::shared_ptr<FileSys::NSP> nsp;
- if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- nsp = std::make_shared<FileSys::NSP>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- if (nsp->IsExtractedType())
- failed();
- } else {
- const auto xci = std::make_shared<FileSys::XCI>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- nsp = xci->GetSecurePartitionNSP();
- }
-
- if (nsp->GetStatus() != Loader::ResultStatus::Success) {
- failed();
- return;
- }
- const auto res = Core::System::GetInstance()
- .GetFileSystemController()
- .GetUserNANDContents()
- ->InstallEntry(*nsp, false, qt_raw_copy);
- if (res == FileSys::InstallResult::Success) {
- success();
- } else {
- if (res == FileSys::InstallResult::ErrorAlreadyExists) {
- if (overwrite()) {
- const auto res2 = Core::System::GetInstance()
- .GetFileSystemController()
- .GetUserNANDContents()
- ->InstallEntry(*nsp, true, qt_raw_copy);
- if (res2 == FileSys::InstallResult::Success) {
- success();
- } else {
- failed();
- }
- }
- } else {
- failed();
- }
+ std::shared_ptr<FileSys::NSP> nsp;
+ if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
+ nsp = std::make_shared<FileSys::NSP>(
+ vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ if (nsp->IsExtractedType()) {
+ return InstallResult::Failure;
}
} else {
- const auto nca = std::make_shared<FileSys::NCA>(
+ const auto xci = std::make_shared<FileSys::XCI>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- const auto id = nca->GetStatus();
+ nsp = xci->GetSecurePartitionNSP();
+ }
- // Game updates necessary are missing base RomFS
- if (id != Loader::ResultStatus::Success &&
- id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
- failed();
- return;
- }
+ if (nsp->GetStatus() != Loader::ResultStatus::Success) {
+ return InstallResult::Failure;
+ }
+ const auto res =
+ Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry(
+ *nsp, true, qt_raw_copy);
+ if (res == FileSys::InstallResult::Success) {
+ return InstallResult::Success;
+ } else if (res == FileSys::InstallResult::OverwriteExisting) {
+ return InstallResult::Overwrite;
+ } else {
+ return InstallResult::Failure;
+ }
+}
- const QStringList tt_options{tr("System Application"),
- tr("System Archive"),
- tr("System Application Update"),
- tr("Firmware Package (Type A)"),
- tr("Firmware Package (Type B)"),
- tr("Game"),
- tr("Game Update"),
- tr("Game DLC"),
- tr("Delta Title")};
- bool ok;
- const auto item = QInputDialog::getItem(
- this, tr("Select NCA Install Type..."),
- tr("Please select the type of title you would like to install this NCA as:\n(In "
- "most instances, the default 'Game' is fine.)"),
- tt_options, 5, false, &ok);
-
- auto index = tt_options.indexOf(item);
- if (!ok || index == -1) {
- QMessageBox::warning(this, tr("Failed to Install"),
- tr("The title type you selected for the NCA is invalid."));
- return;
+InstallResult GMainWindow::InstallNCA(const QString& filename) {
+ const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
+ const FileSys::VirtualFile& dest, std::size_t block_size) {
+ if (src == nullptr || dest == nullptr) {
+ return false;
}
-
- // If index is equal to or past Game, add the jump in TitleType.
- if (index >= 5) {
- index += static_cast<size_t>(FileSys::TitleType::Application) -
- static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
+ if (!dest->Resize(src->GetSize())) {
+ return false;
}
- FileSys::InstallResult res;
- if (index >= static_cast<size_t>(FileSys::TitleType::Application)) {
- res = Core::System::GetInstance()
- .GetFileSystemController()
- .GetUserNANDContents()
- ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
- qt_raw_copy);
- } else {
- res = Core::System::GetInstance()
- .GetFileSystemController()
- .GetSystemNANDContents()
- ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
- qt_raw_copy);
- }
+ std::array<u8, 0x1000> buffer{};
- if (res == FileSys::InstallResult::Success) {
- success();
- } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
- if (overwrite()) {
- const auto res2 = Core::System::GetInstance()
- .GetFileSystemController()
- .GetUserNANDContents()
- ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
- true, qt_raw_copy);
- if (res2 == FileSys::InstallResult::Success) {
- success();
- } else {
- failed();
- }
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ if (install_progress->wasCanceled()) {
+ dest->Resize(0);
+ return false;
}
- } else {
- failed();
- }
- }
-}
-void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
- const auto res = QMessageBox::information(
- this, tr("Changing Emulated Directory"),
- tr("You are about to change the emulated %1 directory of the system. Please note "
- "that this does not also move the contents of the previous directory to the "
- "new one and you will have to do that yourself.")
- .arg(target == EmulatedDirectoryTarget::SDMC ? tr("SD card") : tr("NAND")),
- QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
+ emit UpdateInstallProgress();
- if (res == QMessageBox::Cancel)
- return;
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
+ }
+ return true;
+ };
- QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
- if (!dir_path.isEmpty()) {
- FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
- : FileUtil::UserPath::NANDDir,
- dir_path.toStdString());
- Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
- game_list->PopulateAsync(UISettings::values.game_dirs);
+ const auto nca =
+ std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ const auto id = nca->GetStatus();
+
+ // Game updates necessary are missing base RomFS
+ if (id != Loader::ResultStatus::Success &&
+ id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ return InstallResult::Failure;
+ }
+
+ const QStringList tt_options{tr("System Application"),
+ tr("System Archive"),
+ tr("System Application Update"),
+ tr("Firmware Package (Type A)"),
+ tr("Firmware Package (Type B)"),
+ tr("Game"),
+ tr("Game Update"),
+ tr("Game DLC"),
+ tr("Delta Title")};
+ bool ok;
+ const auto item = QInputDialog::getItem(
+ this, tr("Select NCA Install Type..."),
+ tr("Please select the type of title you would like to install this NCA as:\n(In "
+ "most instances, the default 'Game' is fine.)"),
+ tt_options, 5, false, &ok);
+
+ auto index = tt_options.indexOf(item);
+ if (!ok || index == -1) {
+ QMessageBox::warning(this, tr("Failed to Install"),
+ tr("The title type you selected for the NCA is invalid."));
+ return InstallResult::Failure;
+ }
+
+ // If index is equal to or past Game, add the jump in TitleType.
+ if (index >= 5) {
+ index += static_cast<size_t>(FileSys::TitleType::Application) -
+ static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
+ }
+
+ FileSys::InstallResult res;
+ if (index >= static_cast<s32>(FileSys::TitleType::Application)) {
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
+ } else {
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetSystemNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
+ }
+
+ if (res == FileSys::InstallResult::Success) {
+ return InstallResult::Success;
+ } else if (res == FileSys::InstallResult::OverwriteExisting) {
+ return InstallResult::Overwrite;
+ } else {
+ return InstallResult::Failure;
}
}
@@ -1707,6 +2085,7 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
+ qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
"Core::Frontend::SoftwareKeyboardParameters");
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
@@ -1722,6 +2101,7 @@ void GMainWindow::OnStartGame() {
ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true);
+ ui.action_Configure_Current_Game->setEnabled(true);
ui.action_Report_Compatibility->setEnabled(true);
discord_rpc->Update();
@@ -1747,6 +2127,9 @@ void GMainWindow::OnStopGame() {
}
ShutdownGame();
+
+ Settings::RestoreGlobalState();
+ UpdateStatusButtons();
}
void GMainWindow::OnLoadComplete() {
@@ -1772,6 +2155,26 @@ void GMainWindow::OnMenuReportCompatibility() {
}
}
+void GMainWindow::OpenURL(const QUrl& url) {
+ const bool open = QDesktopServices::openUrl(url);
+ if (!open) {
+ QMessageBox::warning(this, tr("Error opening URL"),
+ tr("Unable to open the URL \"%1\".").arg(url.toString()));
+ }
+}
+
+void GMainWindow::OnOpenModsPage() {
+ OpenURL(QUrl(QStringLiteral("https://github.com/yuzu-emu/yuzu/wiki/Switch-Mods")));
+}
+
+void GMainWindow::OnOpenQuickstartGuide() {
+ OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/help/quickstart/")));
+}
+
+void GMainWindow::OnOpenFAQ() {
+ OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/wiki/faq/")));
+}
+
void GMainWindow::ToggleFullscreen() {
if (!emulation_running) {
return;
@@ -1832,11 +2235,28 @@ void GMainWindow::ToggleWindowMode() {
}
}
+void GMainWindow::ResetWindowSize() {
+ const auto aspect_ratio = Layout::EmulationAspectRatio(
+ static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()),
+ static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width);
+ if (!ui.action_Single_Window_Mode->isChecked()) {
+ render_window->resize(Layout::ScreenUndocked::Height / aspect_ratio,
+ Layout::ScreenUndocked::Height);
+ } else {
+ resize(Layout::ScreenUndocked::Height / aspect_ratio,
+ Layout::ScreenUndocked::Height + menuBar()->height() +
+ (ui.action_Show_Status_Bar->isChecked() ? statusBar()->height() : 0));
+ }
+}
+
void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence;
- ConfigureDialog configure_dialog(this, hotkey_registry);
+ ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get());
+ connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
+ &GMainWindow::OnLanguageChanged);
+
const auto result = configure_dialog.exec();
if (result != QDialog::Accepted) {
return;
@@ -1859,12 +2279,46 @@ void GMainWindow::OnConfigure() {
config->Save();
- dock_status_button->setChecked(Settings::values.use_docked_mode);
- async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
-#ifdef HAS_VULKAN
- renderer_status_button->setChecked(Settings::values.renderer_backend ==
- Settings::RendererBackend::Vulkan);
-#endif
+ if (UISettings::values.hide_mouse && emulation_running) {
+ setMouseTracking(true);
+ ui.centralwidget->setMouseTracking(true);
+ mouse_hide_timer.start();
+ } else {
+ setMouseTracking(false);
+ ui.centralwidget->setMouseTracking(false);
+ }
+
+ UpdateStatusButtons();
+}
+
+void GMainWindow::OnConfigurePerGame() {
+ const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ OpenPerGameConfiguration(title_id, game_path.toStdString());
+}
+
+void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
+ const auto v_file = Core::GetGameFileFromPath(vfs, file_name);
+
+ ConfigurePerGame dialog(this, title_id);
+ dialog.LoadFromFile(v_file);
+ auto result = dialog.exec();
+ if (result == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+
+ const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
+ if (reload) {
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ }
+
+ // Do not cause the global config to write local settings into the config file
+ Settings::RestoreGlobalState();
+
+ if (!Core::System::GetInstance().IsPoweredOn()) {
+ config->Save();
+ }
+ } else {
+ Settings::RestoreGlobalState();
+ }
}
void GMainWindow::OnLoadAmiibo() {
@@ -1914,7 +2368,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
void GMainWindow::OnOpenYuzuFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir))));
}
void GMainWindow::OnAbout() {
@@ -1923,31 +2377,66 @@ void GMainWindow::OnAbout() {
}
void GMainWindow::OnToggleFilterBar() {
- game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
+ game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
if (ui.action_Show_Filter_Bar->isChecked()) {
- game_list->setFilterFocus();
+ game_list->SetFilterFocus();
} else {
- game_list->clearFilter();
+ game_list->ClearFilter();
}
}
void GMainWindow::OnCaptureScreenshot() {
OnPauseGame();
- QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path,
- tr("PNG Image (*.png)"));
- png_dialog.setAcceptMode(QFileDialog::AcceptSave);
- png_dialog.setDefaultSuffix(QStringLiteral("png"));
- if (png_dialog.exec()) {
- const QString path = png_dialog.selectedFiles().first();
- if (!path.isEmpty()) {
- UISettings::values.screenshot_path = QFileInfo(path).path();
- render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
+
+ const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ const auto screenshot_path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir));
+ const auto date =
+ QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz"));
+ QString filename = QStringLiteral("%1%2_%3.png")
+ .arg(screenshot_path)
+ .arg(title_id, 16, 16, QLatin1Char{'0'})
+ .arg(date);
+
+#ifdef _WIN32
+ if (UISettings::values.enable_screenshot_save_as) {
+ filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename,
+ tr("PNG Image (*.png)"));
+ if (filename.isEmpty()) {
+ OnStartGame();
+ return;
}
}
+#endif
+ render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, filename);
OnStartGame();
}
-void GMainWindow::UpdateWindowTitle(const QString& title_name) {
+// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
+void GMainWindow::MigrateConfigFiles() {
+ const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir);
+ const QDir config_dir = QDir(QString::fromStdString(config_dir_str));
+ const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini")));
+
+ Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str));
+ for (QStringList::const_iterator it = config_dir_list.constBegin();
+ it != config_dir_list.constEnd(); ++it) {
+ const auto filename = it->toStdString();
+ if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) {
+ continue;
+ }
+ const auto origin = fmt::format("{}{}", config_dir_str, filename);
+ const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename);
+ LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
+ if (!Common::FS::Rename(origin, destination)) {
+ // Delete the old config file if one already exists in the new location.
+ Common::FS::Delete(origin);
+ }
+ }
+}
+
+void GMainWindow::UpdateWindowTitle(const std::string& title_name,
+ const std::string& title_version) {
const auto full_name = std::string(Common::g_build_fullname);
const auto branch_name = std::string(Common::g_scm_branch);
const auto description = std::string(Common::g_scm_desc);
@@ -1956,7 +2445,7 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) {
const auto date =
QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();
- if (title_name.isEmpty()) {
+ if (title_name.empty()) {
const auto fmt = std::string(Common::g_title_bar_format_idle);
setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt,
full_name, branch_name, description,
@@ -1964,8 +2453,8 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) {
} else {
const auto fmt = std::string(Common::g_title_bar_format_running);
setWindowTitle(QString::fromStdString(
- fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name,
- description, title_name.toStdString(), date, build_id)));
+ fmt::format(fmt.empty() ? "yuzu {0}| {3} | {6} | {1}-{2}" : fmt, full_name, branch_name,
+ description, title_name, date, build_id, title_version)));
}
}
@@ -1976,22 +2465,69 @@ void GMainWindow::UpdateStatusBar() {
}
auto results = Core::System::GetInstance().GetAndResetPerfStats();
+ auto& shader_notify = Core::System::GetInstance().GPU().ShaderNotify();
+ const auto shaders_building = shader_notify.GetShadersBuilding();
- if (Settings::values.use_frame_limit) {
+ if (shaders_building != 0) {
+ shader_building_label->setText(
+ tr("Building: %n shader(s)", "", static_cast<int>(shaders_building)));
+ shader_building_label->setVisible(true);
+ } else {
+ shader_building_label->setVisible(false);
+ }
+
+ if (Settings::values.use_frame_limit.GetValue()) {
emu_speed_label->setText(tr("Speed: %1% / %2%")
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
- .arg(Settings::values.frame_limit));
+ .arg(Settings::values.frame_limit.GetValue()));
} else {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
}
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
- emu_speed_label->setVisible(true);
+ emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
game_fps_label->setVisible(true);
emu_frametime_label->setVisible(true);
}
+void GMainWindow::UpdateStatusButtons() {
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+ multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(
+ Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
+ Settings::values.use_multi_core.GetValue());
+ async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
+#ifdef HAS_VULKAN
+ renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
+ Settings::RendererBackend::Vulkan);
+#endif
+}
+
+void GMainWindow::HideMouseCursor() {
+ if (emu_thread == nullptr || UISettings::values.hide_mouse == false) {
+ mouse_hide_timer.stop();
+ ShowMouseCursor();
+ return;
+ }
+ setCursor(QCursor(Qt::BlankCursor));
+}
+
+void GMainWindow::ShowMouseCursor() {
+ unsetCursor();
+ if (emu_thread != nullptr && UISettings::values.hide_mouse) {
+ mouse_hide_timer.start();
+ }
+}
+
+void GMainWindow::mouseMoveEvent(QMouseEvent* event) {
+ ShowMouseCursor();
+}
+
+void GMainWindow::mousePressEvent(QMouseEvent* event) {
+ ShowMouseCursor();
+}
+
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
QMessageBox::StandardButton answer;
QString status_message;
@@ -2051,6 +2587,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
if (answer == QMessageBox::Yes) {
if (emu_thread) {
ShutdownGame();
+
+ Settings::RestoreGlobalState();
+ UpdateStatusButtons();
}
} else {
// Only show the message if the game is still running.
@@ -2065,57 +2604,60 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
const auto res = QMessageBox::information(
this, tr("Confirm Key Rederivation"),
- tr("You are about to force rederive all of your keys. \nIf you do not know what this "
- "means or what you are doing, \nthis is a potentially destructive action. \nPlease "
- "make sure this is what you want \nand optionally make backups.\n\nThis will delete "
+ tr("You are about to force rederive all of your keys. \nIf you do not know what "
+ "this "
+ "means or what you are doing, \nthis is a potentially destructive action. "
+ "\nPlease "
+ "make sure this is what you want \nand optionally make backups.\n\nThis will "
+ "delete "
"your autogenerated key files and re-run the key derivation module."),
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
if (res == QMessageBox::Cancel)
return;
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "prod.keys_autogenerated");
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "console.keys_autogenerated");
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "title.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "prod.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "console.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "title.keys_autogenerated");
}
- Core::Crypto::KeyManager keys{};
+ Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
if (keys.BaseDeriveNecessary()) {
Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
- FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
+ Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)};
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
- Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
- keys.DeriveETicket(pdm);
+
+ auto& system = Core::System::GetInstance();
+ system.GetFileSystemController().CreateFactories(*vfs);
+ keys.DeriveETicket(pdm, system.GetContentProvider());
};
QString errors;
if (!pdm.HasFuses()) {
- errors += tr("- Missing fuses - Cannot derive SBK\n");
+ errors += tr("Missing fuses");
}
if (!pdm.HasBoot0()) {
- errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
+ errors += tr(" - Missing BOOT0");
}
if (!pdm.HasPackage2()) {
- errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
+ errors += tr(" - Missing BCPKG2-1-Normal-Main");
}
if (!pdm.HasProdInfo()) {
- errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
+ errors += tr(" - Missing PRODINFO");
}
if (!errors.isEmpty()) {
QMessageBox::warning(
- this, tr("Warning Missing Derivation Components"),
- tr("The following are missing from your configuration that may hinder key "
- "derivation. It will be attempted but may not complete.<br><br>") +
- errors +
- tr("<br><br>You can get all of these and dump all of your games easily by "
- "following <a href='https://yuzu-emu.org/help/quickstart/'>the "
- "quickstart guide</a>. Alternatively, you can use another method of dumping "
- "to obtain all of your keys."));
+ this, tr("Derivation Components Missing"),
+ tr("Components are missing that may hinder key derivation from completing. "
+ "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu "
+ "quickstart guide</a> to get all your keys and "
+ "games.<br><br><small>(%1)</small>")
+ .arg(errors));
}
QProgressDialog prog;
@@ -2215,9 +2757,13 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
hotkey_registry.SaveHotkeys();
// Shutdown session if the emu thread is active...
- if (emu_thread != nullptr)
+ if (emu_thread != nullptr) {
ShutdownGame();
+ Settings::RestoreGlobalState();
+ UpdateStatusButtons();
+ }
+
render_window->close();
QWidget::closeEvent(event);
@@ -2348,6 +2894,43 @@ void GMainWindow::UpdateUITheme() {
QIcon::setThemeSearchPaths(theme_paths);
}
+void GMainWindow::LoadTranslation() {
+ // If the selected language is English, no need to install any translation
+ if (UISettings::values.language == QStringLiteral("en")) {
+ return;
+ }
+
+ bool loaded;
+
+ if (UISettings::values.language.isEmpty()) {
+ // If the selected language is empty, use system locale
+ loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
+ } else {
+ // Otherwise load from the specified file
+ loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
+ }
+
+ if (loaded) {
+ qApp->installTranslator(&translator);
+ } else {
+ UISettings::values.language = QStringLiteral("en");
+ }
+}
+
+void GMainWindow::OnLanguageChanged(const QString& locale) {
+ if (UISettings::values.language != QStringLiteral("en")) {
+ qApp->removeTranslator(&translator);
+ }
+
+ UISettings::values.language = locale;
+ LoadTranslation();
+ ui.retranslateUi(this);
+ UpdateWindowTitle();
+
+ if (emulation_running)
+ ui.action_Start->setText(tr("Continue"));
+}
+
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
#ifdef USE_DISCORD_PRESENCE
if (state) {
@@ -2376,9 +2959,9 @@ int main(int argc, char* argv[]) {
#ifdef __APPLE__
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
- // But since we require the working directory to be the executable path for the location of the
- // user folder in the Qt Frontend, we need to cd into that working directory
- const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
+ // But since we require the working directory to be the executable path for the location of
+ // the user folder in the Qt Frontend, we need to cd into that working directory
+ const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
@@ -2397,8 +2980,6 @@ int main(int argc, char* argv[]) {
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
&GMainWindow::OnAppFocusStateChanged);
- Settings::LogSettings();
-
int result = app.exec();
detached_tasks.WaitForAllTasks();
return result;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index a67125567..b380a66f3 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -10,6 +10,7 @@
#include <QMainWindow>
#include <QTimer>
+#include <QTranslator>
#include "common/common_types.h"
#include "core/core.h"
@@ -28,34 +29,48 @@ class MicroProfileDialog;
class ProfilerWidget;
class QLabel;
class QPushButton;
+class QProgressDialog;
class WaitTreeWidget;
enum class GameListOpenTarget;
+enum class GameListRemoveTarget;
+enum class InstalledEntryType;
class GameListPlaceholder;
namespace Core::Frontend {
+struct ControllerParameters;
struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
+namespace DiscordRPC {
+class DiscordInterface;
+}
+
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
+namespace InputCommon {
+class InputSubsystem;
+}
+
enum class EmulatedDirectoryTarget {
NAND,
SDMC,
};
+enum class InstallResult {
+ Success,
+ Overwrite,
+ Failure,
+};
+
enum class ReinitializeKeyBehavior {
NoWarning,
Warning,
};
-namespace DiscordRPC {
-class DiscordInterface;
-}
-
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -76,8 +91,6 @@ public:
GMainWindow();
~GMainWindow() override;
- std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
-
bool DropAction(QDropEvent* event);
void AcceptDropEvent(QDropEvent* event);
@@ -102,9 +115,14 @@ signals:
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
+ void UpdateInstallProgress();
+
+ void ControllerSelectorReconfigureFinished();
+
void ErrorDisplayFinished();
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
+
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
@@ -113,6 +131,8 @@ signals:
public slots:
void OnLoadComplete();
+ void ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters);
void ErrorDisplayDisplayError(QString body);
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
@@ -181,10 +201,16 @@ private slots:
void OnPauseGame();
void OnStopGame();
void OnMenuReportCompatibility();
+ void OnOpenModsPage();
+ void OnOpenQuickstartGuide();
+ void OnOpenFAQ();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
- void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
+ void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path);
void OnTransferableShaderCacheOpenFile(u64 program_id);
+ void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
+ void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
@@ -195,11 +221,11 @@ private slots:
void OnGameListOpenPerGameProperties(const std::string& file);
void OnMenuLoadFile();
void OnMenuLoadFolder();
+ void IncrementInstallProgress();
void OnMenuInstallToNAND();
- /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card
- void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
void OnMenuRecentFile();
void OnConfigure();
+ void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
void OnAbout();
@@ -210,17 +236,37 @@ private slots:
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
+ void ResetWindowSize();
void OnCaptureScreenshot();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
+ void OnLanguageChanged(const QString& locale);
private:
+ void RemoveBaseContent(u64 program_id, const QString& entry_type);
+ void RemoveUpdateContent(u64 program_id, const QString& entry_type);
+ void RemoveAddOnContent(u64 program_id, const QString& entry_type);
+ void RemoveTransferableShaderCache(u64 program_id);
+ void RemoveCustomConfiguration(u64 program_id);
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
- void UpdateWindowTitle(const QString& title_name = {});
+ InstallResult InstallNSPXCI(const QString& filename);
+ InstallResult InstallNCA(const QString& filename);
+ void MigrateConfigFiles();
+ void UpdateWindowTitle(const std::string& title_name = {},
+ const std::string& title_version = {});
void UpdateStatusBar();
+ void UpdateStatusButtons();
+ void HideMouseCursor();
+ void ShowMouseCursor();
+ void OpenURL(const QUrl& url);
+ void LoadTranslation();
+ void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
Ui::MainWindow ui;
+ std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
+
GRenderWindow* render_window;
GameList* game_list;
LoadingScreen* loading_screen;
@@ -229,10 +275,12 @@ private:
// Status bar elements
QLabel* message_label = nullptr;
+ QLabel* shader_building_label = nullptr;
QLabel* emu_speed_label = nullptr;
QLabel* game_fps_label = nullptr;
QLabel* emu_frametime_label = nullptr;
QPushButton* async_status_button = nullptr;
+ QPushButton* multicore_status_button = nullptr;
QPushButton* renderer_status_button = nullptr;
QPushButton* dock_status_button = nullptr;
QTimer status_bar_update_timer;
@@ -246,6 +294,7 @@ private:
QString game_path;
bool auto_paused = false;
+ QTimer mouse_hide_timer;
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
@@ -263,8 +312,15 @@ private:
HotkeyRegistry hotkey_registry;
+ QTranslator translator;
+
+ // Install progress dialog
+ QProgressDialog* install_progress;
+
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mousePressEvent(QMouseEvent* event) override;
};
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index a2c9e4547..2f3792247 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>1081</width>
- <height>730</height>
+ <width>1280</width>
+ <height>720</height>
</rect>
</property>
<property name="windowTitle">
@@ -44,7 +44,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>1081</width>
+ <width>1280</width>
<height>21</height>
</rect>
</property>
@@ -64,8 +64,6 @@
<addaction name="separator"/>
<addaction name="menu_recent_files"/>
<addaction name="separator"/>
- <addaction name="action_Select_NAND_Directory"/>
- <addaction name="action_Select_SDMC_Directory"/>
<addaction name="separator"/>
<addaction name="action_Load_Amiibo"/>
<addaction name="separator"/>
@@ -83,6 +81,7 @@
<addaction name="action_Restart"/>
<addaction name="separator"/>
<addaction name="action_Configure"/>
+ <addaction name="action_Configure_Current_Game"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
@@ -98,6 +97,7 @@
<addaction name="action_Display_Dock_Widget_Headers"/>
<addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/>
+ <addaction name="action_Reset_Window_Size"/>
<addaction name="separator"/>
<addaction name="menu_View_Debugging"/>
</widget>
@@ -114,6 +114,9 @@
<string>&amp;Help</string>
</property>
<addaction name="action_Report_Compatibility"/>
+ <addaction name="action_Open_Mods_Page"/>
+ <addaction name="action_Open_Quickstart_Guide"/>
+ <addaction name="action_Open_FAQ"/>
<addaction name="separator"/>
<addaction name="action_About"/>
</widget>
@@ -128,7 +131,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Install File to NAND...</string>
+ <string>Install Files to NAND...</string>
</property>
</action>
<action name="action_Load_File">
@@ -217,20 +220,9 @@
<string>Show Status Bar</string>
</property>
</action>
- <action name="action_Select_NAND_Directory">
+ <action name="action_Reset_Window_Size">
<property name="text">
- <string>Select NAND Directory...</string>
- </property>
- <property name="toolTip">
- <string>Selects a folder to use as the root of the emulated NAND</string>
- </property>
- </action>
- <action name="action_Select_SDMC_Directory">
- <property name="text">
- <string>Select SD Card Directory...</string>
- </property>
- <property name="toolTip">
- <string>Selects a folder to use as the root of the emulated SD card</string>
+ <string>Reset Window Size</string>
</property>
</action>
<action name="action_Fullscreen">
@@ -268,6 +260,21 @@
<bool>false</bool>
</property>
</action>
+ <action name="action_Open_Mods_Page">
+ <property name="text">
+ <string>Open Mods Page</string>
+ </property>
+ </action>
+ <action name="action_Open_Quickstart_Guide">
+ <property name="text">
+ <string>Open Quickstart Guide</string>
+ </property>
+ </action>
+ <action name="action_Open_FAQ">
+ <property name="text">
+ <string>FAQ</string>
+ </property>
+ </action>
<action name="action_Open_yuzu_Folder">
<property name="text">
<string>Open yuzu Folder</string>
@@ -281,6 +288,14 @@
<string>Capture Screenshot</string>
</property>
</action>
+ <action name="action_Configure_Current_Game">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Configure Current Game...</string>
+ </property>
+ </action>
</widget>
<resources/>
<connections/>
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 738c4b2fc..37499fc85 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -11,7 +11,10 @@ const Themes themes{{
{"Light Colorful", "colorful"},
{"Dark", "qdarkstyle"},
{"Dark Colorful", "colorful_dark"},
+ {"Midnight Blue", "qdarkstyle_midnight_blue"},
+ {"Midnight Blue Colorful", "colorful_midnight_blue"},
}};
Values values = {};
+
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index a675ecf4d..ce3945485 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -24,19 +24,19 @@ struct Shortcut {
ContextualShortcut shortcut;
};
-using Themes = std::array<std::pair<const char*, const char*>, 4>;
+using Themes = std::array<std::pair<const char*, const char*>, 6>;
extern const Themes themes;
struct GameDir {
QString path;
- bool deep_scan;
- bool expanded;
+ bool deep_scan = false;
+ bool expanded = false;
bool operator==(const GameDir& rhs) const {
return path == rhs.path;
- };
+ }
bool operator!=(const GameDir& rhs) const {
return !operator==(rhs);
- };
+ }
};
struct Values {
@@ -59,21 +59,23 @@ struct Values {
bool confirm_before_closing;
bool first_start;
bool pause_when_in_background;
+ bool hide_mouse;
bool select_user_on_boot;
// Discord RPC
bool enable_discord_presence;
+ bool enable_screenshot_save_as;
u16 screenshot_resolution_factor;
QString roms_path;
QString symbols_path;
- QString screenshot_path;
QString game_dir_deprecated;
bool game_dir_deprecated_deepscan;
QVector<UISettings::GameDir> game_dirs;
QStringList recent_files;
+ QString language;
QString theme;
@@ -85,9 +87,6 @@ struct Values {
// logging
bool show_console;
- // Controllers
- int profile_index;
-
// Game List
bool show_add_ons;
uint32_t icon_size;
@@ -98,6 +97,7 @@ struct Values {
};
extern Values values;
+
} // namespace UISettings
Q_DECLARE_METATYPE(UISettings::GameDir*);
diff --git a/src/yuzu/yuzu.rc b/src/yuzu/yuzu.rc
index 1b253653f..4a3645a71 100644
--- a/src/yuzu/yuzu.rc
+++ b/src/yuzu/yuzu.rc
@@ -16,4 +16,4 @@ IDI_ICON1 ICON "../../dist/yuzu.ico"
// RT_MANIFEST
//
-1 RT_MANIFEST "../../dist/yuzu.manifest"
+0 RT_MANIFEST "../../dist/yuzu.manifest"
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index a15719a0f..57f9916f6 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -39,7 +39,5 @@ endif()
if (MSVC)
include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
copy_yuzu_SDL_deps(yuzu-cmd)
- copy_yuzu_unicorn_deps(yuzu-cmd)
endif()
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index f4cd905c9..e1adbbf2b 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -16,9 +16,11 @@
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/default_ini.h"
+namespace FS = Common::FS;
+
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini";
+ sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@@ -31,8 +33,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
- FileUtil::CreateFullPath(location);
- FileUtil::WriteStringToFile(true, location, default_contents);
+ FS::CreateFullPath(location);
+ FS::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@@ -226,24 +228,24 @@ static const std::array<int, 8> keyboard_mods{
void Config::ReadValues() {
// Controls
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
const auto group = fmt::format("ControlsP{}", p);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- Settings::values.players[p].buttons[i] =
+ Settings::values.players.GetValue()[p].buttons[i] =
sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
- if (Settings::values.players[p].buttons[i].empty())
- Settings::values.players[p].buttons[i] = default_param;
+ if (Settings::values.players.GetValue()[p].buttons[i].empty())
+ Settings::values.players.GetValue()[p].buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
- Settings::values.players[p].analogs[i] =
+ Settings::values.players.GetValue()[p].analogs[i] =
sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
- if (Settings::values.players[p].analogs[i].empty())
- Settings::values.players[p].analogs[i] = default_param;
+ if (Settings::values.players.GetValue()[p].analogs[i].empty())
+ Settings::values.players.GetValue()[p].analogs[i] = default_param;
}
}
@@ -286,6 +288,12 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = default_param;
}
+ Settings::values.vibration_enabled.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true));
+ Settings::values.enable_accurate_vibrations.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false));
+ Settings::values.motion_enabled.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true));
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.device =
@@ -315,38 +323,30 @@ void Config::ReadValues() {
// Data Storage
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir,
- sdl2_config->Get("Data Storage", "load_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir,
- sdl2_config->Get("Data Storage", "dump_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
- sdl2_config->Get("Data Storage", "cache_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ FS::GetUserPath(
+ FS::UserPath::NANDDir,
+ sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir)));
+ FS::GetUserPath(
+ FS::UserPath::SDMCDir,
+ sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir)));
+ FS::GetUserPath(
+ FS::UserPath::LoadDir,
+ sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir)));
+ FS::GetUserPath(
+ FS::UserPath::DumpDir,
+ sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir)));
+ FS::GetUserPath(FS::UserPath::CacheDir,
+ sdl2_config->Get("Data Storage", "cache_directory",
+ FS::GetUserPath(FS::UserPath::CacheDir)));
Settings::values.gamecard_inserted =
sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
Settings::values.gamecard_current_game =
sdl2_config->GetBoolean("Data Storage", "gamecard_current_game", false);
Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
- Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(sdl2_config->GetInteger(
- "Data Storage", "nand_total_size", static_cast<long>(Settings::NANDTotalSize::S29_1GB)));
- Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(sdl2_config->GetInteger(
- "Data Storage", "nand_user_size", static_cast<long>(Settings::NANDUserSize::S26GB)));
- Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
- sdl2_config->GetInteger("Data Storage", "nand_system_size",
- static_cast<long>(Settings::NANDSystemSize::S2_5GB)));
- Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(sdl2_config->GetInteger(
- "Data Storage", "sdmc_size", static_cast<long>(Settings::SDMCSize::S16GB)));
// System
- Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
+ Settings::values.use_docked_mode.SetValue(
+ sdl2_config->GetBoolean("System", "use_docked_mode", false));
const auto size = sdl2_config->GetInteger("System", "users_size", 0);
Settings::values.current_user = std::clamp<int>(
@@ -354,60 +354,76 @@ void Config::ReadValues() {
const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
- Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0);
+ Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
} else {
- Settings::values.rng_seed = std::nullopt;
+ Settings::values.rng_seed.SetValue(std::nullopt);
}
const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) {
- Settings::values.custom_rtc =
- std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0));
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)));
} else {
- Settings::values.custom_rtc = std::nullopt;
+ Settings::values.custom_rtc.SetValue(std::nullopt);
}
+ Settings::values.language_index.SetValue(
+ sdl2_config->GetInteger("System", "language_index", 1));
+ Settings::values.time_zone_index.SetValue(
+ sdl2_config->GetInteger("System", "time_zone_index", 0));
+
// Core
- Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
+ Settings::values.use_multi_core.SetValue(
+ sdl2_config->GetBoolean("Core", "use_multi_core", true));
// Renderer
const int renderer_backend = sdl2_config->GetInteger(
"Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
- Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend);
+ Settings::values.renderer_backend.SetValue(
+ static_cast<Settings::RendererBackend>(renderer_backend));
Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false);
- Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0);
-
- Settings::values.resolution_factor =
- static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
- Settings::values.aspect_ratio =
- static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0));
- Settings::values.max_anisotropy =
- static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0));
- Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
- Settings::values.frame_limit =
- static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
- Settings::values.use_disk_shader_cache =
- sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false);
- Settings::values.use_accurate_gpu_emulation =
- sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
- Settings::values.use_asynchronous_gpu_emulation =
- sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false);
- Settings::values.use_vsync =
- static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1));
-
- Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
- Settings::values.bg_green =
- static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
- Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
+ Settings::values.vulkan_device.SetValue(
+ sdl2_config->GetInteger("Renderer", "vulkan_device", 0));
+
+ Settings::values.aspect_ratio.SetValue(
+ static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)));
+ Settings::values.max_anisotropy.SetValue(
+ static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)));
+ Settings::values.use_frame_limit.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_frame_limit", true));
+ Settings::values.frame_limit.SetValue(
+ static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)));
+ Settings::values.use_disk_shader_cache.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
+ const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
+ Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true));
+ Settings::values.use_vsync.SetValue(
+ static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)));
+ Settings::values.use_assembly_shaders.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true));
+ Settings::values.use_asynchronous_shaders.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false));
+ Settings::values.use_asynchronous_shaders.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false));
+ Settings::values.use_fast_gpu_time.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
+
+ Settings::values.bg_red.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)));
+ Settings::values.bg_green.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)));
+ Settings::values.bg_blue.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)));
// Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
- Settings::values.enable_audio_stretching =
- sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
+ Settings::values.enable_audio_stretching.SetValue(
+ sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true));
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
- Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1));
-
- Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
+ Settings::values.volume.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1)));
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
@@ -425,6 +441,8 @@ void Config::ReadValues() {
Settings::values.reporting_services =
sdl2_config->GetBoolean("Debugging", "reporting_services", false);
Settings::values.quest_flag = sdl2_config->GetBoolean("Debugging", "quest_flag", false);
+ Settings::values.disable_macro_jit =
+ sdl2_config->GetBoolean("Debugging", "disable_macro_jit", false);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index d63d7a58e..bcbbcd4ca 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -65,6 +65,14 @@ button_screenshot=
lstick=
rstick=
+# Whether to enable or disable vibration
+# 0: Disabled, 1 (default): Enabled
+vibration_enabled=
+
+# Whether to enable or disable accurate vibrations
+# 0 (default): Disabled, 1: Enabled
+enable_accurate_vibrations=
+
# for motion input, the following devices are available:
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
# - "update_period": update period in milliseconds (default to 100)
@@ -94,9 +102,42 @@ udp_pad_index=
[Core]
# Whether to use multi-core for CPU emulation
-# 0 (default): Disabled, 1: Enabled
+# 0: Disabled, 1 (default): Enabled
use_multi_core=
+[Cpu]
+# Enable inline page tables optimization (faster guest memory access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_page_tables =
+
+# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_block_linking =
+
+# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_return_stack_buffer =
+
+# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_fast_dispatcher =
+
+# Enable context elimination CPU Optimization (reduce host memory use for guest context)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_context_elimination =
+
+# Enable constant propagation CPU optimization (basic IR optimization)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_const_prop =
+
+# Enable miscellaneous CPU optimizations (basic IR optimization)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_misc_ir =
+
+# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_reduce_misalign_checks =
+
[Renderer]
# Which backend API to use.
# 0 (default): OpenGL, 1: Vulkan
@@ -117,11 +158,6 @@ use_hw_renderer =
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit =
-# Resolution scale factor
-# 0: Auto (scales resolution to window size), 1: Native Switch screen resolution, Otherwise a scale
-# factor for the Switch resolution
-resolution_factor =
-
# Aspect ratio
# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window
aspect_ratio =
@@ -134,6 +170,14 @@ max_anisotropy =
# 0 (default): Off, 1: On
use_vsync =
+# Whether to use OpenGL assembly shaders or not. NV_gpu_program5 is required.
+# 0: Off, 1 (default): On
+use_assembly_shaders =
+
+# Whether to allow asynchronous shader building.
+# 0 (default): Off, 1: On
+use_asynchronous_shaders =
+
# Turns on the frame limiter, which will limit frames output to the target game speed
# 0: Off, 1: On (default)
use_frame_limit =
@@ -146,9 +190,9 @@ frame_limit =
# 0 (default): Off, 1 : On
use_disk_shader_cache =
-# Whether to use accurate GPU emulation
-# 0 (default): Off (fast), 1 : On (slow)
-use_accurate_gpu_emulation =
+# Which gpu accuracy level to use
+# 0 (Normal), 1 (High), 2 (Extreme)
+gpu_accuracy =
# Whether to use asynchronous GPU emulation
# 0 : Off (slow), 1 (default): On (fast)
@@ -262,6 +306,10 @@ language_index =
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value =
+# The system time zone that yuzu will use during emulation
+# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
+time_zone_index =
+
[Miscellaneous]
# A filter which removes logs below a certain logging level.
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
@@ -280,6 +328,8 @@ dump_nso=false
# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
# false: Retail/Normal Mode (default), true: Kiosk Mode
quest_flag =
+# Enables/Disables the macro JIT compiler
+disable_macro_jit=false
[WebService]
# Whether or not to enable telemetry
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 19584360c..521209622 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -13,23 +13,24 @@
#include "input_common/sdl/sdl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
-EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} {
+EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)
+ : input_subsystem{input_subsystem_} {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
exit(1);
}
- InputCommon::Init();
+ input_subsystem->Initialize();
SDL_SetMainReady();
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
SDL_Quit();
}
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
- InputCommon::GetMotionEmu()->Tilt(x, y);
+ input_subsystem->GetMotionEmu()->Tilt(x, y);
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
@@ -41,9 +42,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
}
} else if (button == SDL_BUTTON_RIGHT) {
if (state == SDL_PRESSED) {
- InputCommon::GetMotionEmu()->BeginTilt(x, y);
+ input_subsystem->GetMotionEmu()->BeginTilt(x, y);
} else {
- InputCommon::GetMotionEmu()->EndTilt();
+ input_subsystem->GetMotionEmu()->EndTilt();
}
}
}
@@ -79,9 +80,9 @@ void EmuWindow_SDL2::OnFingerUp() {
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
- InputCommon::GetKeyboard()->PressKey(key);
+ input_subsystem->GetKeyboard()->PressKey(key);
} else if (state == SDL_RELEASED) {
- InputCommon::GetKeyboard()->ReleaseKey(key);
+ input_subsystem->GetKeyboard()->ReleaseKey(key);
}
}
@@ -181,9 +182,10 @@ void EmuWindow_SDL2::PollEvents() {
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
- const auto title = fmt::format(
- "yuzu {} | {}-{} | FPS: {:.0f} ({:.0%})", Common::g_build_fullname,
- Common::g_scm_branch, Common::g_scm_desc, results.game_fps, results.emulation_speed);
+ const auto title =
+ fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
+ Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
+ results.emulation_speed * 100.0);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index fffac4252..53d756c3c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -14,9 +14,13 @@ namespace Core {
class System;
}
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
public:
- explicit EmuWindow_SDL2(Core::System& system, bool fullscreen);
+ explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem);
~EmuWindow_SDL2();
/// Polls window events
@@ -28,9 +32,6 @@ public:
/// Returns if window is shown (not minimized)
bool IsShown() const override;
- /// Presents the next frame
- virtual void Present() = 0;
-
protected:
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
@@ -62,9 +63,6 @@ protected:
/// Called when a configuration change affects the minimal size of the window
void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
- /// Instance of the system, used to access renderer for the presentation thread
- Core::System& system;
-
/// Is the window still open?
bool is_open = true;
@@ -76,4 +74,7 @@ protected:
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
+
+ /// Input subsystem to use with this window.
+ InputCommon::InputSubsystem* input_subsystem;
};
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 411e7e647..5f35233b5 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -87,8 +87,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
return unsupported_ext.empty();
}
-EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen)
- : EmuWindow_SDL2{system, fullscreen} {
+EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen)
+ : EmuWindow_SDL2{input_subsystem} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
@@ -98,6 +98,9 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen)
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+ if (Settings::values.renderer_debug) {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
+ }
SDL_GL_SetSwapInterval(0);
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
@@ -159,13 +162,3 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>();
}
-
-void EmuWindow_SDL2_GL::Present() {
- SDL_GL_MakeCurrent(render_window, window_context);
- SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0);
- while (IsOpen()) {
- system.Renderer().TryPresent(100);
- SDL_GL_SwapWindow(render_window);
- }
- SDL_GL_MakeCurrent(render_window, nullptr);
-}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 48bb41683..dba5c293c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -8,13 +8,15 @@
#include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen);
+ explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen);
~EmuWindow_SDL2_GL();
- void Present() override;
-
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
private:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index f2990910e..3ba657c00 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -19,8 +19,8 @@
#include <SDL.h>
#include <SDL_syswm.h>
-EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
- : EmuWindow_SDL2{system, fullscreen} {
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem)
+ : EmuWindow_SDL2{input_subsystem} {
const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
@@ -29,6 +29,7 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SysWMinfo wm;
+ SDL_VERSION(&wm.version);
if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
LOG_CRITICAL(Frontend, "Failed to get information from the window manager");
std::exit(EXIT_FAILURE);
@@ -70,9 +71,5 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
- return nullptr;
-}
-
-void EmuWindow_SDL2_VK::Present() {
- // TODO (bunnei): ImplementMe
+ return std::make_unique<DummyContext>();
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index b8021ebea..bdfdc3c6f 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -13,12 +13,16 @@ namespace Core {
class System;
}
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen);
- ~EmuWindow_SDL2_VK();
-
- void Present() override;
+ explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem);
+ ~EmuWindow_SDL2_VK() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
};
+
+class DummyContext : public Core::Frontend::GraphicsContext {};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 4d2ea7e9e..14a23c71b 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <chrono>
#include <iostream>
#include <memory>
#include <string>
@@ -22,12 +23,15 @@
#include "common/telemetry.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "input_common/main.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
@@ -36,8 +40,6 @@
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
#endif
-#include "core/file_sys/registered_cache.h"
-
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
@@ -81,8 +83,8 @@ static void InitializeLogging() {
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -178,15 +180,16 @@ int main(int argc, char** argv) {
Settings::Apply();
Core::System& system{Core::System::GetInstance()};
+ InputCommon::InputSubsystem input_subsystem;
std::unique_ptr<EmuWindow_SDL2> emu_window;
- switch (Settings::values.renderer_backend) {
+ switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen);
+ emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, fullscreen);
break;
case Settings::RendererBackend::Vulkan:
#ifdef HAS_VULKAN
- emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen);
+ emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem);
break;
#else
LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
@@ -228,19 +231,20 @@ int main(int argc, char** argv) {
}
}
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
+ system.Renderer().Rasterizer().LoadDiskResources(
+ system.CurrentProcess()->GetTitleID(), false,
+ [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
- std::thread render_thread([&emu_window] { emu_window->Present(); });
+ void(system.Run());
while (emu_window->IsOpen()) {
- system.RunLoop();
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
- render_thread.join();
-
+ void(system.Pause());
system.Shutdown();
detached_tasks.WaitForAllTasks();
diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc
index 7de8ef3d9..0cde75e2f 100644
--- a/src/yuzu_cmd/yuzu.rc
+++ b/src/yuzu_cmd/yuzu.rc
@@ -14,4 +14,4 @@ YUZU_ICON ICON "../../dist/yuzu.ico"
// RT_MANIFEST
//
-1 RT_MANIFEST "../../dist/yuzu.manifest"
+0 RT_MANIFEST "../../dist/yuzu.manifest"
diff --git a/src/yuzu_tester/CMakeLists.txt b/src/yuzu_tester/CMakeLists.txt
index 06c2ee011..d8a2a1511 100644
--- a/src/yuzu_tester/CMakeLists.txt
+++ b/src/yuzu_tester/CMakeLists.txt
@@ -28,7 +28,5 @@ endif()
if (MSVC)
include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
copy_yuzu_SDL_deps(yuzu-tester)
- copy_yuzu_unicorn_deps(yuzu-tester)
endif()
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index ee2591c8f..b6cdc7c1c 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -15,10 +15,11 @@
#include "yuzu_tester/config.h"
#include "yuzu_tester/default_ini.h"
+namespace FS = Common::FS;
+
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc =
- FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini";
+ sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@@ -31,8 +32,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
- FileUtil::CreateFullPath(location);
- FileUtil::WriteStringToFile(true, default_contents, location);
+ FS::CreateFullPath(location);
+ FS::WriteStringToFile(true, default_contents, location);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@@ -46,13 +47,13 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
void Config::ReadValues() {
// Controls
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- Settings::values.players[p].buttons[i] = "";
+ Settings::values.players.GetValue()[p].buttons[i] = "";
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- Settings::values.players[p].analogs[i] = "";
+ Settings::values.players.GetValue()[p].analogs[i] = "";
}
}
@@ -74,6 +75,9 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = "";
}
+ Settings::values.vibration_enabled.SetValue(true);
+ Settings::values.enable_accurate_vibrations.SetValue(false);
+ Settings::values.motion_enabled.SetValue(true);
Settings::values.touchscreen.enabled = "";
Settings::values.touchscreen.device = "";
Settings::values.touchscreen.finger = 0;
@@ -81,68 +85,73 @@ void Config::ReadValues() {
Settings::values.touchscreen.diameter_x = 15;
Settings::values.touchscreen.diameter_y = 15;
+ Settings::values.use_docked_mode.SetValue(
+ sdl2_config->GetBoolean("Controls", "use_docked_mode", false));
+
// Data Storage
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ FS::GetUserPath(Common::FS::UserPath::NANDDir,
+ sdl2_config->Get("Data Storage", "nand_directory",
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
+ FS::GetUserPath(Common::FS::UserPath::SDMCDir,
+ sdl2_config->Get("Data Storage", "sdmc_directory",
+ Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
// System
- Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
-
Settings::values.current_user = std::clamp<int>(
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
- Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0);
+ Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
} else {
- Settings::values.rng_seed = std::nullopt;
+ Settings::values.rng_seed.SetValue(std::nullopt);
}
const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) {
- Settings::values.custom_rtc =
- std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0));
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)));
} else {
- Settings::values.custom_rtc = std::nullopt;
+ Settings::values.custom_rtc.SetValue(std::nullopt);
}
// Core
- Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
+ Settings::values.use_multi_core.SetValue(
+ sdl2_config->GetBoolean("Core", "use_multi_core", false));
// Renderer
- Settings::values.resolution_factor =
- static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
- Settings::values.aspect_ratio =
- static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0));
- Settings::values.max_anisotropy =
- static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0));
- Settings::values.use_frame_limit = false;
- Settings::values.frame_limit = 100;
- Settings::values.use_disk_shader_cache =
- sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false);
- Settings::values.use_accurate_gpu_emulation =
- sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
- Settings::values.use_asynchronous_gpu_emulation =
- sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false);
-
- Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
- Settings::values.bg_green =
- static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
- Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
+ Settings::values.aspect_ratio.SetValue(
+ static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)));
+ Settings::values.max_anisotropy.SetValue(
+ static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)));
+ Settings::values.use_frame_limit.SetValue(false);
+ Settings::values.frame_limit.SetValue(100);
+ Settings::values.use_disk_shader_cache.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
+ const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
+ Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false));
+ Settings::values.use_fast_gpu_time.SetValue(
+ sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
+
+ Settings::values.bg_red.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)));
+ Settings::values.bg_green.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)));
+ Settings::values.bg_blue.SetValue(
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)));
// Audio
Settings::values.sink_id = "null";
- Settings::values.enable_audio_stretching = false;
+ Settings::values.enable_audio_stretching.SetValue(false);
Settings::values.audio_device_id = "auto";
- Settings::values.volume = 0;
+ Settings::values.volume.SetValue(0);
- Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
+ Settings::values.language_index.SetValue(
+ sdl2_config->GetInteger("System", "language_index", 1));
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h
index ca203b64d..3eb64e9d7 100644
--- a/src/yuzu_tester/default_ini.h
+++ b/src/yuzu_tester/default_ini.h
@@ -12,6 +12,39 @@ const char* sdl2_config_file = R"(
# 0 (default): Disabled, 1: Enabled
use_multi_core=
+[Cpu]
+# Enable inline page tables optimization (faster guest memory access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_page_tables =
+
+# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_block_linking =
+
+# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_return_stack_buffer =
+
+# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_fast_dispatcher =
+
+# Enable context elimination CPU Optimization (reduce host memory use for guest context)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_context_elimination =
+
+# Enable constant propagation CPU optimization (basic IR optimization)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_const_prop =
+
+# Enable miscellaneous CPU optimizations (basic IR optimization)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_misc_ir =
+
+# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
+# 0: Disabled, 1 (default): Enabled
+cpuopt_reduce_misalign_checks =
+
[Renderer]
# Whether to use software or hardware rendering.
# 0: Software, 1 (default): Hardware
@@ -21,11 +54,6 @@ use_hw_renderer =
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit =
-# Resolution scale factor
-# 0: Auto (scales resolution to window size), 1: Native Switch screen resolution, Otherwise a scale
-# factor for the Switch resolution
-resolution_factor =
-
# Aspect ratio
# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window
aspect_ratio =
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index 8584f6671..78f75fb38 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -13,7 +13,6 @@
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "core/settings.h"
@@ -53,7 +52,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
exit(1);
}
- InputCommon::Init();
+ input_subsystem->Initialize();
SDL_SetMainReady();
@@ -105,7 +104,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
}
EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index c13a82df2..a553b4b95 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -8,6 +8,10 @@
struct SDL_Window;
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2_Hide();
@@ -25,6 +29,8 @@ private:
/// Whether the GPU and driver supports the OpenGL extension required
bool SupportsRequiredGLExtensions();
+ std::unique_ptr<InputCommon::InputSubsystem> input_subsystem;
+
/// Internal SDL2 render window
SDL_Window* render_window;
diff --git a/src/yuzu_tester/service/yuzutest.cpp b/src/yuzu_tester/service/yuzutest.cpp
index 85d3f436b..2d3f6e3a7 100644
--- a/src/yuzu_tester/service/yuzutest.cpp
+++ b/src/yuzu_tester/service/yuzutest.cpp
@@ -53,7 +53,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(write_size);
+ rb.Push<u32>(static_cast<u32>(write_size));
}
void StartIndividual(Kernel::HLERequestContext& ctx) {
diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp
index 676e70ebd..88e4bd1f7 100644
--- a/src/yuzu_tester/yuzu.cpp
+++ b/src/yuzu_tester/yuzu.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <chrono>
#include <iostream>
#include <memory>
#include <string>
@@ -78,8 +79,8 @@ static void InitializeLogging(bool console) {
if (console)
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -250,14 +251,16 @@ int main(int argc, char** argv) {
Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback);
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend",
+ "SDLHideTester");
system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
+ void(system.Run());
while (!finished) {
- system.RunLoop();
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
+ void(system.Pause());
detached_tasks.WaitForAllTasks();
return return_value;
diff --git a/src/yuzu_tester/yuzu.rc b/src/yuzu_tester/yuzu.rc
index 7de8ef3d9..0cde75e2f 100644
--- a/src/yuzu_tester/yuzu.rc
+++ b/src/yuzu_tester/yuzu.rc
@@ -14,4 +14,4 @@ YUZU_ICON ICON "../../dist/yuzu.ico"
// RT_MANIFEST
//
-1 RT_MANIFEST "../../dist/yuzu.manifest"
+0 RT_MANIFEST "../../dist/yuzu.manifest"