summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dist/license.md31
-rw-r--r--dist/qt_themes/colorful/icons/16x16/lock.pngbin0 -> 330 bytes
-rw-r--r--dist/qt_themes/colorful/icons/256x256/plus_folder.pngbin0 -> 4643 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/bad_folder.pngbin0 -> 15494 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/chip.pngbin0 -> 582 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/folder.pngbin0 -> 460 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/plus.pngbin0 -> 496 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/sd_card.pngbin0 -> 680 bytes
-rw-r--r--dist/qt_themes/colorful/icons/index.theme14
-rw-r--r--dist/qt_themes/colorful/style.qrc15
-rw-r--r--dist/qt_themes/colorful/style.qss4
-rw-r--r--dist/qt_themes/colorful_dark/icons/16x16/lock.pngbin0 -> 401 bytes
-rw-r--r--dist/qt_themes/colorful_dark/icons/index.theme8
-rw-r--r--dist/qt_themes/colorful_dark/style.qrc57
-rw-r--r--dist/qt_themes/default/default.qrc14
-rw-r--r--dist/qt_themes/default/icons/16x16/lock.pngbin0 -> 279 bytes
-rw-r--r--dist/qt_themes/default/icons/256x256/plus_folder.pngbin0 -> 3135 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/bad_folder.pngbin0 -> 1088 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/chip.pngbin0 -> 15070 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/folder.pngbin0 -> 410 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/plus.pngbin0 -> 316 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/sd_card.pngbin0 -> 614 bytes
-rw-r--r--dist/qt_themes/default/icons/index.theme5
-rw-r--r--dist/qt_themes/qdarkstyle/icons/16x16/lock.pngbin0 -> 304 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.pngbin0 -> 3438 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.pngbin0 -> 1098 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/chip.pngbin0 -> 15120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/folder.pngbin0 -> 542 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/plus.pngbin0 -> 339 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/sd_card.pngbin0 -> 676 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/index.theme7
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc7
m---------externals/fmt0
-rw-r--r--license.txt16
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/crypto/key_manager.cpp241
-rw-r--r--src/core/crypto/key_manager.h116
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp46
-rw-r--r--src/core/file_sys/system_archive/mii_model.h13
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/hle/service/am/am.cpp13
-rw-r--r--src/core/hle/service/am/am.h3
-rw-r--r--src/core/hle/service/audio/audren_u.cpp33
-rw-r--r--src/core/hle/service/es/es.cpp230
-rw-r--r--src/core/hle/service/fatal/fatal.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/npad.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp25
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp43
-rw-r--r--src/video_core/renderer_opengl/gl_device.h6
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp930
-rw-r--r--src/yuzu/configuration/config.cpp44
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp44
-rw-r--r--src/yuzu/configuration/configure_general.cpp12
-rw-r--r--src/yuzu/configuration/configure_general.ui30
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.ui27
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp17
-rw-r--r--src/yuzu/game_list.cpp436
-rw-r--r--src/yuzu/game_list.h43
-rw-r--r--src/yuzu/game_list_p.h127
-rw-r--r--src/yuzu/game_list_worker.cpp86
-rw-r--r--src/yuzu/game_list_worker.h26
-rw-r--r--src/yuzu/main.cpp87
-rw-r--r--src/yuzu/main.h8
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h21
69 files changed, 2164 insertions, 746 deletions
diff --git a/dist/license.md b/dist/license.md
new file mode 100644
index 000000000..b777ebb20
--- /dev/null
+++ b/dist/license.md
@@ -0,0 +1,31 @@
1The icons in this folder and its subfolders have the following licenses:
2
3Icon Name | License | Origin/Author
4--- | --- | ---
5qt_themes/default/icons/16x16/checked.png | Free for non-commercial use
6qt_themes/default/icons/16x16/failed.png | Free for non-commercial use
7qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
8qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
9qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
10qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
11qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
12qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
13qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
14qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
15qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
16qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
17qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
18qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
19qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
20qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
21qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
22qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
23qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
24qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
25qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
26qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
27qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
28qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com
29qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
30
31<!-- TODO: Add the license of the yuzu icon --> \ No newline at end of file
diff --git a/dist/qt_themes/colorful/icons/16x16/lock.png b/dist/qt_themes/colorful/icons/16x16/lock.png
new file mode 100644
index 000000000..fd27069d8
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/256x256/plus_folder.png b/dist/qt_themes/colorful/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..760fe6245
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/bad_folder.png b/dist/qt_themes/colorful/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..a7ab7a1f6
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/chip.png b/dist/qt_themes/colorful/icons/48x48/chip.png
new file mode 100644
index 000000000..6fa158999
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/folder.png b/dist/qt_themes/colorful/icons/48x48/folder.png
new file mode 100644
index 000000000..498de4c62
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/plus.png b/dist/qt_themes/colorful/icons/48x48/plus.png
new file mode 100644
index 000000000..bc2c47c91
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/sd_card.png b/dist/qt_themes/colorful/icons/48x48/sd_card.png
new file mode 100644
index 000000000..29be71a0d
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/index.theme b/dist/qt_themes/colorful/icons/index.theme
new file mode 100644
index 000000000..b452aca16
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/index.theme
@@ -0,0 +1,14 @@
1[Icon Theme]
2Name=colorful
3Comment=Colorful theme
4Inherits=default
5Directories=16x16,48x48,256x256
6
7[16x16]
8Size=16
9
10[48x48]
11Size=48
12
13[256x256]
14Size=256
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc
new file mode 100644
index 000000000..af2f3fd56
--- /dev/null
+++ b/dist/qt_themes/colorful/style.qrc
@@ -0,0 +1,15 @@
1<RCC>
2 <qresource prefix="icons/colorful">
3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
11 </qresource>
12 <qresource prefix="colorful">
13 <file>style.qss</file>
14 </qresource>
15</RCC>
diff --git a/dist/qt_themes/colorful/style.qss b/dist/qt_themes/colorful/style.qss
new file mode 100644
index 000000000..413fc81da
--- /dev/null
+++ b/dist/qt_themes/colorful/style.qss
@@ -0,0 +1,4 @@
1/*
2 This file is intentionally left blank.
3 We do not want to apply any stylesheet for colorful, only icons.
4*/
diff --git a/dist/qt_themes/colorful_dark/icons/16x16/lock.png b/dist/qt_themes/colorful_dark/icons/16x16/lock.png
new file mode 100644
index 000000000..32c505848
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme
new file mode 100644
index 000000000..94d5ae8aa
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/index.theme
@@ -0,0 +1,8 @@
1[Icon Theme]
2Name=colorful_dark
3Comment=Colorful theme (Dark style)
4Inherits=default
5Directories=16x16
6
7[16x16]
8Size=16
diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc
new file mode 100644
index 000000000..27a6cc87d
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/style.qrc
@@ -0,0 +1,57 @@
1<RCC>
2 <qresource prefix="icons/colorful_dark">
3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
11 </qresource>
12
13 <qresource prefix="qss_icons">
14 <file alias="rc/up_arrow_disabled.png">../qdarkstyle/rc/up_arrow_disabled.png</file>
15 <file alias="rc/Hmovetoolbar.png">../qdarkstyle/rc/Hmovetoolbar.png</file>
16 <file alias="rc/stylesheet-branch-end.png">../qdarkstyle/rc/stylesheet-branch-end.png</file>
17 <file alias="rc/branch_closed-on.png">../qdarkstyle/rc/branch_closed-on.png</file>
18 <file alias="rc/stylesheet-vline.png">../qdarkstyle/rc/stylesheet-vline.png</file>
19 <file alias="rc/branch_closed.png">../qdarkstyle/rc/branch_closed.png</file>
20 <file alias="rc/branch_open-on.png">../qdarkstyle/rc/branch_open-on.png</file>
21 <file alias="rc/transparent.png">../qdarkstyle/rc/transparent.png</file>
22 <file alias="rc/right_arrow_disabled.png">../qdarkstyle/rc/right_arrow_disabled.png</file>
23 <file alias="rc/sizegrip.png">../qdarkstyle/rc/sizegrip.png</file>
24 <file alias="rc/close.png">../qdarkstyle/rc/close.png</file>
25 <file alias="rc/close-hover.png">../qdarkstyle/rc/close-hover.png</file>
26 <file alias="rc/close-pressed.png">../qdarkstyle/rc/close-pressed.png</file>
27 <file alias="rc/down_arrow.png">../qdarkstyle/rc/down_arrow.png</file>
28 <file alias="rc/Vmovetoolbar.png">../qdarkstyle/rc/Vmovetoolbar.png</file>
29 <file alias="rc/left_arrow.png">../qdarkstyle/rc/left_arrow.png</file>
30 <file alias="rc/stylesheet-branch-more.png">../qdarkstyle/rc/stylesheet-branch-more.png</file>
31 <file alias="rc/up_arrow.png">../qdarkstyle/rc/up_arrow.png</file>
32 <file alias="rc/right_arrow.png">../qdarkstyle/rc/right_arrow.png</file>
33 <file alias="rc/left_arrow_disabled.png">../qdarkstyle/rc/left_arrow_disabled.png</file>
34 <file alias="rc/Hsepartoolbar.png">../qdarkstyle/rc/Hsepartoolbar.png</file>
35 <file alias="rc/branch_open.png">../qdarkstyle/rc/branch_open.png</file>
36 <file alias="rc/Vsepartoolbar.png">../qdarkstyle/rc/Vsepartoolbar.png</file>
37 <file alias="rc/down_arrow_disabled.png">../qdarkstyle/rc/down_arrow_disabled.png</file>
38 <file alias="rc/undock.png">../qdarkstyle/rc/undock.png</file>
39 <file alias="rc/checkbox_checked_disabled.png">../qdarkstyle/rc/checkbox_checked_disabled.png</file>
40 <file alias="rc/checkbox_checked_focus.png">../qdarkstyle/rc/checkbox_checked_focus.png</file>
41 <file alias="rc/checkbox_checked.png">../qdarkstyle/rc/checkbox_checked.png</file>
42 <file alias="rc/checkbox_indeterminate.png">../qdarkstyle/rc/checkbox_indeterminate.png</file>
43 <file alias="rc/checkbox_indeterminate_focus.png">../qdarkstyle/rc/checkbox_indeterminate_focus.png</file>
44 <file alias="rc/checkbox_unchecked_disabled.png">../qdarkstyle/rc/checkbox_unchecked_disabled.png</file>
45 <file alias="rc/checkbox_unchecked_focus.png">../qdarkstyle/rc/checkbox_unchecked_focus.png</file>
46 <file alias="rc/checkbox_unchecked.png">../qdarkstyle/rc/checkbox_unchecked.png</file>
47 <file alias="rc/radio_checked_disabled.png">../qdarkstyle/rc/radio_checked_disabled.png</file>
48 <file alias="rc/radio_checked_focus.png">../qdarkstyle/rc/radio_checked_focus.png</file>
49 <file alias="rc/radio_checked.png">../qdarkstyle/rc/radio_checked.png</file>
50 <file alias="rc/radio_unchecked_disabled.png">../qdarkstyle/rc/radio_unchecked_disabled.png</file>
51 <file alias="rc/radio_unchecked_focus.png">../qdarkstyle/rc/radio_unchecked_focus.png</file>
52 <file alias="rc/radio_unchecked.png">../qdarkstyle/rc/radio_unchecked.png</file>
53 </qresource>
54 <qresource prefix="colorful_dark">
55 <file alias="style.qss">../qdarkstyle/style.qss</file>
56 </qresource>
57</RCC>
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index 14a0cf6f9..d1a0ee1be 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -5,7 +5,21 @@
5 <file alias="16x16/checked.png">icons/16x16/checked.png</file> 5 <file alias="16x16/checked.png">icons/16x16/checked.png</file>
6 6
7 <file alias="16x16/failed.png">icons/16x16/failed.png</file> 7 <file alias="16x16/failed.png">icons/16x16/failed.png</file>
8
9 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
10
11 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
12
13 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
14
15 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
16
17 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
18
19 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
8 20
9 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> 21 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
22
23 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
10 </qresource> 24 </qresource>
11</RCC> 25</RCC>
diff --git a/dist/qt_themes/default/icons/16x16/lock.png b/dist/qt_themes/default/icons/16x16/lock.png
new file mode 100644
index 000000000..496b58078
--- /dev/null
+++ b/dist/qt_themes/default/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/256x256/plus_folder.png b/dist/qt_themes/default/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..ae4afccc7
--- /dev/null
+++ b/dist/qt_themes/default/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/bad_folder.png b/dist/qt_themes/default/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..2527c1318
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/chip.png b/dist/qt_themes/default/icons/48x48/chip.png
new file mode 100644
index 000000000..3efdf301e
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/folder.png b/dist/qt_themes/default/icons/48x48/folder.png
new file mode 100644
index 000000000..2e67d8b38
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/plus.png b/dist/qt_themes/default/icons/48x48/plus.png
new file mode 100644
index 000000000..dbc74687b
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png
new file mode 100644
index 000000000..edacaeeb5
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme
index ac67cb236..1edbe6408 100644
--- a/dist/qt_themes/default/icons/index.theme
+++ b/dist/qt_themes/default/icons/index.theme
@@ -1,10 +1,13 @@
1[Icon Theme] 1[Icon Theme]
2Name=default 2Name=default
3Comment=default theme 3Comment=default theme
4Directories=16x16,256x256 4Directories=16x16,48x48,256x256
5 5
6[16x16] 6[16x16]
7Size=16 7Size=16
8
9[48x48]
10Size=48
8 11
9[256x256] 12[256x256]
10Size=256 \ No newline at end of file 13Size=256 \ No newline at end of file
diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/lock.png b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png
new file mode 100644
index 000000000..c750a39e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..303f9a321
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..4a9709623
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/chip.png b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png
new file mode 100644
index 000000000..973fabd05
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png
new file mode 100644
index 000000000..0f1e987d6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/plus.png b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png
new file mode 100644
index 000000000..16cc8b4f4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
new file mode 100644
index 000000000..0291c6542
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme
index 558ece40b..d1e12f3ef 100644
--- a/dist/qt_themes/qdarkstyle/icons/index.theme
+++ b/dist/qt_themes/qdarkstyle/icons/index.theme
@@ -2,10 +2,13 @@
2Name=qdarkstyle 2Name=qdarkstyle
3Comment=dark theme 3Comment=dark theme
4Inherits=default 4Inherits=default
5Directories=16x16,256x256 5Directories=16x16,48x48,256x256
6 6
7[16x16] 7[16x16]
8Size=16 8Size=16
9 9
10[48x48]
11Size=48
12
10[256x256] 13[256x256]
11Size=256 \ No newline at end of file 14Size=256 \ No newline at end of file
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index efbd0b9dc..c2c14c28a 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -1,6 +1,13 @@
1<RCC> 1<RCC>
2 <qresource prefix="icons/qdarkstyle"> 2 <qresource prefix="icons/qdarkstyle">
3 <file alias="index.theme">icons/index.theme</file> 3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
4 </qresource> 11 </qresource>
5 <qresource prefix="qss_icons"> 12 <qresource prefix="qss_icons">
6 <file>rc/up_arrow_disabled.png</file> 13 <file>rc/up_arrow_disabled.png</file>
diff --git a/externals/fmt b/externals/fmt
Subproject 9e554999ce02cf86fcdfe74fe740c4fe3f5a56d Subproject 7512a55aa3ae309587ca89668ef9ec4074a51a1
diff --git a/license.txt b/license.txt
index d511905c1..2b858f9a7 100644
--- a/license.txt
+++ b/license.txt
@@ -337,3 +337,19 @@ proprietary programs. If your program is a subroutine library, you may
337consider it more useful to permit linking proprietary applications with the 337consider it more useful to permit linking proprietary applications with the
338library. If this is what you want to do, use the GNU Lesser General 338library. If this is what you want to do, use the GNU Lesser General
339Public License instead of this License. 339Public License instead of this License.
340
341
342The icons used in this project have the following licenses:
343
344Icon Name | License | Origin/Author
345--- | --- | ---
346checked.png | Free for non-commercial use
347failed.png | Free for non-commercial use
348lock.png | CC BY-ND 3.0 | https://icons8.com
349plus_folder.png | CC BY-ND 3.0 | https://icons8.com
350bad_folder.png | CC BY-ND 3.0 | https://icons8.com
351chip.png | CC BY-ND 3.0 | https://icons8.com
352folder.png | CC BY-ND 3.0 | https://icons8.com
353plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team
354plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
355sd_card.png | CC BY-ND 3.0 | https://icons8.com
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 5462decee..877a9e353 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -70,6 +70,8 @@ add_library(core STATIC
70 file_sys/sdmc_factory.h 70 file_sys/sdmc_factory.h
71 file_sys/submission_package.cpp 71 file_sys/submission_package.cpp
72 file_sys/submission_package.h 72 file_sys/submission_package.h
73 file_sys/system_archive/mii_model.cpp
74 file_sys/system_archive/mii_model.h
73 file_sys/system_archive/ng_word.cpp 75 file_sys/system_archive/ng_word.cpp
74 file_sys/system_archive/ng_word.h 76 file_sys/system_archive/ng_word.h
75 file_sys/system_archive/system_archive.cpp 77 file_sys/system_archive/system_archive.cpp
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 6dd633363..46aceec3d 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -37,6 +37,7 @@
37namespace Core::Crypto { 37namespace Core::Crypto {
38 38
39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
40constexpr u64 FULL_TICKET_SIZE = 0x400;
40 41
41using namespace Common; 42using namespace Common;
42 43
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
55 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, 56 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
56}; 57};
57 58
59namespace {
60template <std::size_t Size>
61bool IsAllZeroArray(const std::array<u8, Size>& array) {
62 return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
63}
64} // namespace
65
66u64 GetSignatureTypeDataSize(SignatureType type) {
67 switch (type) {
68 case SignatureType::RSA_4096_SHA1:
69 case SignatureType::RSA_4096_SHA256:
70 return 0x200;
71 case SignatureType::RSA_2048_SHA1:
72 case SignatureType::RSA_2048_SHA256:
73 return 0x100;
74 case SignatureType::ECDSA_SHA1:
75 case SignatureType::ECDSA_SHA256:
76 return 0x3C;
77 }
78 UNREACHABLE();
79}
80
81u64 GetSignatureTypePaddingSize(SignatureType type) {
82 switch (type) {
83 case SignatureType::RSA_4096_SHA1:
84 case SignatureType::RSA_4096_SHA256:
85 case SignatureType::RSA_2048_SHA1:
86 case SignatureType::RSA_2048_SHA256:
87 return 0x3C;
88 case SignatureType::ECDSA_SHA1:
89 case SignatureType::ECDSA_SHA256:
90 return 0x40;
91 }
92 UNREACHABLE();
93}
94
95SignatureType Ticket::GetSignatureType() const {
96 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
97 return ticket->sig_type;
98 }
99 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
100 return ticket->sig_type;
101 }
102 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
103 return ticket->sig_type;
104 }
105
106 UNREACHABLE();
107}
108
109TicketData& Ticket::GetData() {
110 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
111 return ticket->data;
112 }
113 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
114 return ticket->data;
115 }
116 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
117 return ticket->data;
118 }
119
120 UNREACHABLE();
121}
122
123const TicketData& Ticket::GetData() const {
124 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
125 return ticket->data;
126 }
127 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
128 return ticket->data;
129 }
130 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
131 return ticket->data;
132 }
133
134 UNREACHABLE();
135}
136
137u64 Ticket::GetSize() const {
138 const auto sig_type = GetSignatureType();
139
140 return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
141 GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
142}
143
144Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
145 RSA2048Ticket out{};
146 out.sig_type = SignatureType::RSA_2048_SHA256;
147 out.data.rights_id = rights_id;
148 out.data.title_key_common = title_key;
149 return Ticket{out};
150}
151
58Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { 152Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
59 Key128 out{}; 153 Key128 out{};
60 154
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
135 } 229 }
136} 230}
137 231
232RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
233 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
234 return {};
235
236 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
237
238 std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
239 std::array<u8, 0x230> extended_dec{};
240 AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
241 rsa_1.SetIV(extended_iv);
242 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
243 extended_dec.data(), Op::Decrypt);
244
245 RSAKeyPair<2048> rsa_key{};
246 std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
247 std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
248 std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
249
250 return rsa_key;
251}
252
138Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { 253Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
139 AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); 254 AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
140 Key128 mac_key{}; 255 Key128 mac_key{};
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
237 return Loader::ResultStatus::Success; 352 return Loader::ResultStatus::Success;
238} 353}
239 354
240std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { 355std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
241 if (!ticket_save.IsOpen()) 356 if (!ticket_save.IsOpen())
242 return {}; 357 return {};
243 358
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
246 return {}; 361 return {};
247 } 362 }
248 363
249 std::vector<TicketRaw> out; 364 std::vector<Ticket> out;
250 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { 365 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
251 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && 366 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
252 buffer[offset + 3] == 0x0) { 367 buffer[offset + 3] == 0x0) {
253 out.emplace_back(); 368 out.emplace_back();
254 auto& next = out.back(); 369 auto& next = out.back();
255 std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); 370 std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
256 offset += next.size(); 371 offset += FULL_TICKET_SIZE;
257 } 372 }
258 } 373 }
259 374
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
305 return offset; 420 return offset;
306} 421}
307 422
308std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 423std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
309 const RSAKeyPair<2048>& key) { 424 const RSAKeyPair<2048>& key) {
310 u32 cert_authority; 425 const auto issuer = ticket.GetData().issuer;
311 std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); 426 if (issuer == std::array<u8, 0x40>{})
312 if (cert_authority == 0)
313 return {}; 427 return {};
314 if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { 428 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
315 LOG_INFO(Crypto, 429 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
316 "Attempting to parse ticket with non-standard certificate authority {:08X}.",
317 cert_authority);
318 } 430 }
319 431
320 Key128 rights_id; 432 Key128 rights_id = ticket.GetData().rights_id;
321 std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
322 433
323 if (rights_id == Key128{}) 434 if (rights_id == Key128{})
324 return {}; 435 return {};
325 436
326 Key128 key_temp{}; 437 if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
327 438 ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
328 if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { 439 return std::make_pair(rights_id, ticket.GetData().title_key_common);
329 std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
330 return std::make_pair(rights_id, key_temp);
331 } 440 }
332 441
333 mbedtls_mpi D; // RSA Private Exponent 442 mbedtls_mpi D; // RSA Private Exponent
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
342 451
343 mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); 452 mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
344 mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); 453 mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
345 mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); 454 mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
346 455
347 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); 456 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
348 457
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
366 return {}; 475 return {};
367 ASSERT(*offset > 0); 476 ASSERT(*offset > 0);
368 477
478 Key128 key_temp{};
369 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); 479 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
370 480
371 return std::make_pair(rights_id, key_temp); 481 return std::make_pair(rights_id, key_temp);
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
450 560
451 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 561 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
452 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 562 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
563 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
564 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
453 } else { 565 } else {
454 for (const auto& kv : KEYS_VARIABLE_LENGTH) { 566 for (const auto& kv : KEYS_VARIABLE_LENGTH) {
455 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) 567 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
862 // Titlekeys 974 // Titlekeys
863 data.DecryptProdInfo(GetBISKey(0)); 975 data.DecryptProdInfo(GetBISKey(0));
864 976
865 const auto eticket_extended_kek = data.GetETicketExtendedKek(); 977 eticket_extended_kek = data.GetETicketExtendedKek();
978 WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
979 PopulateTickets();
980}
866 981
867 std::vector<u8> extended_iv(0x10); 982void KeyManager::PopulateTickets() {
868 std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); 983 const auto rsa_key = GetETicketRSAKey();
869 std::array<u8, 0x230> extended_dec{};
870 AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
871 rsa_1.SetIV(extended_iv);
872 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
873 extended_dec.data(), Op::Decrypt);
874 984
875 RSAKeyPair<2048> rsa_key{}; 985 if (rsa_key == RSAKeyPair<2048>{})
876 std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); 986 return;
877 std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); 987
878 std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); 988 if (!common_tickets.empty() && !personal_tickets.empty())
989 return;
879 990
880 const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 991 const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
881 "/system/save/80000000000000e1", 992 "/system/save/80000000000000e1",
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
886 997
887 const auto blob2 = GetTicketblob(save2); 998 const auto blob2 = GetTicketblob(save2);
888 auto res = GetTicketblob(save1); 999 auto res = GetTicketblob(save1);
1000 const auto idx = res.size();
889 res.insert(res.end(), blob2.begin(), blob2.end()); 1001 res.insert(res.end(), blob2.begin(), blob2.end());
890 1002
891 for (const auto& raw : res) { 1003 for (std::size_t i = 0; i < res.size(); ++i) {
892 const auto pair = ParseTicket(raw, rsa_key); 1004 const auto common = i < idx;
1005 const auto pair = ParseTicket(res[i], rsa_key);
893 if (!pair) 1006 if (!pair)
894 continue; 1007 continue;
895 const auto& [rid, key] = *pair; 1008 const auto& [rid, key] = *pair;
896 u128 rights_id; 1009 u128 rights_id;
897 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1010 std::memcpy(rights_id.data(), rid.data(), rid.size());
1011
1012 if (common) {
1013 common_tickets[rights_id] = res[i];
1014 } else {
1015 personal_tickets[rights_id] = res[i];
1016 }
1017
898 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); 1018 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
899 } 1019 }
900} 1020}
901 1021
1022void KeyManager::SynthesizeTickets() {
1023 for (const auto& key : s128_keys) {
1024 if (key.first.type != S128KeyType::Titlekey) {
1025 continue;
1026 }
1027 u128 rights_id{key.first.field1, key.first.field2};
1028 Key128 rights_id_2;
1029 std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
1030 const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
1031 common_tickets.insert_or_assign(rights_id, ticket);
1032 }
1033}
1034
902void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { 1035void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
903 if (key == Key128{}) 1036 if (key == Key128{})
904 return; 1037 return;
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
997 DeriveBase(); 1130 DeriveBase();
998} 1131}
999 1132
1133const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
1134 return common_tickets;
1135}
1136
1137const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
1138 return personal_tickets;
1139}
1140
1141bool KeyManager::AddTicketCommon(Ticket raw) {
1142 const auto rsa_key = GetETicketRSAKey();
1143 if (rsa_key == RSAKeyPair<2048>{})
1144 return false;
1145
1146 const auto pair = ParseTicket(raw, rsa_key);
1147 if (!pair)
1148 return false;
1149 const auto& [rid, key] = *pair;
1150 u128 rights_id;
1151 std::memcpy(rights_id.data(), rid.data(), rid.size());
1152 common_tickets[rights_id] = raw;
1153 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1154 return true;
1155}
1156
1157bool KeyManager::AddTicketPersonalized(Ticket raw) {
1158 const auto rsa_key = GetETicketRSAKey();
1159 if (rsa_key == RSAKeyPair<2048>{})
1160 return false;
1161
1162 const auto pair = ParseTicket(raw, rsa_key);
1163 if (!pair)
1164 return false;
1165 const auto& [rid, key] = *pair;
1166 u128 rights_id;
1167 std::memcpy(rights_id.data(), rid.data(), rid.size());
1168 common_tickets[rights_id] = raw;
1169 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1170 return true;
1171}
1172
1000const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { 1173const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
1001 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, 1174 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
1002 {"eticket_rsa_kek_source", 1175 {"eticket_rsa_kek_source",
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 22f268c65..7265c4171 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -9,8 +9,10 @@
9#include <optional> 9#include <optional>
10#include <string> 10#include <string>
11 11
12#include <variant>
12#include <boost/container/flat_map.hpp> 13#include <boost/container/flat_map.hpp>
13#include <fmt/format.h> 14#include <fmt/format.h>
15#include "common/common_funcs.h"
14#include "common/common_types.h" 16#include "common/common_types.h"
15#include "core/crypto/partition_data_manager.h" 17#include "core/crypto/partition_data_manager.h"
16#include "core/file_sys/vfs_types.h" 18#include "core/file_sys/vfs_types.h"
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
30using Key128 = std::array<u8, 0x10>; 32using Key128 = std::array<u8, 0x10>;
31using Key256 = std::array<u8, 0x20>; 33using Key256 = std::array<u8, 0x20>;
32using SHA256Hash = std::array<u8, 0x20>; 34using SHA256Hash = std::array<u8, 0x20>;
33using TicketRaw = std::array<u8, 0x400>; 35
36enum class SignatureType {
37 RSA_4096_SHA1 = 0x10000,
38 RSA_2048_SHA1 = 0x10001,
39 ECDSA_SHA1 = 0x10002,
40 RSA_4096_SHA256 = 0x10003,
41 RSA_2048_SHA256 = 0x10004,
42 ECDSA_SHA256 = 0x10005,
43};
44
45u64 GetSignatureTypeDataSize(SignatureType type);
46u64 GetSignatureTypePaddingSize(SignatureType type);
47
48enum class TitleKeyType : u8 {
49 Common = 0,
50 Personalized = 1,
51};
52
53struct TicketData {
54 std::array<u8, 0x40> issuer;
55 union {
56 std::array<u8, 0x100> title_key_block;
57
58 struct {
59 Key128 title_key_common;
60 std::array<u8, 0xF0> title_key_common_pad;
61 };
62 };
63
64 INSERT_PADDING_BYTES(0x1);
65 TitleKeyType type;
66 INSERT_PADDING_BYTES(0x3);
67 u8 revision;
68 INSERT_PADDING_BYTES(0xA);
69 u64 ticket_id;
70 u64 device_id;
71 std::array<u8, 0x10> rights_id;
72 u32 account_id;
73 INSERT_PADDING_BYTES(0x14C);
74};
75static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
76
77struct RSA4096Ticket {
78 SignatureType sig_type;
79 std::array<u8, 0x200> sig_data;
80 INSERT_PADDING_BYTES(0x3C);
81 TicketData data;
82};
83
84struct RSA2048Ticket {
85 SignatureType sig_type;
86 std::array<u8, 0x100> sig_data;
87 INSERT_PADDING_BYTES(0x3C);
88 TicketData data;
89};
90
91struct ECDSATicket {
92 SignatureType sig_type;
93 std::array<u8, 0x3C> sig_data;
94 INSERT_PADDING_BYTES(0x40);
95 TicketData data;
96};
97
98struct Ticket {
99 std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
100
101 SignatureType GetSignatureType() const;
102 TicketData& GetData();
103 const TicketData& GetData() const;
104 u64 GetSize() const;
105
106 static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
107};
34 108
35static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); 109static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
36static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); 110static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
@@ -43,6 +117,19 @@ struct RSAKeyPair {
43 std::array<u8, 4> exponent; 117 std::array<u8, 4> exponent;
44}; 118};
45 119
120template <size_t bit_size, size_t byte_size>
121bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
122 const RSAKeyPair<bit_size, byte_size>& rhs) {
123 return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
124 std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
125}
126
127template <size_t bit_size, size_t byte_size>
128bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
129 const RSAKeyPair<bit_size, byte_size>& rhs) {
130 return !(lhs == rhs);
131}
132
46enum class KeyCategory : u8 { 133enum class KeyCategory : u8 {
47 Standard, 134 Standard,
48 Title, 135 Title,
@@ -151,22 +238,35 @@ public:
151 238
152 static bool KeyFileExists(bool title); 239 static bool KeyFileExists(bool title);
153 240
154 // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save 241 // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
155 // 8*43 and the private file to exist. 242 // save 8*43 and the private file to exist.
156 void DeriveSDSeedLazy(); 243 void DeriveSDSeedLazy();
157 244
158 bool BaseDeriveNecessary() const; 245 bool BaseDeriveNecessary() const;
159 void DeriveBase(); 246 void DeriveBase();
160 void DeriveETicket(PartitionDataManager& data); 247 void DeriveETicket(PartitionDataManager& data);
248 void PopulateTickets();
249 void SynthesizeTickets();
161 250
162 void PopulateFromPartitionData(PartitionDataManager& data); 251 void PopulateFromPartitionData(PartitionDataManager& data);
163 252
253 const std::map<u128, Ticket>& GetCommonTickets() const;
254 const std::map<u128, Ticket>& GetPersonalizedTickets() const;
255
256 bool AddTicketCommon(Ticket raw);
257 bool AddTicketPersonalized(Ticket raw);
258
164private: 259private:
165 std::map<KeyIndex<S128KeyType>, Key128> s128_keys; 260 std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
166 std::map<KeyIndex<S256KeyType>, Key256> s256_keys; 261 std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
167 262
263 // Map from rights ID to ticket
264 std::map<u128, Ticket> common_tickets;
265 std::map<u128, Ticket> personal_tickets;
266
168 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; 267 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
169 std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; 268 std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
269 std::array<u8, 576> eticket_extended_kek{};
170 270
171 bool dev_mode; 271 bool dev_mode;
172 void LoadFromFile(const std::string& filename, bool is_title_keys); 272 void LoadFromFile(const std::string& filename, bool is_title_keys);
@@ -178,6 +278,8 @@ private:
178 278
179 void DeriveGeneralPurposeKeys(std::size_t crypto_revision); 279 void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
180 280
281 RSAKeyPair<2048> GetETicketRSAKey() const;
282
181 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); 283 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
182 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); 284 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
183 285
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
195std::optional<Key128> DeriveSDSeed(); 297std::optional<Key128> DeriveSDSeed();
196Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); 298Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
197 299
198std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); 300std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
199 301
200// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset 302// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
201// 0x140-0x144 is zero) 303// (offset 0x140-0x144 is zero)
202std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 304std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
203 const RSAKeyPair<2048>& eticket_extended_key); 305 const RSAKeyPair<2048>& eticket_extended_key);
204 306
205} // namespace Core::Crypto 307} // namespace Core::Crypto
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
new file mode 100644
index 000000000..6a9add87c
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -0,0 +1,46 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/file_sys/system_archive/mii_model.h"
6#include "core/file_sys/vfs_vector.h"
7
8namespace FileSys::SystemArchive {
9
10namespace MiiModelData {
11
12constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
13 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
14constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
15 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
16
17constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
18constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
19constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
20constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
21constexpr auto SHAPE_HIGH = NFSR_STANDARD;
22constexpr auto SHAPE_MID = NFSR_STANDARD;
23
24} // namespace MiiModelData
25
26VirtualDir MiiModel() {
27 auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
28 std::vector<VirtualDir>{}, "data");
29
30 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
31 MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
32 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
33 MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
34 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
35 MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
36 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
37 MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
38 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
39 MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
40 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
41 MiiModelData::SHAPE_MID, "ShapeMid.dat"));
42
43 return std::move(out);
44}
45
46} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h
new file mode 100644
index 000000000..6c2d9398b
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.h
@@ -0,0 +1,13 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/file_sys/vfs_types.h"
8
9namespace FileSys::SystemArchive {
10
11VirtualDir MiiModel();
12
13} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index c9722ed77..6d8445383 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/file_sys/romfs.h" 6#include "core/file_sys/romfs.h"
7#include "core/file_sys/system_archive/mii_model.h"
7#include "core/file_sys/system_archive/ng_word.h" 8#include "core/file_sys/system_archive/ng_word.h"
8#include "core/file_sys/system_archive/system_archive.h" 9#include "core/file_sys/system_archive/system_archive.h"
9#include "core/file_sys/system_archive/system_version.h" 10#include "core/file_sys/system_archive/system_version.h"
@@ -24,7 +25,7 @@ struct SystemArchiveDescriptor {
24constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ 25constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
25 {0x0100000000000800, "CertStore", nullptr}, 26 {0x0100000000000800, "CertStore", nullptr},
26 {0x0100000000000801, "ErrorMessage", nullptr}, 27 {0x0100000000000801, "ErrorMessage", nullptr},
27 {0x0100000000000802, "MiiModel", nullptr}, 28 {0x0100000000000802, "MiiModel", &MiiModel},
28 {0x0100000000000803, "BrowserDll", nullptr}, 29 {0x0100000000000803, "BrowserDll", nullptr},
29 {0x0100000000000804, "Help", nullptr}, 30 {0x0100000000000804, "Help", nullptr},
30 {0x0100000000000805, "SharedFont", nullptr}, 31 {0x0100000000000805, "SharedFont", nullptr},
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index a192a1f5f..111633ba3 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1057,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
1057 {120, nullptr, "ExecuteProgram"}, 1057 {120, nullptr, "ExecuteProgram"},
1058 {121, nullptr, "ClearUserChannel"}, 1058 {121, nullptr, "ClearUserChannel"},
1059 {122, nullptr, "UnpopToUserChannel"}, 1059 {122, nullptr, "UnpopToUserChannel"},
1060 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
1060 {500, nullptr, "StartContinuousRecordingFlushForDebug"}, 1061 {500, nullptr, "StartContinuousRecordingFlushForDebug"},
1061 {1000, nullptr, "CreateMovieMaker"}, 1062 {1000, nullptr, "CreateMovieMaker"},
1062 {1001, nullptr, "PrepareForJit"}, 1063 {1001, nullptr, "PrepareForJit"},
@@ -1064,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
1064 // clang-format on 1065 // clang-format on
1065 1066
1066 RegisterHandlers(functions); 1067 RegisterHandlers(functions);
1068
1069 auto& kernel = Core::System::GetInstance().Kernel();
1070 gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
1071 kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
1067} 1072}
1068 1073
1069IApplicationFunctions::~IApplicationFunctions() = default; 1074IApplicationFunctions::~IApplicationFunctions() = default;
@@ -1285,6 +1290,14 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
1285 rb.Push(size.journal); 1290 rb.Push(size.journal);
1286} 1291}
1287 1292
1293void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
1294 LOG_WARNING(Service_AM, "(STUBBED) called");
1295
1296 IPC::ResponseBuilder rb{ctx, 2, 1};
1297 rb.Push(RESULT_SUCCESS);
1298 rb.PushCopyObjects(gpu_error_detected_event.readable);
1299}
1300
1288void InstallInterfaces(SM::ServiceManager& service_manager, 1301void InstallInterfaces(SM::ServiceManager& service_manager,
1289 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { 1302 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
1290 auto message_queue = std::make_shared<AppletMessageQueue>(); 1303 auto message_queue = std::make_shared<AppletMessageQueue>();
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6cb582483..cbc9da7b6 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -242,6 +242,9 @@ private:
242 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); 242 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
243 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); 243 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
244 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); 244 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
245 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
246
247 Kernel::EventPair gpu_error_detected_event;
245}; 248};
246 249
247class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { 250class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 5b0b7f17e..f162249ed 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -165,15 +165,15 @@ public:
165 static const FunctionInfo functions[] = { 165 static const FunctionInfo functions[] = {
166 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, 166 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
167 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, 167 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
168 {2, nullptr, "GetAudioDeviceOutputVolume"}, 168 {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"},
169 {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, 169 {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
170 {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, 170 {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
171 {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, 171 {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
172 {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, 172 {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"},
173 {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, 173 {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
174 {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, 174 {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"},
175 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, 175 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
176 {11, nullptr, "QueryAudioDeviceInputEvent"}, 176 {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
177 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, 177 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
178 {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, 178 {13, nullptr, "GetAudioSystemMasterVolumeSetting"},
179 }; 179 };
@@ -183,6 +183,10 @@ public:
183 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, 183 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
184 "IAudioOutBufferReleasedEvent"); 184 "IAudioOutBufferReleasedEvent");
185 185
186 // Should be similar to audio_output_device_switch_event
187 audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
188 kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent");
189
186 // Should only be signalled when an audio output device has been changed, example: speaker 190 // Should only be signalled when an audio output device has been changed, example: speaker
187 // to headset 191 // to headset
188 audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( 192 audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
@@ -246,6 +250,19 @@ private:
246 rb.Push(RESULT_SUCCESS); 250 rb.Push(RESULT_SUCCESS);
247 } 251 }
248 252
253 void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
254 IPC::RequestParser rp{ctx};
255
256 const auto device_name_buffer = ctx.ReadBuffer();
257 const std::string name = Common::StringFromBuffer(device_name_buffer);
258
259 LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name);
260
261 IPC::ResponseBuilder rb{ctx, 3};
262 rb.Push(RESULT_SUCCESS);
263 rb.Push(1.0f);
264 }
265
249 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { 266 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
250 LOG_WARNING(Service_Audio, "(STUBBED) called"); 267 LOG_WARNING(Service_Audio, "(STUBBED) called");
251 268
@@ -279,6 +296,15 @@ private:
279 rb.Push<u32>(1); 296 rb.Push<u32>(1);
280 } 297 }
281 298
299 // Should be similar to QueryAudioDeviceOutputEvent
300 void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
301 LOG_WARNING(Service_Audio, "(STUBBED) called");
302
303 IPC::ResponseBuilder rb{ctx, 2, 1};
304 rb.Push(RESULT_SUCCESS);
305 rb.PushCopyObjects(audio_input_device_switch_event.readable);
306 }
307
282 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { 308 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
283 LOG_DEBUG(Service_Audio, "called"); 309 LOG_DEBUG(Service_Audio, "called");
284 310
@@ -289,6 +315,7 @@ private:
289 315
290 u32_le revision = 0; 316 u32_le revision = 0;
291 Kernel::EventPair buffer_event; 317 Kernel::EventPair buffer_event;
318 Kernel::EventPair audio_input_device_switch_event;
292 Kernel::EventPair audio_output_device_switch_event; 319 Kernel::EventPair audio_output_device_switch_event;
293 320
294}; // namespace Audio 321}; // namespace Audio
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 6701cb913..af70d174d 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -2,32 +2,37 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "core/crypto/key_manager.h"
6#include "core/hle/ipc_helpers.h"
5#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
6 8
7namespace Service::ES { 9namespace Service::ES {
8 10
11constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
12constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
13
9class ETicket final : public ServiceFramework<ETicket> { 14class ETicket final : public ServiceFramework<ETicket> {
10public: 15public:
11 explicit ETicket() : ServiceFramework{"es"} { 16 explicit ETicket() : ServiceFramework{"es"} {
12 // clang-format off 17 // clang-format off
13 static const FunctionInfo functions[] = { 18 static const FunctionInfo functions[] = {
14 {1, nullptr, "ImportTicket"}, 19 {1, &ETicket::ImportTicket, "ImportTicket"},
15 {2, nullptr, "ImportTicketCertificateSet"}, 20 {2, nullptr, "ImportTicketCertificateSet"},
16 {3, nullptr, "DeleteTicket"}, 21 {3, nullptr, "DeleteTicket"},
17 {4, nullptr, "DeletePersonalizedTicket"}, 22 {4, nullptr, "DeletePersonalizedTicket"},
18 {5, nullptr, "DeleteAllCommonTicket"}, 23 {5, nullptr, "DeleteAllCommonTicket"},
19 {6, nullptr, "DeleteAllPersonalizedTicket"}, 24 {6, nullptr, "DeleteAllPersonalizedTicket"},
20 {7, nullptr, "DeleteAllPersonalizedTicketEx"}, 25 {7, nullptr, "DeleteAllPersonalizedTicketEx"},
21 {8, nullptr, "GetTitleKey"}, 26 {8, &ETicket::GetTitleKey, "GetTitleKey"},
22 {9, nullptr, "CountCommonTicket"}, 27 {9, &ETicket::CountCommonTicket, "CountCommonTicket"},
23 {10, nullptr, "CountPersonalizedTicket"}, 28 {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
24 {11, nullptr, "ListCommonTicket"}, 29 {11, &ETicket::ListCommonTicket, "ListCommonTicket"},
25 {12, nullptr, "ListPersonalizedTicket"}, 30 {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
26 {13, nullptr, "ListMissingPersonalizedTicket"}, 31 {13, nullptr, "ListMissingPersonalizedTicket"},
27 {14, nullptr, "GetCommonTicketSize"}, 32 {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
28 {15, nullptr, "GetPersonalizedTicketSize"}, 33 {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
29 {16, nullptr, "GetCommonTicketData"}, 34 {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"},
30 {17, nullptr, "GetPersonalizedTicketData"}, 35 {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"},
31 {18, nullptr, "OwnTicket"}, 36 {18, nullptr, "OwnTicket"},
32 {19, nullptr, "GetTicketInfo"}, 37 {19, nullptr, "GetTicketInfo"},
33 {20, nullptr, "ListLightTicketInfo"}, 38 {20, nullptr, "ListLightTicketInfo"},
@@ -51,7 +56,212 @@ public:
51 }; 56 };
52 // clang-format on 57 // clang-format on
53 RegisterHandlers(functions); 58 RegisterHandlers(functions);
59
60 keys.PopulateTickets();
61 keys.SynthesizeTickets();
62 }
63
64private:
65 bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) {
66 if (rights_id == u128{}) {
67 LOG_ERROR(Service_ETicket, "The rights ID was invalid!");
68 IPC::ResponseBuilder rb{ctx, 2};
69 rb.Push(ERROR_INVALID_RIGHTS_ID);
70 return false;
71 }
72
73 return true;
74 }
75
76 void ImportTicket(Kernel::HLERequestContext& ctx) {
77 IPC::RequestParser rp{ctx};
78 const auto ticket = ctx.ReadBuffer();
79 const auto cert = ctx.ReadBuffer(1);
80
81 if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
82 LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
83 IPC::ResponseBuilder rb{ctx, 2};
84 rb.Push(ERROR_INVALID_ARGUMENT);
85 return;
86 }
87
88 Core::Crypto::Ticket raw{};
89 std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
90
91 if (!keys.AddTicketPersonalized(raw)) {
92 LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
93 IPC::ResponseBuilder rb{ctx, 2};
94 rb.Push(ERROR_INVALID_ARGUMENT);
95 return;
96 }
97
98 IPC::ResponseBuilder rb{ctx, 2};
99 rb.Push(RESULT_SUCCESS);
100 }
101
102 void GetTitleKey(Kernel::HLERequestContext& ctx) {
103 IPC::RequestParser rp{ctx};
104 const auto rights_id = rp.PopRaw<u128>();
105
106 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
107
108 if (!CheckRightsId(ctx, rights_id))
109 return;
110
111 const auto key =
112 keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
113
114 if (key == Core::Crypto::Key128{}) {
115 LOG_ERROR(Service_ETicket,
116 "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!");
117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(ERROR_INVALID_RIGHTS_ID);
119 return;
120 }
121
122 ctx.WriteBuffer(key.data(), key.size());
123
124 IPC::ResponseBuilder rb{ctx, 2};
125 rb.Push(RESULT_SUCCESS);
126 }
127
128 void CountCommonTicket(Kernel::HLERequestContext& ctx) {
129 LOG_DEBUG(Service_ETicket, "called");
130
131 const auto count = keys.GetCommonTickets().size();
132
133 IPC::ResponseBuilder rb{ctx, 3};
134 rb.Push(RESULT_SUCCESS);
135 rb.Push<u32>(count);
136 }
137
138 void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
139 LOG_DEBUG(Service_ETicket, "called");
140
141 const auto count = keys.GetPersonalizedTickets().size();
142
143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(RESULT_SUCCESS);
145 rb.Push<u32>(count);
146 }
147
148 void ListCommonTicket(Kernel::HLERequestContext& ctx) {
149 u32 out_entries;
150 if (keys.GetCommonTickets().empty())
151 out_entries = 0;
152 else
153 out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
154
155 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
156
157 keys.PopulateTickets();
158 const auto tickets = keys.GetCommonTickets();
159 std::vector<u128> ids;
160 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
161 [](const auto& pair) { return pair.first; });
162
163 out_entries = std::min<u32>(ids.size(), out_entries);
164 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
165
166 IPC::ResponseBuilder rb{ctx, 3};
167 rb.Push(RESULT_SUCCESS);
168 rb.Push<u32>(out_entries);
54 } 169 }
170
171 void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
172 u32 out_entries;
173 if (keys.GetPersonalizedTickets().empty())
174 out_entries = 0;
175 else
176 out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
177
178 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
179
180 keys.PopulateTickets();
181 const auto tickets = keys.GetPersonalizedTickets();
182 std::vector<u128> ids;
183 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
184 [](const auto& pair) { return pair.first; });
185
186 out_entries = std::min<u32>(ids.size(), out_entries);
187 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
188
189 IPC::ResponseBuilder rb{ctx, 3};
190 rb.Push(RESULT_SUCCESS);
191 rb.Push<u32>(out_entries);
192 }
193
194 void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
195 IPC::RequestParser rp{ctx};
196 const auto rights_id = rp.PopRaw<u128>();
197
198 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
199
200 if (!CheckRightsId(ctx, rights_id))
201 return;
202
203 const auto ticket = keys.GetCommonTickets().at(rights_id);
204
205 IPC::ResponseBuilder rb{ctx, 4};
206 rb.Push(RESULT_SUCCESS);
207 rb.Push<u64>(ticket.GetSize());
208 }
209
210 void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
211 IPC::RequestParser rp{ctx};
212 const auto rights_id = rp.PopRaw<u128>();
213
214 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
215
216 if (!CheckRightsId(ctx, rights_id))
217 return;
218
219 const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
220
221 IPC::ResponseBuilder rb{ctx, 4};
222 rb.Push(RESULT_SUCCESS);
223 rb.Push<u64>(ticket.GetSize());
224 }
225
226 void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
227 IPC::RequestParser rp{ctx};
228 const auto rights_id = rp.PopRaw<u128>();
229
230 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
231
232 if (!CheckRightsId(ctx, rights_id))
233 return;
234
235 const auto ticket = keys.GetCommonTickets().at(rights_id);
236
237 const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
238 ctx.WriteBuffer(&ticket, write_size);
239
240 IPC::ResponseBuilder rb{ctx, 4};
241 rb.Push(RESULT_SUCCESS);
242 rb.Push<u64>(write_size);
243 }
244
245 void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
246 IPC::RequestParser rp{ctx};
247 const auto rights_id = rp.PopRaw<u128>();
248
249 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
250
251 if (!CheckRightsId(ctx, rights_id))
252 return;
253
254 const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
255
256 const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
257 ctx.WriteBuffer(&ticket, write_size);
258
259 IPC::ResponseBuilder rb{ctx, 4};
260 rb.Push(RESULT_SUCCESS);
261 rb.Push<u64>(write_size);
262 }
263
264 Core::Crypto::KeyManager keys;
55}; 265};
56 266
57void InstallInterfaces(SM::ServiceManager& service_manager) { 267void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index fe49c2161..01fa06ad3 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -5,7 +5,7 @@
5#include <array> 5#include <array>
6#include <cstring> 6#include <cstring>
7#include <ctime> 7#include <ctime>
8#include <fmt/time.h> 8#include <fmt/chrono.h>
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/scm_rev.h" 11#include "common/scm_rev.h"
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 1e81f776f..e47fe8188 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -636,10 +636,15 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
636 return LedPattern{0, 0, 0, 0}; 636 return LedPattern{0, 0, 0, 0};
637 }; 637 };
638} 638}
639
639void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { 640void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
640 can_controllers_vibrate = can_vibrate; 641 can_controllers_vibrate = can_vibrate;
641} 642}
642 643
644bool Controller_NPad::IsVibrationEnabled() const {
645 return can_controllers_vibrate;
646}
647
643void Controller_NPad::ClearAllConnectedControllers() { 648void Controller_NPad::ClearAllConnectedControllers() {
644 for (auto& controller : connected_controllers) { 649 for (auto& controller : connected_controllers) {
645 if (controller.is_connected && controller.type != NPadControllerType::None) { 650 if (controller.is_connected && controller.type != NPadControllerType::None) {
@@ -648,6 +653,7 @@ void Controller_NPad::ClearAllConnectedControllers() {
648 } 653 }
649 } 654 }
650} 655}
656
651void Controller_NPad::DisconnectAllConnectedControllers() { 657void Controller_NPad::DisconnectAllConnectedControllers() {
652 std::for_each(connected_controllers.begin(), connected_controllers.end(), 658 std::for_each(connected_controllers.begin(), connected_controllers.end(),
653 [](ControllerHolder& controller) { controller.is_connected = false; }); 659 [](ControllerHolder& controller) { controller.is_connected = false; });
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 4b6c1083f..f28b36806 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -119,6 +119,7 @@ public:
119 void DisconnectNPad(u32 npad_id); 119 void DisconnectNPad(u32 npad_id);
120 LedPattern GetLedPattern(u32 npad_id); 120 LedPattern GetLedPattern(u32 npad_id);
121 void SetVibrationEnabled(bool can_vibrate); 121 void SetVibrationEnabled(bool can_vibrate);
122 bool IsVibrationEnabled() const;
122 void ClearAllConnectedControllers(); 123 void ClearAllConnectedControllers();
123 void DisconnectAllConnectedControllers(); 124 void DisconnectAllConnectedControllers();
124 void ConnectAllDisconnectedControllers(); 125 void ConnectAllDisconnectedControllers();
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 0bd24b8eb..f8b1ca816 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -216,8 +216,8 @@ Hid::Hid() : ServiceFramework("hid") {
216 {201, &Hid::SendVibrationValue, "SendVibrationValue"}, 216 {201, &Hid::SendVibrationValue, "SendVibrationValue"},
217 {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, 217 {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
218 {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, 218 {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
219 {204, nullptr, "PermitVibration"}, 219 {204, &Hid::PermitVibration, "PermitVibration"},
220 {205, nullptr, "IsVibrationPermitted"}, 220 {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"},
221 {206, &Hid::SendVibrationValues, "SendVibrationValues"}, 221 {206, &Hid::SendVibrationValues, "SendVibrationValues"},
222 {207, nullptr, "SendVibrationGcErmCommand"}, 222 {207, nullptr, "SendVibrationGcErmCommand"},
223 {208, nullptr, "GetActualVibrationGcErmCommand"}, 223 {208, nullptr, "GetActualVibrationGcErmCommand"},
@@ -679,6 +679,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
679 rb.PushIpcInterface<IActiveVibrationDeviceList>(); 679 rb.PushIpcInterface<IActiveVibrationDeviceList>();
680} 680}
681 681
682void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx};
684 const auto can_vibrate{rp.Pop<bool>()};
685 applet_resource->GetController<Controller_NPad>(HidController::NPad)
686 .SetVibrationEnabled(can_vibrate);
687
688 LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
689
690 IPC::ResponseBuilder rb{ctx, 2};
691 rb.Push(RESULT_SUCCESS);
692}
693
694void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
695 LOG_DEBUG(Service_HID, "called");
696
697 IPC::ResponseBuilder rb{ctx, 3};
698 rb.Push(RESULT_SUCCESS);
699 rb.Push(
700 applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled());
701}
702
682void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { 703void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx}; 704 IPC::RequestParser rp{ctx};
684 const auto applet_resource_user_id{rp.Pop<u64>()}; 705 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 28260ef1b..2fd6d9fc7 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -114,6 +114,8 @@ private:
114 void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); 114 void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
115 void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); 115 void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
116 void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); 116 void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
117 void PermitVibration(Kernel::HLERequestContext& ctx);
118 void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
117 void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); 119 void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
118 void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); 120 void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
119 void StopSixAxisSensor(Kernel::HLERequestContext& ctx); 121 void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 5d4c3e6ea..cfe0771e2 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -5,8 +5,8 @@
5#include <ctime> 5#include <ctime>
6#include <fstream> 6#include <fstream>
7 7
8#include <fmt/chrono.h>
8#include <fmt/format.h> 9#include <fmt/format.h>
9#include <fmt/time.h>
10#include <json.hpp> 10#include <json.hpp>
11 11
12#include "common/file_util.h" 12#include "common/file_util.h"
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 03d434b28..4f59a87b4 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -14,12 +14,22 @@
14namespace OpenGL { 14namespace OpenGL {
15 15
16namespace { 16namespace {
17
17template <typename T> 18template <typename T>
18T GetInteger(GLenum pname) { 19T GetInteger(GLenum pname) {
19 GLint temporary; 20 GLint temporary;
20 glGetIntegerv(pname, &temporary); 21 glGetIntegerv(pname, &temporary);
21 return static_cast<T>(temporary); 22 return static_cast<T>(temporary);
22} 23}
24
25bool TestProgram(const GLchar* glsl) {
26 const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)};
27 GLint link_status;
28 glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
29 glDeleteProgram(shader);
30 return link_status == GL_TRUE;
31}
32
23} // Anonymous namespace 33} // Anonymous namespace
24 34
25Device::Device() { 35Device::Device() {
@@ -32,6 +42,11 @@ Device::Device() {
32 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; 42 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
33 has_variable_aoffi = TestVariableAoffi(); 43 has_variable_aoffi = TestVariableAoffi();
34 has_component_indexing_bug = TestComponentIndexingBug(); 44 has_component_indexing_bug = TestComponentIndexingBug();
45 has_precise_bug = TestPreciseBug();
46
47 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
48 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
49 LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
35} 50}
36 51
37Device::Device(std::nullptr_t) { 52Device::Device(std::nullptr_t) {
@@ -42,30 +57,21 @@ Device::Device(std::nullptr_t) {
42 has_vertex_viewport_layer = true; 57 has_vertex_viewport_layer = true;
43 has_variable_aoffi = true; 58 has_variable_aoffi = true;
44 has_component_indexing_bug = false; 59 has_component_indexing_bug = false;
60 has_precise_bug = false;
45} 61}
46 62
47bool Device::TestVariableAoffi() { 63bool Device::TestVariableAoffi() {
48 const GLchar* AOFFI_TEST = R"(#version 430 core 64 return TestProgram(R"(#version 430 core
49// This is a unit test, please ignore me on apitrace bug reports. 65// This is a unit test, please ignore me on apitrace bug reports.
50uniform sampler2D tex; 66uniform sampler2D tex;
51uniform ivec2 variable_offset; 67uniform ivec2 variable_offset;
52out vec4 output_attribute; 68out vec4 output_attribute;
53void main() { 69void main() {
54 output_attribute = textureOffset(tex, vec2(0), variable_offset); 70 output_attribute = textureOffset(tex, vec2(0), variable_offset);
55} 71})");
56)";
57 const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)};
58 GLint link_status{};
59 glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
60 glDeleteProgram(shader);
61
62 const bool supported{link_status == GL_TRUE};
63 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported);
64 return supported;
65} 72}
66 73
67bool Device::TestComponentIndexingBug() { 74bool Device::TestComponentIndexingBug() {
68 constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}";
69 const GLchar* COMPONENT_TEST = R"(#version 430 core 75 const GLchar* COMPONENT_TEST = R"(#version 430 core
70layout (std430, binding = 0) buffer OutputBuffer { 76layout (std430, binding = 0) buffer OutputBuffer {
71 uint output_value; 77 uint output_value;
@@ -105,12 +111,21 @@ void main() {
105 GLuint result; 111 GLuint result;
106 glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); 112 glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result);
107 if (result != values.at(index)) { 113 if (result != values.at(index)) {
108 LOG_INFO(Render_OpenGL, log_message, true);
109 return true; 114 return true;
110 } 115 }
111 } 116 }
112 LOG_INFO(Render_OpenGL, log_message, false);
113 return false; 117 return false;
114} 118}
115 119
120bool Device::TestPreciseBug() {
121 return !TestProgram(R"(#version 430 core
122in vec3 coords;
123out float out_value;
124uniform sampler2DShadow tex;
125void main() {
126 precise float tmp_value = vec4(texture(tex, coords)).x;
127 out_value = tmp_value;
128})");
129}
130
116} // namespace OpenGL 131} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 3ef7c6dd8..ba6dcd3be 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -46,9 +46,14 @@ public:
46 return has_component_indexing_bug; 46 return has_component_indexing_bug;
47 } 47 }
48 48
49 bool HasPreciseBug() const {
50 return has_precise_bug;
51 }
52
49private: 53private:
50 static bool TestVariableAoffi(); 54 static bool TestVariableAoffi();
51 static bool TestComponentIndexingBug(); 55 static bool TestComponentIndexingBug();
56 static bool TestPreciseBug();
52 57
53 std::size_t uniform_buffer_alignment{}; 58 std::size_t uniform_buffer_alignment{};
54 std::size_t shader_storage_alignment{}; 59 std::size_t shader_storage_alignment{};
@@ -58,6 +63,7 @@ private:
58 bool has_vertex_viewport_layer{}; 63 bool has_vertex_viewport_layer{};
59 bool has_variable_aoffi{}; 64 bool has_variable_aoffi{};
60 bool has_component_indexing_bug{}; 65 bool has_component_indexing_bug{};
66 bool has_precise_bug{};
61}; 67};
62 68
63} // namespace OpenGL 69} // 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 359d58cbe..a5cc1a86f 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -39,7 +39,7 @@ using namespace VideoCommon::Shader;
39using Maxwell = Tegra::Engines::Maxwell3D::Regs; 39using Maxwell = Tegra::Engines::Maxwell3D::Regs;
40using Operation = const OperationNode&; 40using Operation = const OperationNode&;
41 41
42enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; 42enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat };
43 43
44struct TextureAoffi {}; 44struct TextureAoffi {};
45using TextureArgument = std::pair<Type, Node>; 45using TextureArgument = std::pair<Type, Node>;
@@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>;
48constexpr u32 MAX_CONSTBUFFER_ELEMENTS = 48constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); 49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float));
50 50
51class ShaderWriter { 51class ShaderWriter final {
52public: 52public:
53 void AddExpression(std::string_view text) { 53 void AddExpression(std::string_view text) {
54 DEBUG_ASSERT(scope >= 0); 54 DEBUG_ASSERT(scope >= 0);
@@ -93,9 +93,157 @@ private:
93 u32 temporary_index = 1; 93 u32 temporary_index = 1;
94}; 94};
95 95
96class Expression final {
97public:
98 Expression(std::string code, Type type) : code{std::move(code)}, type{type} {
99 ASSERT(type != Type::Void);
100 }
101 Expression() : type{Type::Void} {}
102
103 Type GetType() const {
104 return type;
105 }
106
107 std::string GetCode() const {
108 return code;
109 }
110
111 void CheckVoid() const {
112 ASSERT(type == Type::Void);
113 }
114
115 std::string As(Type type) const {
116 switch (type) {
117 case Type::Bool:
118 return AsBool();
119 case Type::Bool2:
120 return AsBool2();
121 case Type::Float:
122 return AsFloat();
123 case Type::Int:
124 return AsInt();
125 case Type::Uint:
126 return AsUint();
127 case Type::HalfFloat:
128 return AsHalfFloat();
129 default:
130 UNREACHABLE_MSG("Invalid type");
131 return code;
132 }
133 }
134
135 std::string AsBool() const {
136 switch (type) {
137 case Type::Bool:
138 return code;
139 default:
140 UNREACHABLE_MSG("Incompatible types");
141 return code;
142 }
143 }
144
145 std::string AsBool2() const {
146 switch (type) {
147 case Type::Bool2:
148 return code;
149 default:
150 UNREACHABLE_MSG("Incompatible types");
151 return code;
152 }
153 }
154
155 std::string AsFloat() const {
156 switch (type) {
157 case Type::Float:
158 return code;
159 case Type::Uint:
160 return fmt::format("utof({})", code);
161 case Type::Int:
162 return fmt::format("itof({})", code);
163 case Type::HalfFloat:
164 return fmt::format("utof(packHalf2x16({}))", code);
165 default:
166 UNREACHABLE_MSG("Incompatible types");
167 return code;
168 }
169 }
170
171 std::string AsInt() const {
172 switch (type) {
173 case Type::Float:
174 return fmt::format("ftoi({})", code);
175 case Type::Uint:
176 return fmt::format("int({})", code);
177 case Type::Int:
178 return code;
179 case Type::HalfFloat:
180 return fmt::format("int(packHalf2x16({}))", code);
181 default:
182 UNREACHABLE_MSG("Incompatible types");
183 return code;
184 }
185 }
186
187 std::string AsUint() const {
188 switch (type) {
189 case Type::Float:
190 return fmt::format("ftou({})", code);
191 case Type::Uint:
192 return code;
193 case Type::Int:
194 return fmt::format("uint({})", code);
195 case Type::HalfFloat:
196 return fmt::format("packHalf2x16({})", code);
197 default:
198 UNREACHABLE_MSG("Incompatible types");
199 return code;
200 }
201 }
202
203 std::string AsHalfFloat() const {
204 switch (type) {
205 case Type::Float:
206 return fmt::format("unpackHalf2x16(ftou({}))", code);
207 case Type::Uint:
208 return fmt::format("unpackHalf2x16({})", code);
209 case Type::Int:
210 return fmt::format("unpackHalf2x16(int({}))", code);
211 case Type::HalfFloat:
212 return code;
213 default:
214 UNREACHABLE_MSG("Incompatible types");
215 return code;
216 }
217 }
218
219private:
220 std::string code;
221 Type type{};
222};
223
224constexpr const char* GetTypeString(Type type) {
225 switch (type) {
226 case Type::Bool:
227 return "bool";
228 case Type::Bool2:
229 return "bvec2";
230 case Type::Float:
231 return "float";
232 case Type::Int:
233 return "int";
234 case Type::Uint:
235 return "uint";
236 case Type::HalfFloat:
237 return "vec2";
238 default:
239 UNREACHABLE_MSG("Invalid type");
240 return "<invalid type>";
241 }
242}
243
96/// Generates code to use for a swizzle operation. 244/// Generates code to use for a swizzle operation.
97constexpr const char* GetSwizzle(u32 element) { 245constexpr const char* GetSwizzle(u32 element) {
98 constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"}; 246 constexpr std::array swizzle = {".x", ".y", ".z", ".w"};
99 return swizzle.at(element); 247 return swizzle.at(element);
100} 248}
101 249
@@ -134,8 +282,8 @@ constexpr bool IsGenericAttribute(Attribute::Index index) {
134 return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; 282 return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31;
135} 283}
136 284
137constexpr Attribute::Index ToGenericAttribute(u32 value) { 285constexpr Attribute::Index ToGenericAttribute(u64 value) {
138 return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0)); 286 return static_cast<Attribute::Index>(value + static_cast<u64>(Attribute::Index::Attribute_0));
139} 287}
140 288
141u32 GetGenericAttributeIndex(Attribute::Index index) { 289u32 GetGenericAttributeIndex(Attribute::Index index) {
@@ -191,7 +339,7 @@ public:
191 339
192 // VM's program counter 340 // VM's program counter
193 const auto first_address = ir.GetBasicBlocks().begin()->first; 341 const auto first_address = ir.GetBasicBlocks().begin()->first;
194 code.AddLine("uint jmp_to = {}u;", first_address); 342 code.AddLine("uint jmp_to = {}U;", first_address);
195 343
196 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems 344 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
197 // unlikely that shaders will use 20 nested SSYs and PBKs. 345 // unlikely that shaders will use 20 nested SSYs and PBKs.
@@ -199,7 +347,7 @@ public:
199 constexpr u32 FLOW_STACK_SIZE = 20; 347 constexpr u32 FLOW_STACK_SIZE = 20;
200 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { 348 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
201 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); 349 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
202 code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); 350 code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
203 } 351 }
204 } 352 }
205 353
@@ -210,7 +358,7 @@ public:
210 358
211 for (const auto& pair : ir.GetBasicBlocks()) { 359 for (const auto& pair : ir.GetBasicBlocks()) {
212 const auto [address, bb] = pair; 360 const auto [address, bb] = pair;
213 code.AddLine("case 0x{:x}u: {{", address); 361 code.AddLine("case 0x{:X}U: {{", address);
214 ++code.scope; 362 ++code.scope;
215 363
216 VisitBlock(bb); 364 VisitBlock(bb);
@@ -322,7 +470,7 @@ private:
322 void DeclareRegisters() { 470 void DeclareRegisters() {
323 const auto& registers = ir.GetRegisters(); 471 const auto& registers = ir.GetRegisters();
324 for (const u32 gpr : registers) { 472 for (const u32 gpr : registers) {
325 code.AddLine("float {} = 0;", GetRegister(gpr)); 473 code.AddLine("float {} = 0.0f;", GetRegister(gpr));
326 } 474 }
327 if (!registers.empty()) { 475 if (!registers.empty()) {
328 code.AddNewLine(); 476 code.AddNewLine();
@@ -348,7 +496,7 @@ private:
348 return; 496 return;
349 } 497 }
350 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; 498 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4;
351 code.AddLine("float {}[{}];", GetLocalMemory(), element_count); 499 code.AddLine("uint {}[{}];", GetLocalMemory(), element_count);
352 code.AddNewLine(); 500 code.AddNewLine();
353 } 501 }
354 502
@@ -371,8 +519,6 @@ private:
371 return "noperspective "; 519 return "noperspective ";
372 default: 520 default:
373 case AttributeUse::Unused: 521 case AttributeUse::Unused:
374 UNREACHABLE_MSG("Unused attribute being fetched");
375 return {};
376 UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); 522 UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute));
377 return {}; 523 return {};
378 } 524 }
@@ -449,7 +595,7 @@ private:
449 const auto [index, size] = entry; 595 const auto [index, size] = entry;
450 code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, 596 code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index,
451 GetConstBufferBlock(index)); 597 GetConstBufferBlock(index));
452 code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); 598 code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS);
453 code.AddLine("}};"); 599 code.AddLine("}};");
454 code.AddNewLine(); 600 code.AddNewLine();
455 } 601 }
@@ -470,7 +616,7 @@ private:
470 616
471 code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", 617 code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{",
472 base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); 618 base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base));
473 code.AddLine(" float {}[];", GetGlobalMemory(base)); 619 code.AddLine(" uint {}[];", GetGlobalMemory(base));
474 code.AddLine("}};"); 620 code.AddLine("}};");
475 code.AddNewLine(); 621 code.AddNewLine();
476 } 622 }
@@ -528,7 +674,7 @@ private:
528 if (!ir.HasPhysicalAttributes()) { 674 if (!ir.HasPhysicalAttributes()) {
529 return; 675 return;
530 } 676 }
531 code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); 677 code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{");
532 ++code.scope; 678 ++code.scope;
533 code.AddLine("switch (physical_address) {{"); 679 code.AddLine("switch (physical_address) {{");
534 680
@@ -537,15 +683,16 @@ private:
537 for (u32 index = 0; index < num_attributes; ++index) { 683 for (u32 index = 0; index < num_attributes; ++index) {
538 const auto attribute{ToGenericAttribute(index)}; 684 const auto attribute{ToGenericAttribute(index)};
539 for (u32 element = 0; element < 4; ++element) { 685 for (u32 element = 0; element < 4; ++element) {
540 constexpr u32 generic_base{0x80}; 686 constexpr u32 generic_base = 0x80;
541 constexpr u32 generic_stride{16}; 687 constexpr u32 generic_stride = 16;
542 constexpr u32 element_stride{4}; 688 constexpr u32 element_stride = 4;
543 const u32 address{generic_base + index * generic_stride + element * element_stride}; 689 const u32 address{generic_base + index * generic_stride + element * element_stride};
544 690
545 const bool declared{stage != ProgramType::Fragment || 691 const bool declared = stage != ProgramType::Fragment ||
546 header.ps.GetAttributeUse(index) != AttributeUse::Unused}; 692 header.ps.GetAttributeUse(index) != AttributeUse::Unused;
547 const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; 693 const std::string value =
548 code.AddLine("case 0x{:x}: return {};", address, value); 694 declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f";
695 code.AddLine("case 0x{:X}U: return {};", address, value);
549 } 696 }
550 } 697 }
551 698
@@ -590,13 +737,11 @@ private:
590 737
591 void VisitBlock(const NodeBlock& bb) { 738 void VisitBlock(const NodeBlock& bb) {
592 for (const auto& node : bb) { 739 for (const auto& node : bb) {
593 if (const std::string expr = Visit(node); !expr.empty()) { 740 Visit(node).CheckVoid();
594 code.AddLine(expr);
595 }
596 } 741 }
597 } 742 }
598 743
599 std::string Visit(const Node& node) { 744 Expression Visit(const Node& node) {
600 if (const auto operation = std::get_if<OperationNode>(&*node)) { 745 if (const auto operation = std::get_if<OperationNode>(&*node)) {
601 const auto operation_index = static_cast<std::size_t>(operation->GetCode()); 746 const auto operation_index = static_cast<std::size_t>(operation->GetCode());
602 if (operation_index >= operation_decompilers.size()) { 747 if (operation_index >= operation_decompilers.size()) {
@@ -614,18 +759,18 @@ private:
614 if (const auto gpr = std::get_if<GprNode>(&*node)) { 759 if (const auto gpr = std::get_if<GprNode>(&*node)) {
615 const u32 index = gpr->GetIndex(); 760 const u32 index = gpr->GetIndex();
616 if (index == Register::ZeroIndex) { 761 if (index == Register::ZeroIndex) {
617 return "0"; 762 return {"0U", Type::Uint};
618 } 763 }
619 return GetRegister(index); 764 return {GetRegister(index), Type::Float};
620 } 765 }
621 766
622 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { 767 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
623 const u32 value = immediate->GetValue(); 768 const u32 value = immediate->GetValue();
624 if (value < 10) { 769 if (value < 10) {
625 // For eyecandy avoid using hex numbers on single digits 770 // For eyecandy avoid using hex numbers on single digits
626 return fmt::format("utof({}u)", immediate->GetValue()); 771 return {fmt::format("{}U", immediate->GetValue()), Type::Uint};
627 } 772 }
628 return fmt::format("utof(0x{:x}u)", immediate->GetValue()); 773 return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint};
629 } 774 }
630 775
631 if (const auto predicate = std::get_if<PredicateNode>(&*node)) { 776 if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
@@ -640,17 +785,18 @@ private:
640 } 785 }
641 }(); 786 }();
642 if (predicate->IsNegated()) { 787 if (predicate->IsNegated()) {
643 return fmt::format("!({})", value); 788 return {fmt::format("!({})", value), Type::Bool};
644 } 789 }
645 return value; 790 return {value, Type::Bool};
646 } 791 }
647 792
648 if (const auto abuf = std::get_if<AbufNode>(&*node)) { 793 if (const auto abuf = std::get_if<AbufNode>(&*node)) {
649 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, 794 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry,
650 "Physical attributes in geometry shaders are not implemented"); 795 "Physical attributes in geometry shaders are not implemented");
651 if (abuf->IsPhysicalBuffer()) { 796 if (abuf->IsPhysicalBuffer()) {
652 return fmt::format("readPhysicalAttribute(ftou({}))", 797 return {fmt::format("ReadPhysicalAttribute({})",
653 Visit(abuf->GetPhysicalAddress())); 798 Visit(abuf->GetPhysicalAddress()).AsUint()),
799 Type::Float};
654 } 800 }
655 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); 801 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer());
656 } 802 }
@@ -661,59 +807,64 @@ private:
661 // Direct access 807 // Direct access
662 const u32 offset_imm = immediate->GetValue(); 808 const u32 offset_imm = immediate->GetValue();
663 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); 809 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
664 return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), 810 return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
665 offset_imm / (4 * 4), (offset_imm / 4) % 4); 811 offset_imm / (4 * 4), (offset_imm / 4) % 4),
812 Type::Uint};
666 } 813 }
667 814
668 if (std::holds_alternative<OperationNode>(*offset)) { 815 if (std::holds_alternative<OperationNode>(*offset)) {
669 // Indirect access 816 // Indirect access
670 const std::string final_offset = code.GenerateTemporary(); 817 const std::string final_offset = code.GenerateTemporary();
671 code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); 818 code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
672 819
673 if (!device.HasComponentIndexingBug()) { 820 if (!device.HasComponentIndexingBug()) {
674 return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), 821 return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
675 final_offset, final_offset); 822 final_offset, final_offset),
823 Type::Uint};
676 } 824 }
677 825
678 // AMD's proprietary GLSL compiler emits ill code for variable component access. 826 // AMD's proprietary GLSL compiler emits ill code for variable component access.
679 // To bypass this driver bug generate 4 ifs, one per each component. 827 // To bypass this driver bug generate 4 ifs, one per each component.
680 const std::string pack = code.GenerateTemporary(); 828 const std::string pack = code.GenerateTemporary();
681 code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), 829 code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
682 final_offset); 830 final_offset);
683 831
684 const std::string result = code.GenerateTemporary(); 832 const std::string result = code.GenerateTemporary();
685 code.AddLine("float {};", result); 833 code.AddLine("uint {};", result);
686 for (u32 swizzle = 0; swizzle < 4; ++swizzle) { 834 for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
687 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, 835 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
688 pack, GetSwizzle(swizzle)); 836 pack, GetSwizzle(swizzle));
689 } 837 }
690 return result; 838 return {result, Type::Uint};
691 } 839 }
692 840
693 UNREACHABLE_MSG("Unmanaged offset node type"); 841 UNREACHABLE_MSG("Unmanaged offset node type");
694 } 842 }
695 843
696 if (const auto gmem = std::get_if<GmemNode>(&*node)) { 844 if (const auto gmem = std::get_if<GmemNode>(&*node)) {
697 const std::string real = Visit(gmem->GetRealAddress()); 845 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
698 const std::string base = Visit(gmem->GetBaseAddress()); 846 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
699 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 847 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
700 return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 848 return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
849 Type::Uint};
701 } 850 }
702 851
703 if (const auto lmem = std::get_if<LmemNode>(&*node)) { 852 if (const auto lmem = std::get_if<LmemNode>(&*node)) {
704 if (stage == ProgramType::Compute) { 853 if (stage == ProgramType::Compute) {
705 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 854 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
706 } 855 }
707 return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 856 return {
857 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
858 Type::Uint};
708 } 859 }
709 860
710 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { 861 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
711 return GetInternalFlag(internal_flag->GetFlag()); 862 return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool};
712 } 863 }
713 864
714 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { 865 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
715 // It's invalid to call conditional on nested nodes, use an operation instead 866 // It's invalid to call conditional on nested nodes, use an operation instead
716 code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); 867 code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool());
717 ++code.scope; 868 ++code.scope;
718 869
719 VisitBlock(conditional->GetCode()); 870 VisitBlock(conditional->GetCode());
@@ -724,20 +875,21 @@ private:
724 } 875 }
725 876
726 if (const auto comment = std::get_if<CommentNode>(&*node)) { 877 if (const auto comment = std::get_if<CommentNode>(&*node)) {
727 return "// " + comment->GetText(); 878 code.AddLine("// " + comment->GetText());
879 return {};
728 } 880 }
729 881
730 UNREACHABLE(); 882 UNREACHABLE();
731 return {}; 883 return {};
732 } 884 }
733 885
734 std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { 886 Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) {
735 const auto GeometryPass = [&](std::string_view name) { 887 const auto GeometryPass = [&](std::string_view name) {
736 if (stage == ProgramType::Geometry && buffer) { 888 if (stage == ProgramType::Geometry && buffer) {
737 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games 889 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
738 // set an 0x80000000 index for those and the shader fails to build. Find out why 890 // set an 0x80000000 index for those and the shader fails to build. Find out why
739 // this happens and what's its intent. 891 // this happens and what's its intent.
740 return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); 892 return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint());
741 } 893 }
742 return std::string(name); 894 return std::string(name);
743 }; 895 };
@@ -746,25 +898,27 @@ private:
746 case Attribute::Index::Position: 898 case Attribute::Index::Position:
747 switch (stage) { 899 switch (stage) {
748 case ProgramType::Geometry: 900 case ProgramType::Geometry:
749 return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), 901 return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(),
750 GetSwizzle(element)); 902 GetSwizzle(element)),
903 Type::Float};
751 case ProgramType::Fragment: 904 case ProgramType::Fragment:
752 return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); 905 return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)),
906 Type::Float};
753 default: 907 default:
754 UNREACHABLE(); 908 UNREACHABLE();
755 } 909 }
756 case Attribute::Index::PointCoord: 910 case Attribute::Index::PointCoord:
757 switch (element) { 911 switch (element) {
758 case 0: 912 case 0:
759 return "gl_PointCoord.x"; 913 return {"gl_PointCoord.x", Type::Float};
760 case 1: 914 case 1:
761 return "gl_PointCoord.y"; 915 return {"gl_PointCoord.y", Type::Float};
762 case 2: 916 case 2:
763 case 3: 917 case 3:
764 return "0"; 918 return {"0.0f", Type::Float};
765 } 919 }
766 UNREACHABLE(); 920 UNREACHABLE();
767 return "0"; 921 return {"0", Type::Int};
768 case Attribute::Index::TessCoordInstanceIDVertexID: 922 case Attribute::Index::TessCoordInstanceIDVertexID:
769 // TODO(Subv): Find out what the values are for the first two elements when inside a 923 // TODO(Subv): Find out what the values are for the first two elements when inside a
770 // vertex shader, and what's the value of the fourth element when inside a Tess Eval 924 // vertex shader, and what's the value of the fourth element when inside a Tess Eval
@@ -773,44 +927,49 @@ private:
773 switch (element) { 927 switch (element) {
774 case 2: 928 case 2:
775 // Config pack's first value is instance_id. 929 // Config pack's first value is instance_id.
776 return "uintBitsToFloat(config_pack[0])"; 930 return {"config_pack[0]", Type::Uint};
777 case 3: 931 case 3:
778 return "uintBitsToFloat(gl_VertexID)"; 932 return {"gl_VertexID", Type::Int};
779 } 933 }
780 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); 934 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
781 return "0"; 935 return {"0", Type::Int};
782 case Attribute::Index::FrontFacing: 936 case Attribute::Index::FrontFacing:
783 // TODO(Subv): Find out what the values are for the other elements. 937 // TODO(Subv): Find out what the values are for the other elements.
784 ASSERT(stage == ProgramType::Fragment); 938 ASSERT(stage == ProgramType::Fragment);
785 switch (element) { 939 switch (element) {
786 case 3: 940 case 3:
787 return "itof(gl_FrontFacing ? -1 : 0)"; 941 return {"(gl_FrontFacing ? -1 : 0)", Type::Int};
788 } 942 }
789 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); 943 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
790 return "0"; 944 return {"0", Type::Int};
791 default: 945 default:
792 if (IsGenericAttribute(attribute)) { 946 if (IsGenericAttribute(attribute)) {
793 return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); 947 return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element),
948 Type::Float};
794 } 949 }
795 break; 950 break;
796 } 951 }
797 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); 952 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
798 return "0"; 953 return {"0", Type::Int};
799 } 954 }
800 955
801 std::string ApplyPrecise(Operation operation, const std::string& value) { 956 Expression ApplyPrecise(Operation operation, std::string value, Type type) {
802 if (!IsPrecise(operation)) { 957 if (!IsPrecise(operation)) {
803 return value; 958 return {std::move(value), type};
804 } 959 }
805 // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders 960 // Old Nvidia drivers have a bug with precise and texture sampling. These are more likely to
806 const std::string precise = stage != ProgramType::Fragment ? "precise " : ""; 961 // be found in fragment shaders, so we disable precise there. There are vertex shaders that
962 // also fail to build but nobody seems to care about those.
963 // Note: Only bugged drivers will skip precise.
964 const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment;
807 965
808 const std::string temporary = code.GenerateTemporary(); 966 std::string temporary = code.GenerateTemporary();
809 code.AddLine("{}float {} = {};", precise, temporary, value); 967 code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type),
810 return temporary; 968 temporary, value);
969 return {std::move(temporary), type};
811 } 970 }
812 971
813 std::string VisitOperand(Operation operation, std::size_t operand_index) { 972 Expression VisitOperand(Operation operation, std::size_t operand_index) {
814 const auto& operand = operation[operand_index]; 973 const auto& operand = operation[operand_index];
815 const bool parent_precise = IsPrecise(operation); 974 const bool parent_precise = IsPrecise(operation);
816 const bool child_precise = IsPrecise(operand); 975 const bool child_precise = IsPrecise(operand);
@@ -819,19 +978,16 @@ private:
819 return Visit(operand); 978 return Visit(operand);
820 } 979 }
821 980
822 const std::string temporary = code.GenerateTemporary(); 981 Expression value = Visit(operand);
823 code.AddLine("float {} = {};", temporary, Visit(operand)); 982 std::string temporary = code.GenerateTemporary();
824 return temporary; 983 code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode());
825 } 984 return {std::move(temporary), value.GetType()};
826
827 std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) {
828 return CastOperand(VisitOperand(operation, operand_index), type);
829 } 985 }
830 986
831 std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { 987 Expression GetOutputAttribute(const AbufNode* abuf) {
832 switch (const auto attribute = abuf->GetIndex()) { 988 switch (const auto attribute = abuf->GetIndex()) {
833 case Attribute::Index::Position: 989 case Attribute::Index::Position:
834 return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); 990 return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float};
835 case Attribute::Index::LayerViewportPointSize: 991 case Attribute::Index::LayerViewportPointSize:
836 switch (abuf->GetElement()) { 992 switch (abuf->GetElement()) {
837 case 0: 993 case 0:
@@ -841,119 +997,79 @@ private:
841 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 997 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
842 return {}; 998 return {};
843 } 999 }
844 return std::make_pair("gl_Layer", true); 1000 return {"gl_Layer", Type::Int};
845 case 2: 1001 case 2:
846 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1002 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
847 return {}; 1003 return {};
848 } 1004 }
849 return std::make_pair("gl_ViewportIndex", true); 1005 return {"gl_ViewportIndex", Type::Int};
850 case 3: 1006 case 3:
851 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); 1007 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
852 return std::make_pair("gl_PointSize", false); 1008 return {"gl_PointSize", Type::Float};
853 } 1009 }
854 return {}; 1010 return {};
855 case Attribute::Index::ClipDistances0123: 1011 case Attribute::Index::ClipDistances0123:
856 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); 1012 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float};
857 case Attribute::Index::ClipDistances4567: 1013 case Attribute::Index::ClipDistances4567:
858 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), 1014 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float};
859 false);
860 default: 1015 default:
861 if (IsGenericAttribute(attribute)) { 1016 if (IsGenericAttribute(attribute)) {
862 return std::make_pair( 1017 return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()),
863 GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); 1018 Type::Float};
864 } 1019 }
865 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); 1020 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
866 return {}; 1021 return {};
867 } 1022 }
868 } 1023 }
869 1024
870 std::string CastOperand(const std::string& value, Type type) const { 1025 Expression GenerateUnary(Operation operation, std::string_view func, Type result_type,
871 switch (type) { 1026 Type type_a) {
872 case Type::Bool: 1027 std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a));
873 case Type::Bool2: 1028 return ApplyPrecise(operation, std::move(op_str), result_type);
874 case Type::Float:
875 return value;
876 case Type::Int:
877 return fmt::format("ftoi({})", value);
878 case Type::Uint:
879 return fmt::format("ftou({})", value);
880 case Type::HalfFloat:
881 return fmt::format("toHalf2({})", value);
882 }
883 UNREACHABLE();
884 return value;
885 } 1029 }
886 1030
887 std::string BitwiseCastResult(const std::string& value, Type type, 1031 Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type,
888 bool needs_parenthesis = false) { 1032 Type type_a, Type type_b) {
889 switch (type) { 1033 const std::string op_a = VisitOperand(operation, 0).As(type_a);
890 case Type::Bool: 1034 const std::string op_b = VisitOperand(operation, 1).As(type_b);
891 case Type::Bool2: 1035 std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
892 case Type::Float:
893 if (needs_parenthesis) {
894 return fmt::format("({})", value);
895 }
896 return value;
897 case Type::Int:
898 return fmt::format("itof({})", value);
899 case Type::Uint:
900 return fmt::format("utof({})", value);
901 case Type::HalfFloat:
902 return fmt::format("fromHalf2({})", value);
903 }
904 UNREACHABLE();
905 return value;
906 }
907
908 std::string GenerateUnary(Operation operation, const std::string& func, Type result_type,
909 Type type_a, bool needs_parenthesis = true) {
910 const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a));
911
912 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis));
913 }
914
915 std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type,
916 Type type_a, Type type_b) {
917 const std::string op_a = VisitOperand(operation, 0, type_a);
918 const std::string op_b = VisitOperand(operation, 1, type_b);
919 const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
920 1036
921 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1037 return ApplyPrecise(operation, std::move(op_str), result_type);
922 } 1038 }
923 1039
924 std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, 1040 Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type,
925 Type type_a, Type type_b) { 1041 Type type_a, Type type_b) {
926 const std::string op_a = VisitOperand(operation, 0, type_a); 1042 const std::string op_a = VisitOperand(operation, 0).As(type_a);
927 const std::string op_b = VisitOperand(operation, 1, type_b); 1043 const std::string op_b = VisitOperand(operation, 1).As(type_b);
928 const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); 1044 std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b);
929 1045
930 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1046 return ApplyPrecise(operation, std::move(op_str), result_type);
931 } 1047 }
932 1048
933 std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, 1049 Expression GenerateTernary(Operation operation, std::string_view func, Type result_type,
934 Type type_a, Type type_b, Type type_c) { 1050 Type type_a, Type type_b, Type type_c) {
935 const std::string op_a = VisitOperand(operation, 0, type_a); 1051 const std::string op_a = VisitOperand(operation, 0).As(type_a);
936 const std::string op_b = VisitOperand(operation, 1, type_b); 1052 const std::string op_b = VisitOperand(operation, 1).As(type_b);
937 const std::string op_c = VisitOperand(operation, 2, type_c); 1053 const std::string op_c = VisitOperand(operation, 2).As(type_c);
938 const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); 1054 std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c);
939 1055
940 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1056 return ApplyPrecise(operation, std::move(op_str), result_type);
941 } 1057 }
942 1058
943 std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, 1059 Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type,
944 Type type_a, Type type_b, Type type_c, Type type_d) { 1060 Type type_a, Type type_b, Type type_c, Type type_d) {
945 const std::string op_a = VisitOperand(operation, 0, type_a); 1061 const std::string op_a = VisitOperand(operation, 0).As(type_a);
946 const std::string op_b = VisitOperand(operation, 1, type_b); 1062 const std::string op_b = VisitOperand(operation, 1).As(type_b);
947 const std::string op_c = VisitOperand(operation, 2, type_c); 1063 const std::string op_c = VisitOperand(operation, 2).As(type_c);
948 const std::string op_d = VisitOperand(operation, 3, type_d); 1064 const std::string op_d = VisitOperand(operation, 3).As(type_d);
949 const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); 1065 std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d);
950 1066
951 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1067 return ApplyPrecise(operation, std::move(op_str), result_type);
952 } 1068 }
953 1069
954 std::string GenerateTexture(Operation operation, const std::string& function_suffix, 1070 std::string GenerateTexture(Operation operation, const std::string& function_suffix,
955 const std::vector<TextureIR>& extras) { 1071 const std::vector<TextureIR>& extras) {
956 constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; 1072 constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"};
957 1073
958 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1074 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
959 ASSERT(meta); 1075 ASSERT(meta);
@@ -970,17 +1086,17 @@ private:
970 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); 1086 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
971 expr += '('; 1087 expr += '(';
972 for (std::size_t i = 0; i < count; ++i) { 1088 for (std::size_t i = 0; i < count; ++i) {
973 expr += Visit(operation[i]); 1089 expr += Visit(operation[i]).AsFloat();
974 1090
975 const std::size_t next = i + 1; 1091 const std::size_t next = i + 1;
976 if (next < count) 1092 if (next < count)
977 expr += ", "; 1093 expr += ", ";
978 } 1094 }
979 if (has_array) { 1095 if (has_array) {
980 expr += ", float(ftoi(" + Visit(meta->array) + "))"; 1096 expr += ", float(" + Visit(meta->array).AsInt() + ')';
981 } 1097 }
982 if (has_shadow) { 1098 if (has_shadow) {
983 expr += ", " + Visit(meta->depth_compare); 1099 expr += ", " + Visit(meta->depth_compare).AsFloat();
984 } 1100 }
985 expr += ')'; 1101 expr += ')';
986 1102
@@ -1011,11 +1127,11 @@ private:
1011 // required to be constant) 1127 // required to be constant)
1012 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1128 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1013 } else { 1129 } else {
1014 expr += fmt::format("ftoi({})", Visit(operand)); 1130 expr += Visit(operand).AsInt();
1015 } 1131 }
1016 break; 1132 break;
1017 case Type::Float: 1133 case Type::Float:
1018 expr += Visit(operand); 1134 expr += Visit(operand).AsFloat();
1019 break; 1135 break;
1020 default: { 1136 default: {
1021 const auto type_int = static_cast<u32>(type); 1137 const auto type_int = static_cast<u32>(type);
@@ -1031,7 +1147,7 @@ private:
1031 if (aoffi.empty()) { 1147 if (aoffi.empty()) {
1032 return {}; 1148 return {};
1033 } 1149 }
1034 constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; 1150 constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"};
1035 std::string expr = ", "; 1151 std::string expr = ", ";
1036 expr += coord_constructors.at(aoffi.size() - 1); 1152 expr += coord_constructors.at(aoffi.size() - 1);
1037 expr += '('; 1153 expr += '(';
@@ -1044,7 +1160,7 @@ private:
1044 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1160 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1045 } else if (device.HasVariableAoffi()) { 1161 } else if (device.HasVariableAoffi()) {
1046 // Avoid using variable AOFFI on unsupported devices. 1162 // Avoid using variable AOFFI on unsupported devices.
1047 expr += fmt::format("ftoi({})", Visit(operand)); 1163 expr += Visit(operand).AsInt();
1048 } else { 1164 } else {
1049 // Insert 0 on devices not supporting variable AOFFI. 1165 // Insert 0 on devices not supporting variable AOFFI.
1050 expr += '0'; 1166 expr += '0';
@@ -1058,328 +1174,314 @@ private:
1058 return expr; 1174 return expr;
1059 } 1175 }
1060 1176
1061 std::string Assign(Operation operation) { 1177 Expression Assign(Operation operation) {
1062 const Node& dest = operation[0]; 1178 const Node& dest = operation[0];
1063 const Node& src = operation[1]; 1179 const Node& src = operation[1];
1064 1180
1065 std::string target; 1181 Expression target;
1066 bool is_integer = false;
1067
1068 if (const auto gpr = std::get_if<GprNode>(&*dest)) { 1182 if (const auto gpr = std::get_if<GprNode>(&*dest)) {
1069 if (gpr->GetIndex() == Register::ZeroIndex) { 1183 if (gpr->GetIndex() == Register::ZeroIndex) {
1070 // Writing to Register::ZeroIndex is a no op 1184 // Writing to Register::ZeroIndex is a no op
1071 return {}; 1185 return {};
1072 } 1186 }
1073 target = GetRegister(gpr->GetIndex()); 1187 target = {GetRegister(gpr->GetIndex()), Type::Float};
1074 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { 1188 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
1075 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); 1189 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
1076 const auto result = GetOutputAttribute(abuf); 1190 target = GetOutputAttribute(abuf);
1077 if (!result) {
1078 return {};
1079 }
1080 target = result->first;
1081 is_integer = result->second;
1082 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { 1191 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
1083 if (stage == ProgramType::Compute) { 1192 if (stage == ProgramType::Compute) {
1084 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 1193 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
1085 } 1194 }
1086 target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 1195 target = {
1196 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
1197 Type::Uint};
1087 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { 1198 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
1088 const std::string real = Visit(gmem->GetRealAddress()); 1199 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
1089 const std::string base = Visit(gmem->GetBaseAddress()); 1200 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
1090 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 1201 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
1091 target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 1202 target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
1203 Type::Uint};
1092 } else { 1204 } else {
1093 UNREACHABLE_MSG("Assign called without a proper target"); 1205 UNREACHABLE_MSG("Assign called without a proper target");
1094 } 1206 }
1095 1207
1096 if (is_integer) { 1208 code.AddLine("{} = {};", target.GetCode(), Visit(src).As(target.GetType()));
1097 code.AddLine("{} = ftoi({});", target, Visit(src));
1098 } else {
1099 code.AddLine("{} = {};", target, Visit(src));
1100 }
1101 return {}; 1209 return {};
1102 } 1210 }
1103 1211
1104 template <Type type> 1212 template <Type type>
1105 std::string Add(Operation operation) { 1213 Expression Add(Operation operation) {
1106 return GenerateBinaryInfix(operation, "+", type, type, type); 1214 return GenerateBinaryInfix(operation, "+", type, type, type);
1107 } 1215 }
1108 1216
1109 template <Type type> 1217 template <Type type>
1110 std::string Mul(Operation operation) { 1218 Expression Mul(Operation operation) {
1111 return GenerateBinaryInfix(operation, "*", type, type, type); 1219 return GenerateBinaryInfix(operation, "*", type, type, type);
1112 } 1220 }
1113 1221
1114 template <Type type> 1222 template <Type type>
1115 std::string Div(Operation operation) { 1223 Expression Div(Operation operation) {
1116 return GenerateBinaryInfix(operation, "/", type, type, type); 1224 return GenerateBinaryInfix(operation, "/", type, type, type);
1117 } 1225 }
1118 1226
1119 template <Type type> 1227 template <Type type>
1120 std::string Fma(Operation operation) { 1228 Expression Fma(Operation operation) {
1121 return GenerateTernary(operation, "fma", type, type, type, type); 1229 return GenerateTernary(operation, "fma", type, type, type, type);
1122 } 1230 }
1123 1231
1124 template <Type type> 1232 template <Type type>
1125 std::string Negate(Operation operation) { 1233 Expression Negate(Operation operation) {
1126 return GenerateUnary(operation, "-", type, type, true); 1234 return GenerateUnary(operation, "-", type, type);
1127 } 1235 }
1128 1236
1129 template <Type type> 1237 template <Type type>
1130 std::string Absolute(Operation operation) { 1238 Expression Absolute(Operation operation) {
1131 return GenerateUnary(operation, "abs", type, type, false); 1239 return GenerateUnary(operation, "abs", type, type);
1132 } 1240 }
1133 1241
1134 std::string FClamp(Operation operation) { 1242 Expression FClamp(Operation operation) {
1135 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, 1243 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float,
1136 Type::Float); 1244 Type::Float);
1137 } 1245 }
1138 1246
1139 std::string FCastHalf0(Operation operation) { 1247 Expression FCastHalf0(Operation operation) {
1140 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1248 return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1141 return fmt::format("({})[0]", op_a);
1142 } 1249 }
1143 1250
1144 std::string FCastHalf1(Operation operation) { 1251 Expression FCastHalf1(Operation operation) {
1145 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1252 return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1146 return fmt::format("({})[1]", op_a);
1147 } 1253 }
1148 1254
1149 template <Type type> 1255 template <Type type>
1150 std::string Min(Operation operation) { 1256 Expression Min(Operation operation) {
1151 return GenerateBinaryCall(operation, "min", type, type, type); 1257 return GenerateBinaryCall(operation, "min", type, type, type);
1152 } 1258 }
1153 1259
1154 template <Type type> 1260 template <Type type>
1155 std::string Max(Operation operation) { 1261 Expression Max(Operation operation) {
1156 return GenerateBinaryCall(operation, "max", type, type, type); 1262 return GenerateBinaryCall(operation, "max", type, type, type);
1157 } 1263 }
1158 1264
1159 std::string Select(Operation operation) { 1265 Expression Select(Operation operation) {
1160 const std::string condition = Visit(operation[0]); 1266 const std::string condition = Visit(operation[0]).AsBool();
1161 const std::string true_case = Visit(operation[1]); 1267 const std::string true_case = Visit(operation[1]).AsUint();
1162 const std::string false_case = Visit(operation[2]); 1268 const std::string false_case = Visit(operation[2]).AsUint();
1163 const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); 1269 std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case);
1164 1270
1165 return ApplyPrecise(operation, op_str); 1271 return ApplyPrecise(operation, std::move(op_str), Type::Uint);
1166 } 1272 }
1167 1273
1168 std::string FCos(Operation operation) { 1274 Expression FCos(Operation operation) {
1169 return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); 1275 return GenerateUnary(operation, "cos", Type::Float, Type::Float);
1170 } 1276 }
1171 1277
1172 std::string FSin(Operation operation) { 1278 Expression FSin(Operation operation) {
1173 return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); 1279 return GenerateUnary(operation, "sin", Type::Float, Type::Float);
1174 } 1280 }
1175 1281
1176 std::string FExp2(Operation operation) { 1282 Expression FExp2(Operation operation) {
1177 return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); 1283 return GenerateUnary(operation, "exp2", Type::Float, Type::Float);
1178 } 1284 }
1179 1285
1180 std::string FLog2(Operation operation) { 1286 Expression FLog2(Operation operation) {
1181 return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); 1287 return GenerateUnary(operation, "log2", Type::Float, Type::Float);
1182 } 1288 }
1183 1289
1184 std::string FInverseSqrt(Operation operation) { 1290 Expression FInverseSqrt(Operation operation) {
1185 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); 1291 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float);
1186 } 1292 }
1187 1293
1188 std::string FSqrt(Operation operation) { 1294 Expression FSqrt(Operation operation) {
1189 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); 1295 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float);
1190 } 1296 }
1191 1297
1192 std::string FRoundEven(Operation operation) { 1298 Expression FRoundEven(Operation operation) {
1193 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); 1299 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float);
1194 } 1300 }
1195 1301
1196 std::string FFloor(Operation operation) { 1302 Expression FFloor(Operation operation) {
1197 return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); 1303 return GenerateUnary(operation, "floor", Type::Float, Type::Float);
1198 } 1304 }
1199 1305
1200 std::string FCeil(Operation operation) { 1306 Expression FCeil(Operation operation) {
1201 return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); 1307 return GenerateUnary(operation, "ceil", Type::Float, Type::Float);
1202 } 1308 }
1203 1309
1204 std::string FTrunc(Operation operation) { 1310 Expression FTrunc(Operation operation) {
1205 return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); 1311 return GenerateUnary(operation, "trunc", Type::Float, Type::Float);
1206 } 1312 }
1207 1313
1208 template <Type type> 1314 template <Type type>
1209 std::string FCastInteger(Operation operation) { 1315 Expression FCastInteger(Operation operation) {
1210 return GenerateUnary(operation, "float", Type::Float, type, false); 1316 return GenerateUnary(operation, "float", Type::Float, type);
1211 } 1317 }
1212 1318
1213 std::string ICastFloat(Operation operation) { 1319 Expression ICastFloat(Operation operation) {
1214 return GenerateUnary(operation, "int", Type::Int, Type::Float, false); 1320 return GenerateUnary(operation, "int", Type::Int, Type::Float);
1215 } 1321 }
1216 1322
1217 std::string ICastUnsigned(Operation operation) { 1323 Expression ICastUnsigned(Operation operation) {
1218 return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); 1324 return GenerateUnary(operation, "int", Type::Int, Type::Uint);
1219 } 1325 }
1220 1326
1221 template <Type type> 1327 template <Type type>
1222 std::string LogicalShiftLeft(Operation operation) { 1328 Expression LogicalShiftLeft(Operation operation) {
1223 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); 1329 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint);
1224 } 1330 }
1225 1331
1226 std::string ILogicalShiftRight(Operation operation) { 1332 Expression ILogicalShiftRight(Operation operation) {
1227 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1333 const std::string op_a = VisitOperand(operation, 0).AsUint();
1228 const std::string op_b = VisitOperand(operation, 1, Type::Uint); 1334 const std::string op_b = VisitOperand(operation, 1).AsUint();
1229 const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); 1335 std::string op_str = fmt::format("int({} >> {})", op_a, op_b);
1230 1336
1231 return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); 1337 return ApplyPrecise(operation, std::move(op_str), Type::Int);
1232 } 1338 }
1233 1339
1234 std::string IArithmeticShiftRight(Operation operation) { 1340 Expression IArithmeticShiftRight(Operation operation) {
1235 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); 1341 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint);
1236 } 1342 }
1237 1343
1238 template <Type type> 1344 template <Type type>
1239 std::string BitwiseAnd(Operation operation) { 1345 Expression BitwiseAnd(Operation operation) {
1240 return GenerateBinaryInfix(operation, "&", type, type, type); 1346 return GenerateBinaryInfix(operation, "&", type, type, type);
1241 } 1347 }
1242 1348
1243 template <Type type> 1349 template <Type type>
1244 std::string BitwiseOr(Operation operation) { 1350 Expression BitwiseOr(Operation operation) {
1245 return GenerateBinaryInfix(operation, "|", type, type, type); 1351 return GenerateBinaryInfix(operation, "|", type, type, type);
1246 } 1352 }
1247 1353
1248 template <Type type> 1354 template <Type type>
1249 std::string BitwiseXor(Operation operation) { 1355 Expression BitwiseXor(Operation operation) {
1250 return GenerateBinaryInfix(operation, "^", type, type, type); 1356 return GenerateBinaryInfix(operation, "^", type, type, type);
1251 } 1357 }
1252 1358
1253 template <Type type> 1359 template <Type type>
1254 std::string BitwiseNot(Operation operation) { 1360 Expression BitwiseNot(Operation operation) {
1255 return GenerateUnary(operation, "~", type, type, false); 1361 return GenerateUnary(operation, "~", type, type);
1256 } 1362 }
1257 1363
1258 std::string UCastFloat(Operation operation) { 1364 Expression UCastFloat(Operation operation) {
1259 return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); 1365 return GenerateUnary(operation, "uint", Type::Uint, Type::Float);
1260 } 1366 }
1261 1367
1262 std::string UCastSigned(Operation operation) { 1368 Expression UCastSigned(Operation operation) {
1263 return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); 1369 return GenerateUnary(operation, "uint", Type::Uint, Type::Int);
1264 } 1370 }
1265 1371
1266 std::string UShiftRight(Operation operation) { 1372 Expression UShiftRight(Operation operation) {
1267 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); 1373 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint);
1268 } 1374 }
1269 1375
1270 template <Type type> 1376 template <Type type>
1271 std::string BitfieldInsert(Operation operation) { 1377 Expression BitfieldInsert(Operation operation) {
1272 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, 1378 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int,
1273 Type::Int); 1379 Type::Int);
1274 } 1380 }
1275 1381
1276 template <Type type> 1382 template <Type type>
1277 std::string BitfieldExtract(Operation operation) { 1383 Expression BitfieldExtract(Operation operation) {
1278 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); 1384 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int);
1279 } 1385 }
1280 1386
1281 template <Type type> 1387 template <Type type>
1282 std::string BitCount(Operation operation) { 1388 Expression BitCount(Operation operation) {
1283 return GenerateUnary(operation, "bitCount", type, type, false); 1389 return GenerateUnary(operation, "bitCount", type, type);
1284 } 1390 }
1285 1391
1286 std::string HNegate(Operation operation) { 1392 Expression HNegate(Operation operation) {
1287 const auto GetNegate = [&](std::size_t index) { 1393 const auto GetNegate = [&](std::size_t index) {
1288 return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; 1394 return VisitOperand(operation, index).AsBool() + " ? -1 : 1";
1289 }; 1395 };
1290 const std::string value = 1396 return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(),
1291 fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), 1397 GetNegate(1), GetNegate(2)),
1292 GetNegate(1), GetNegate(2)); 1398 Type::HalfFloat};
1293 return BitwiseCastResult(value, Type::HalfFloat); 1399 }
1294 } 1400
1295 1401 Expression HClamp(Operation operation) {
1296 std::string HClamp(Operation operation) { 1402 const std::string value = VisitOperand(operation, 0).AsHalfFloat();
1297 const std::string value = VisitOperand(operation, 0, Type::HalfFloat); 1403 const std::string min = VisitOperand(operation, 1).AsFloat();
1298 const std::string min = VisitOperand(operation, 1, Type::Float); 1404 const std::string max = VisitOperand(operation, 2).AsFloat();
1299 const std::string max = VisitOperand(operation, 2, Type::Float); 1405 std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max);
1300 const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); 1406
1301 1407 return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat);
1302 return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); 1408 }
1303 } 1409
1304 1410 Expression HCastFloat(Operation operation) {
1305 std::string HCastFloat(Operation operation) { 1411 return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat};
1306 const std::string op_a = VisitOperand(operation, 0, Type::Float); 1412 }
1307 return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a); 1413
1308 } 1414 Expression HUnpack(Operation operation) {
1309 1415 Expression operand = VisitOperand(operation, 0);
1310 std::string HUnpack(Operation operation) { 1416 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
1311 const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; 1417 case Tegra::Shader::HalfType::H0_H1:
1312 const auto value = [&]() -> std::string { 1418 return operand;
1313 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { 1419 case Tegra::Shader::HalfType::F32:
1314 case Tegra::Shader::HalfType::H0_H1: 1420 return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat};
1315 return operand; 1421 case Tegra::Shader::HalfType::H0_H0:
1316 case Tegra::Shader::HalfType::F32: 1422 return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat};
1317 return fmt::format("vec2(fromHalf2({}))", operand); 1423 case Tegra::Shader::HalfType::H1_H1:
1318 case Tegra::Shader::HalfType::H0_H0: 1424 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
1319 return fmt::format("vec2({}[0])", operand); 1425 }
1320 case Tegra::Shader::HalfType::H1_H1:
1321 return fmt::format("vec2({}[1])", operand);
1322 }
1323 UNREACHABLE();
1324 return "0";
1325 }();
1326 return fmt::format("fromHalf2({})", value);
1327 } 1426 }
1328 1427
1329 std::string HMergeF32(Operation operation) { 1428 Expression HMergeF32(Operation operation) {
1330 return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); 1429 return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1331 } 1430 }
1332 1431
1333 std::string HMergeH0(Operation operation) { 1432 Expression HMergeH0(Operation operation) {
1334 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), 1433 std::string dest = VisitOperand(operation, 0).AsUint();
1335 Visit(operation[0])); 1434 std::string src = VisitOperand(operation, 1).AsUint();
1435 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint};
1336 } 1436 }
1337 1437
1338 std::string HMergeH1(Operation operation) { 1438 Expression HMergeH1(Operation operation) {
1339 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), 1439 std::string dest = VisitOperand(operation, 0).AsUint();
1340 Visit(operation[1])); 1440 std::string src = VisitOperand(operation, 1).AsUint();
1441 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint};
1341 } 1442 }
1342 1443
1343 std::string HPack2(Operation operation) { 1444 Expression HPack2(Operation operation) {
1344 return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), 1445 return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(),
1345 Visit(operation[1])); 1446 VisitOperand(operation, 1).AsFloat()),
1447 Type::HalfFloat};
1346 } 1448 }
1347 1449
1348 template <Type type> 1450 template <Type type>
1349 std::string LogicalLessThan(Operation operation) { 1451 Expression LogicalLessThan(Operation operation) {
1350 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); 1452 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type);
1351 } 1453 }
1352 1454
1353 template <Type type> 1455 template <Type type>
1354 std::string LogicalEqual(Operation operation) { 1456 Expression LogicalEqual(Operation operation) {
1355 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); 1457 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type);
1356 } 1458 }
1357 1459
1358 template <Type type> 1460 template <Type type>
1359 std::string LogicalLessEqual(Operation operation) { 1461 Expression LogicalLessEqual(Operation operation) {
1360 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); 1462 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type);
1361 } 1463 }
1362 1464
1363 template <Type type> 1465 template <Type type>
1364 std::string LogicalGreaterThan(Operation operation) { 1466 Expression LogicalGreaterThan(Operation operation) {
1365 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); 1467 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type);
1366 } 1468 }
1367 1469
1368 template <Type type> 1470 template <Type type>
1369 std::string LogicalNotEqual(Operation operation) { 1471 Expression LogicalNotEqual(Operation operation) {
1370 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); 1472 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type);
1371 } 1473 }
1372 1474
1373 template <Type type> 1475 template <Type type>
1374 std::string LogicalGreaterEqual(Operation operation) { 1476 Expression LogicalGreaterEqual(Operation operation) {
1375 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); 1477 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type);
1376 } 1478 }
1377 1479
1378 std::string LogicalFIsNan(Operation operation) { 1480 Expression LogicalFIsNan(Operation operation) {
1379 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); 1481 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float);
1380 } 1482 }
1381 1483
1382 std::string LogicalAssign(Operation operation) { 1484 Expression LogicalAssign(Operation operation) {
1383 const Node& dest = operation[0]; 1485 const Node& dest = operation[0];
1384 const Node& src = operation[1]; 1486 const Node& src = operation[1];
1385 1487
@@ -1400,78 +1502,80 @@ private:
1400 target = GetInternalFlag(flag->GetFlag()); 1502 target = GetInternalFlag(flag->GetFlag());
1401 } 1503 }
1402 1504
1403 code.AddLine("{} = {};", target, Visit(src)); 1505 code.AddLine("{} = {};", target, Visit(src).AsBool());
1404 return {}; 1506 return {};
1405 } 1507 }
1406 1508
1407 std::string LogicalAnd(Operation operation) { 1509 Expression LogicalAnd(Operation operation) {
1408 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); 1510 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool);
1409 } 1511 }
1410 1512
1411 std::string LogicalOr(Operation operation) { 1513 Expression LogicalOr(Operation operation) {
1412 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); 1514 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool);
1413 } 1515 }
1414 1516
1415 std::string LogicalXor(Operation operation) { 1517 Expression LogicalXor(Operation operation) {
1416 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); 1518 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool);
1417 } 1519 }
1418 1520
1419 std::string LogicalNegate(Operation operation) { 1521 Expression LogicalNegate(Operation operation) {
1420 return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); 1522 return GenerateUnary(operation, "!", Type::Bool, Type::Bool);
1421 } 1523 }
1422 1524
1423 std::string LogicalPick2(Operation operation) { 1525 Expression LogicalPick2(Operation operation) {
1424 const std::string pair = VisitOperand(operation, 0, Type::Bool2); 1526 return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(),
1425 return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); 1527 VisitOperand(operation, 1).AsUint()),
1528 Type::Bool};
1426 } 1529 }
1427 1530
1428 std::string LogicalAnd2(Operation operation) { 1531 Expression LogicalAnd2(Operation operation) {
1429 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); 1532 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2);
1430 } 1533 }
1431 1534
1432 template <bool with_nan> 1535 template <bool with_nan>
1433 std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { 1536 Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) {
1434 const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, 1537 Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2,
1435 Type::HalfFloat, Type::HalfFloat)}; 1538 Type::HalfFloat, Type::HalfFloat);
1436 if constexpr (!with_nan) { 1539 if constexpr (!with_nan) {
1437 return comparison; 1540 return comparison;
1438 } 1541 }
1439 return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, 1542 return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(),
1440 VisitOperand(operation, 0, Type::HalfFloat), 1543 VisitOperand(operation, 0).AsHalfFloat(),
1441 VisitOperand(operation, 1, Type::HalfFloat)); 1544 VisitOperand(operation, 1).AsHalfFloat()),
1545 Type::Bool2};
1442 } 1546 }
1443 1547
1444 template <bool with_nan> 1548 template <bool with_nan>
1445 std::string Logical2HLessThan(Operation operation) { 1549 Expression Logical2HLessThan(Operation operation) {
1446 return GenerateHalfComparison<with_nan>(operation, "lessThan"); 1550 return GenerateHalfComparison<with_nan>(operation, "lessThan");
1447 } 1551 }
1448 1552
1449 template <bool with_nan> 1553 template <bool with_nan>
1450 std::string Logical2HEqual(Operation operation) { 1554 Expression Logical2HEqual(Operation operation) {
1451 return GenerateHalfComparison<with_nan>(operation, "equal"); 1555 return GenerateHalfComparison<with_nan>(operation, "equal");
1452 } 1556 }
1453 1557
1454 template <bool with_nan> 1558 template <bool with_nan>
1455 std::string Logical2HLessEqual(Operation operation) { 1559 Expression Logical2HLessEqual(Operation operation) {
1456 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); 1560 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual");
1457 } 1561 }
1458 1562
1459 template <bool with_nan> 1563 template <bool with_nan>
1460 std::string Logical2HGreaterThan(Operation operation) { 1564 Expression Logical2HGreaterThan(Operation operation) {
1461 return GenerateHalfComparison<with_nan>(operation, "greaterThan"); 1565 return GenerateHalfComparison<with_nan>(operation, "greaterThan");
1462 } 1566 }
1463 1567
1464 template <bool with_nan> 1568 template <bool with_nan>
1465 std::string Logical2HNotEqual(Operation operation) { 1569 Expression Logical2HNotEqual(Operation operation) {
1466 return GenerateHalfComparison<with_nan>(operation, "notEqual"); 1570 return GenerateHalfComparison<with_nan>(operation, "notEqual");
1467 } 1571 }
1468 1572
1469 template <bool with_nan> 1573 template <bool with_nan>
1470 std::string Logical2HGreaterEqual(Operation operation) { 1574 Expression Logical2HGreaterEqual(Operation operation) {
1471 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); 1575 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual");
1472 } 1576 }
1473 1577
1474 std::string Texture(Operation operation) { 1578 Expression Texture(Operation operation) {
1475 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1579 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1476 ASSERT(meta); 1580 ASSERT(meta);
1477 1581
@@ -1480,10 +1584,10 @@ private:
1480 if (meta->sampler.IsShadow()) { 1584 if (meta->sampler.IsShadow()) {
1481 expr = "vec4(" + expr + ')'; 1585 expr = "vec4(" + expr + ')';
1482 } 1586 }
1483 return expr + GetSwizzle(meta->element); 1587 return {expr + GetSwizzle(meta->element), Type::Float};
1484 } 1588 }
1485 1589
1486 std::string TextureLod(Operation operation) { 1590 Expression TextureLod(Operation operation) {
1487 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1591 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1488 ASSERT(meta); 1592 ASSERT(meta);
1489 1593
@@ -1492,54 +1596,54 @@ private:
1492 if (meta->sampler.IsShadow()) { 1596 if (meta->sampler.IsShadow()) {
1493 expr = "vec4(" + expr + ')'; 1597 expr = "vec4(" + expr + ')';
1494 } 1598 }
1495 return expr + GetSwizzle(meta->element); 1599 return {expr + GetSwizzle(meta->element), Type::Float};
1496 } 1600 }
1497 1601
1498 std::string TextureGather(Operation operation) { 1602 Expression TextureGather(Operation operation) {
1499 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1603 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1500 ASSERT(meta); 1604 ASSERT(meta);
1501 1605
1502 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; 1606 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
1503 return GenerateTexture(operation, "Gather", 1607 return {GenerateTexture(operation, "Gather",
1504 {TextureArgument{type, meta->component}, TextureAoffi{}}) + 1608 {TextureArgument{type, meta->component}, TextureAoffi{}}) +
1505 GetSwizzle(meta->element); 1609 GetSwizzle(meta->element),
1610 Type::Float};
1506 } 1611 }
1507 1612
1508 std::string TextureQueryDimensions(Operation operation) { 1613 Expression TextureQueryDimensions(Operation operation) {
1509 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1614 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1510 ASSERT(meta); 1615 ASSERT(meta);
1511 1616
1512 const std::string sampler = GetSampler(meta->sampler); 1617 const std::string sampler = GetSampler(meta->sampler);
1513 const std::string lod = VisitOperand(operation, 0, Type::Int); 1618 const std::string lod = VisitOperand(operation, 0).AsInt();
1514 1619
1515 switch (meta->element) { 1620 switch (meta->element) {
1516 case 0: 1621 case 0:
1517 case 1: 1622 case 1:
1518 return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, 1623 return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)),
1519 GetSwizzle(meta->element)); 1624 Type::Int};
1520 case 2:
1521 return "0";
1522 case 3: 1625 case 3:
1523 return fmt::format("itof(textureQueryLevels({}))", sampler); 1626 return {fmt::format("textureQueryLevels({})", sampler), Type::Int};
1524 } 1627 }
1525 UNREACHABLE(); 1628 UNREACHABLE();
1526 return "0"; 1629 return {"0", Type::Int};
1527 } 1630 }
1528 1631
1529 std::string TextureQueryLod(Operation operation) { 1632 Expression TextureQueryLod(Operation operation) {
1530 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1633 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1531 ASSERT(meta); 1634 ASSERT(meta);
1532 1635
1533 if (meta->element < 2) { 1636 if (meta->element < 2) {
1534 return fmt::format("itof(int(({} * vec2(256)){}))", 1637 return {fmt::format("int(({} * vec2(256)){})",
1535 GenerateTexture(operation, "QueryLod", {}), 1638 GenerateTexture(operation, "QueryLod", {}),
1536 GetSwizzle(meta->element)); 1639 GetSwizzle(meta->element)),
1640 Type::Int};
1537 } 1641 }
1538 return "0"; 1642 return {"0", Type::Int};
1539 } 1643 }
1540 1644
1541 std::string TexelFetch(Operation operation) { 1645 Expression TexelFetch(Operation operation) {
1542 constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; 1646 constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"};
1543 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1647 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1544 ASSERT(meta); 1648 ASSERT(meta);
1545 UNIMPLEMENTED_IF(meta->sampler.IsArray()); 1649 UNIMPLEMENTED_IF(meta->sampler.IsArray());
@@ -1552,7 +1656,7 @@ private:
1552 expr += constructors.at(operation.GetOperandsCount() - 1); 1656 expr += constructors.at(operation.GetOperandsCount() - 1);
1553 expr += '('; 1657 expr += '(';
1554 for (std::size_t i = 0; i < count; ++i) { 1658 for (std::size_t i = 0; i < count; ++i) {
1555 expr += VisitOperand(operation, i, Type::Int); 1659 expr += VisitOperand(operation, i).AsInt();
1556 const std::size_t next = i + 1; 1660 const std::size_t next = i + 1;
1557 if (next == count) 1661 if (next == count)
1558 expr += ')'; 1662 expr += ')';
@@ -1565,7 +1669,7 @@ private:
1565 1669
1566 if (meta->lod) { 1670 if (meta->lod) {
1567 expr += ", "; 1671 expr += ", ";
1568 expr += CastOperand(Visit(meta->lod), Type::Int); 1672 expr += Visit(meta->lod).AsInt();
1569 } 1673 }
1570 expr += ')'; 1674 expr += ')';
1571 expr += GetSwizzle(meta->element); 1675 expr += GetSwizzle(meta->element);
@@ -1580,11 +1684,11 @@ private:
1580 code.AddLine("float {} = {};", tmp, expr); 1684 code.AddLine("float {} = {};", tmp, expr);
1581 code.AddLine("#endif"); 1685 code.AddLine("#endif");
1582 1686
1583 return tmp; 1687 return {tmp, Type::Float};
1584 } 1688 }
1585 1689
1586 std::string ImageStore(Operation operation) { 1690 Expression ImageStore(Operation operation) {
1587 constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; 1691 constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1588 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1692 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1589 1693
1590 std::string expr = "imageStore("; 1694 std::string expr = "imageStore(";
@@ -1594,7 +1698,7 @@ private:
1594 const std::size_t coords_count{operation.GetOperandsCount()}; 1698 const std::size_t coords_count{operation.GetOperandsCount()};
1595 expr += constructors.at(coords_count - 1); 1699 expr += constructors.at(coords_count - 1);
1596 for (std::size_t i = 0; i < coords_count; ++i) { 1700 for (std::size_t i = 0; i < coords_count; ++i) {
1597 expr += VisitOperand(operation, i, Type::Int); 1701 expr += VisitOperand(operation, i).AsInt();
1598 if (i + 1 < coords_count) { 1702 if (i + 1 < coords_count) {
1599 expr += ", "; 1703 expr += ", ";
1600 } 1704 }
@@ -1605,7 +1709,7 @@ private:
1605 UNIMPLEMENTED_IF(values_count != 4); 1709 UNIMPLEMENTED_IF(values_count != 4);
1606 expr += "vec4("; 1710 expr += "vec4(";
1607 for (std::size_t i = 0; i < values_count; ++i) { 1711 for (std::size_t i = 0; i < values_count; ++i) {
1608 expr += Visit(meta.values.at(i)); 1712 expr += Visit(meta.values.at(i)).AsFloat();
1609 if (i + 1 < values_count) { 1713 if (i + 1 < values_count) {
1610 expr += ", "; 1714 expr += ", ";
1611 } 1715 }
@@ -1616,52 +1720,52 @@ private:
1616 return {}; 1720 return {};
1617 } 1721 }
1618 1722
1619 std::string Branch(Operation operation) { 1723 Expression Branch(Operation operation) {
1620 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1724 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1621 UNIMPLEMENTED_IF(!target); 1725 UNIMPLEMENTED_IF(!target);
1622 1726
1623 code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); 1727 code.AddLine("jmp_to = 0x{:X}U;", target->GetValue());
1624 code.AddLine("break;"); 1728 code.AddLine("break;");
1625 return {}; 1729 return {};
1626 } 1730 }
1627 1731
1628 std::string BranchIndirect(Operation operation) { 1732 Expression BranchIndirect(Operation operation) {
1629 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1733 const std::string op_a = VisitOperand(operation, 0).AsUint();
1630 1734
1631 code.AddLine("jmp_to = {};", op_a); 1735 code.AddLine("jmp_to = {};", op_a);
1632 code.AddLine("break;"); 1736 code.AddLine("break;");
1633 return {}; 1737 return {};
1634 } 1738 }
1635 1739
1636 std::string PushFlowStack(Operation operation) { 1740 Expression PushFlowStack(Operation operation) {
1637 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1741 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1638 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1742 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1639 UNIMPLEMENTED_IF(!target); 1743 UNIMPLEMENTED_IF(!target);
1640 1744
1641 code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), 1745 code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack),
1642 target->GetValue()); 1746 target->GetValue());
1643 return {}; 1747 return {};
1644 } 1748 }
1645 1749
1646 std::string PopFlowStack(Operation operation) { 1750 Expression PopFlowStack(Operation operation) {
1647 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1751 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1648 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); 1752 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack));
1649 code.AddLine("break;"); 1753 code.AddLine("break;");
1650 return {}; 1754 return {};
1651 } 1755 }
1652 1756
1653 std::string Exit(Operation operation) { 1757 Expression Exit(Operation operation) {
1654 if (stage != ProgramType::Fragment) { 1758 if (stage != ProgramType::Fragment) {
1655 code.AddLine("return;"); 1759 code.AddLine("return;");
1656 return {}; 1760 return {};
1657 } 1761 }
1658 const auto& used_registers = ir.GetRegisters(); 1762 const auto& used_registers = ir.GetRegisters();
1659 const auto SafeGetRegister = [&](u32 reg) -> std::string { 1763 const auto SafeGetRegister = [&](u32 reg) -> Expression {
1660 // TODO(Rodrigo): Replace with contains once C++20 releases 1764 // TODO(Rodrigo): Replace with contains once C++20 releases
1661 if (used_registers.find(reg) != used_registers.end()) { 1765 if (used_registers.find(reg) != used_registers.end()) {
1662 return GetRegister(reg); 1766 return {GetRegister(reg), Type::Float};
1663 } 1767 }
1664 return "0.0f"; 1768 return {"0.0f", Type::Float};
1665 }; 1769 };
1666 1770
1667 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); 1771 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented");
@@ -1674,7 +1778,7 @@ private:
1674 for (u32 component = 0; component < 4; ++component) { 1778 for (u32 component = 0; component < 4; ++component) {
1675 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { 1779 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
1676 code.AddLine("FragColor{}[{}] = {};", render_target, component, 1780 code.AddLine("FragColor{}[{}] = {};", render_target, component,
1677 SafeGetRegister(current_reg)); 1781 SafeGetRegister(current_reg).AsFloat());
1678 ++current_reg; 1782 ++current_reg;
1679 } 1783 }
1680 } 1784 }
@@ -1683,14 +1787,14 @@ private:
1683 if (header.ps.omap.depth) { 1787 if (header.ps.omap.depth) {
1684 // The depth output is always 2 registers after the last color output, and current_reg 1788 // The depth output is always 2 registers after the last color output, and current_reg
1685 // already contains one past the last color register. 1789 // already contains one past the last color register.
1686 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); 1790 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
1687 } 1791 }
1688 1792
1689 code.AddLine("return;"); 1793 code.AddLine("return;");
1690 return {}; 1794 return {};
1691 } 1795 }
1692 1796
1693 std::string Discard(Operation operation) { 1797 Expression Discard(Operation operation) {
1694 // Enclose "discard" in a conditional, so that GLSL compilation does not complain 1798 // Enclose "discard" in a conditional, so that GLSL compilation does not complain
1695 // about unexecuted instructions that may follow this. 1799 // about unexecuted instructions that may follow this.
1696 code.AddLine("if (true) {{"); 1800 code.AddLine("if (true) {{");
@@ -1701,7 +1805,7 @@ private:
1701 return {}; 1805 return {};
1702 } 1806 }
1703 1807
1704 std::string EmitVertex(Operation operation) { 1808 Expression EmitVertex(Operation operation) {
1705 ASSERT_MSG(stage == ProgramType::Geometry, 1809 ASSERT_MSG(stage == ProgramType::Geometry,
1706 "EmitVertex is expected to be used in a geometry shader."); 1810 "EmitVertex is expected to be used in a geometry shader.");
1707 1811
@@ -1712,7 +1816,7 @@ private:
1712 return {}; 1816 return {};
1713 } 1817 }
1714 1818
1715 std::string EndPrimitive(Operation operation) { 1819 Expression EndPrimitive(Operation operation) {
1716 ASSERT_MSG(stage == ProgramType::Geometry, 1820 ASSERT_MSG(stage == ProgramType::Geometry,
1717 "EndPrimitive is expected to be used in a geometry shader."); 1821 "EndPrimitive is expected to be used in a geometry shader.");
1718 1822
@@ -1720,59 +1824,59 @@ private:
1720 return {}; 1824 return {};
1721 } 1825 }
1722 1826
1723 std::string YNegate(Operation operation) { 1827 Expression YNegate(Operation operation) {
1724 // Config pack's third value is Y_NEGATE's state. 1828 // Config pack's third value is Y_NEGATE's state.
1725 return "uintBitsToFloat(config_pack[2])"; 1829 return {"config_pack[2]", Type::Uint};
1726 } 1830 }
1727 1831
1728 template <u32 element> 1832 template <u32 element>
1729 std::string LocalInvocationId(Operation) { 1833 Expression LocalInvocationId(Operation) {
1730 return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; 1834 return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint};
1731 } 1835 }
1732 1836
1733 template <u32 element> 1837 template <u32 element>
1734 std::string WorkGroupId(Operation) { 1838 Expression WorkGroupId(Operation) {
1735 return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; 1839 return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint};
1736 } 1840 }
1737 1841
1738 std::string BallotThread(Operation operation) { 1842 Expression BallotThread(Operation operation) {
1739 const std::string value = VisitOperand(operation, 0, Type::Bool); 1843 const std::string value = VisitOperand(operation, 0).AsBool();
1740 if (!device.HasWarpIntrinsics()) { 1844 if (!device.HasWarpIntrinsics()) {
1741 LOG_ERROR(Render_OpenGL, 1845 LOG_ERROR(Render_OpenGL,
1742 "Nvidia warp intrinsics are not available and its required by a shader"); 1846 "Nvidia warp intrinsics are not available and its required by a shader");
1743 // Stub on non-Nvidia devices by simulating all threads voting the same as the active 1847 // Stub on non-Nvidia devices by simulating all threads voting the same as the active
1744 // one. 1848 // one.
1745 return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); 1849 return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint};
1746 } 1850 }
1747 return fmt::format("utof(ballotThreadNV({}))", value); 1851 return {fmt::format("ballotThreadNV({})", value), Type::Uint};
1748 } 1852 }
1749 1853
1750 std::string Vote(Operation operation, const char* func) { 1854 Expression Vote(Operation operation, const char* func) {
1751 const std::string value = VisitOperand(operation, 0, Type::Bool); 1855 const std::string value = VisitOperand(operation, 0).AsBool();
1752 if (!device.HasWarpIntrinsics()) { 1856 if (!device.HasWarpIntrinsics()) {
1753 LOG_ERROR(Render_OpenGL, 1857 LOG_ERROR(Render_OpenGL,
1754 "Nvidia vote intrinsics are not available and its required by a shader"); 1858 "Nvidia vote intrinsics are not available and its required by a shader");
1755 // Stub with a warp size of one. 1859 // Stub with a warp size of one.
1756 return value; 1860 return {value, Type::Bool};
1757 } 1861 }
1758 return fmt::format("{}({})", func, value); 1862 return {fmt::format("{}({})", func, value), Type::Bool};
1759 } 1863 }
1760 1864
1761 std::string VoteAll(Operation operation) { 1865 Expression VoteAll(Operation operation) {
1762 return Vote(operation, "allThreadsNV"); 1866 return Vote(operation, "allThreadsNV");
1763 } 1867 }
1764 1868
1765 std::string VoteAny(Operation operation) { 1869 Expression VoteAny(Operation operation) {
1766 return Vote(operation, "anyThreadNV"); 1870 return Vote(operation, "anyThreadNV");
1767 } 1871 }
1768 1872
1769 std::string VoteEqual(Operation operation) { 1873 Expression VoteEqual(Operation operation) {
1770 if (!device.HasWarpIntrinsics()) { 1874 if (!device.HasWarpIntrinsics()) {
1771 LOG_ERROR(Render_OpenGL, 1875 LOG_ERROR(Render_OpenGL,
1772 "Nvidia vote intrinsics are not available and its required by a shader"); 1876 "Nvidia vote intrinsics are not available and its required by a shader");
1773 // We must return true here since a stub for a theoretical warp size of 1 will always 1877 // We must return true here since a stub for a theoretical warp size of 1 will always
1774 // return an equal result for all its votes. 1878 // return an equal result for all its votes.
1775 return "true"; 1879 return {"true", Type::Bool};
1776 } 1880 }
1777 return Vote(operation, "allThreadsEqualNV"); 1881 return Vote(operation, "allThreadsEqualNV");
1778 } 1882 }
@@ -1973,8 +2077,8 @@ private:
1973 } 2077 }
1974 2078
1975 std::string GetInternalFlag(InternalFlag flag) const { 2079 std::string GetInternalFlag(InternalFlag flag) const {
1976 constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", 2080 constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag",
1977 "carry_flag", "overflow_flag"}; 2081 "overflow_flag"};
1978 const auto index = static_cast<u32>(flag); 2082 const auto index = static_cast<u32>(flag);
1979 ASSERT(index < static_cast<u32>(InternalFlag::Amount)); 2083 ASSERT(index < static_cast<u32>(InternalFlag::Amount));
1980 2084
@@ -2022,24 +2126,16 @@ private:
2022 2126
2023std::string GetCommonDeclarations() { 2127std::string GetCommonDeclarations() {
2024 return fmt::format( 2128 return fmt::format(
2025 "#define MAX_CONSTBUFFER_ELEMENTS {}\n"
2026 "#define ftoi floatBitsToInt\n" 2129 "#define ftoi floatBitsToInt\n"
2027 "#define ftou floatBitsToUint\n" 2130 "#define ftou floatBitsToUint\n"
2028 "#define itof intBitsToFloat\n" 2131 "#define itof intBitsToFloat\n"
2029 "#define utof uintBitsToFloat\n\n" 2132 "#define utof uintBitsToFloat\n\n"
2030 "float fromHalf2(vec2 pair) {{\n" 2133 "bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
2031 " return utof(packHalf2x16(pair));\n"
2032 "}}\n\n"
2033 "vec2 toHalf2(float value) {{\n"
2034 " return unpackHalf2x16(ftou(value));\n"
2035 "}}\n\n"
2036 "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
2037 " bvec2 is_nan1 = isnan(pair1);\n" 2134 " bvec2 is_nan1 = isnan(pair1);\n"
2038 " bvec2 is_nan2 = isnan(pair2);\n" 2135 " bvec2 is_nan2 = isnan(pair2);\n"
2039 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " 2136 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
2040 "is_nan2.y);\n" 2137 "is_nan2.y);\n"
2041 "}}\n", 2138 "}}\n\n");
2042 MAX_CONSTBUFFER_ELEMENTS);
2043} 2139}
2044 2140
2045ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, 2141ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage,
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0456248ac..f594106bf 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -517,10 +517,37 @@ void Config::ReadPathValues() {
517 UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); 517 UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
518 UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); 518 UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
519 UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); 519 UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
520 UISettings::values.game_directory_path = 520 UISettings::values.game_dir_deprecated =
521 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); 521 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
522 UISettings::values.game_directory_deepscan = 522 UISettings::values.game_dir_deprecated_deepscan =
523 ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); 523 ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
524 const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
525 for (int i = 0; i < gamedirs_size; ++i) {
526 qt_config->setArrayIndex(i);
527 UISettings::GameDir game_dir;
528 game_dir.path = ReadSetting(QStringLiteral("path")).toString();
529 game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
530 game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
531 UISettings::values.game_dirs.append(game_dir);
532 }
533 qt_config->endArray();
534 // create NAND and SD card directories if empty, these are not removable through the UI,
535 // also carries over old game list settings if present
536 if (UISettings::values.game_dirs.isEmpty()) {
537 UISettings::GameDir game_dir;
538 game_dir.path = QStringLiteral("SDMC");
539 game_dir.expanded = true;
540 UISettings::values.game_dirs.append(game_dir);
541 game_dir.path = QStringLiteral("UserNAND");
542 UISettings::values.game_dirs.append(game_dir);
543 game_dir.path = QStringLiteral("SysNAND");
544 UISettings::values.game_dirs.append(game_dir);
545 if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
546 game_dir.path = UISettings::values.game_dir_deprecated;
547 game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
548 UISettings::values.game_dirs.append(game_dir);
549 }
550 }
524 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); 551 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
525 552
526 qt_config->endGroup(); 553 qt_config->endGroup();
@@ -899,10 +926,15 @@ void Config::SavePathValues() {
899 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); 926 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
900 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); 927 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
901 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); 928 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
902 WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, 929 qt_config->beginWriteArray(QStringLiteral("gamedirs"));
903 QStringLiteral(".")); 930 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
904 WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, 931 qt_config->setArrayIndex(i);
905 false); 932 const auto& game_dir = UISettings::values.game_dirs[i];
933 WriteSetting(QStringLiteral("path"), game_dir.path);
934 WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
935 WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
936 }
937 qt_config->endArray();
906 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); 938 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
907 939
908 qt_config->endGroup(); 940 qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index e636964e3..775e3f2ea 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -68,12 +68,14 @@ void ConfigureDialog::RetranslateUI() {
68 ui->tabWidget->setCurrentIndex(old_index); 68 ui->tabWidget->setCurrentIndex(old_index);
69} 69}
70 70
71Q_DECLARE_METATYPE(QList<QWidget*>);
72
71void ConfigureDialog::PopulateSelectionList() { 73void ConfigureDialog::PopulateSelectionList() {
72 const std::array<std::pair<QString, QStringList>, 4> items{ 74 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
73 {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, 75 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
74 {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, 76 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}},
75 {tr("Graphics"), {tr("Graphics")}}, 77 {tr("Graphics"), {ui->graphicsTab}},
76 {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, 78 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
77 }; 79 };
78 80
79 [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); 81 [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -81,7 +83,7 @@ void ConfigureDialog::PopulateSelectionList() {
81 ui->selectorList->clear(); 83 ui->selectorList->clear();
82 for (const auto& entry : items) { 84 for (const auto& entry : items) {
83 auto* const item = new QListWidgetItem(entry.first); 85 auto* const item = new QListWidgetItem(entry.first);
84 item->setData(Qt::UserRole, entry.second); 86 item->setData(Qt::UserRole, QVariant::fromValue(entry.second));
85 87
86 ui->selectorList->addItem(item); 88 ui->selectorList->addItem(item);
87 } 89 }
@@ -93,24 +95,26 @@ void ConfigureDialog::UpdateVisibleTabs() {
93 return; 95 return;
94 } 96 }
95 97
96 const std::map<QString, QWidget*> widgets = { 98 const std::map<QWidget*, QString> widgets = {
97 {tr("General"), ui->generalTab}, 99 {ui->generalTab, tr("General")},
98 {tr("System"), ui->systemTab}, 100 {ui->systemTab, tr("System")},
99 {tr("Profiles"), ui->profileManagerTab}, 101 {ui->profileManagerTab, tr("Profiles")},
100 {tr("Input"), ui->inputTab}, 102 {ui->inputTab, tr("Input")},
101 {tr("Hotkeys"), ui->hotkeysTab}, 103 {ui->hotkeysTab, tr("Hotkeys")},
102 {tr("Graphics"), ui->graphicsTab}, 104 {ui->graphicsTab, tr("Graphics")},
103 {tr("Audio"), ui->audioTab}, 105 {ui->audioTab, tr("Audio")},
104 {tr("Debug"), ui->debugTab}, 106 {ui->debugTab, tr("Debug")},
105 {tr("Web"), ui->webTab}, 107 {ui->webTab, tr("Web")},
106 {tr("Game List"), ui->gameListTab}, 108 {ui->gameListTab, tr("Game List")},
107 }; 109 };
108 110
109 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); 111 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
110 112
111 ui->tabWidget->clear(); 113 ui->tabWidget->clear();
112 const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); 114
113 for (const auto& tab : tabs) { 115 const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
114 ui->tabWidget->addTab(widgets.find(tab)->second, tab); 116
117 for (const auto tab : tabs) {
118 ui->tabWidget->addTab(tab, widgets.at(tab));
115 } 119 }
116} 120}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 75fcbfea3..10bcd650e 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -20,25 +20,29 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
20 20
21 SetConfiguration(); 21 SetConfiguration();
22 22
23 connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, 23 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
24 [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
25} 24}
26 25
27ConfigureGeneral::~ConfigureGeneral() = default; 26ConfigureGeneral::~ConfigureGeneral() = default;
28 27
29void ConfigureGeneral::SetConfiguration() { 28void ConfigureGeneral::SetConfiguration() {
30 ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 29 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 30 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
33 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 31 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
32
33 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
34 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
35 ui->frame_limit->setValue(Settings::values.frame_limit);
34} 36}
35 37
36void ConfigureGeneral::ApplyConfiguration() { 38void ConfigureGeneral::ApplyConfiguration() {
37 UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
38 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 39 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
39 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 40 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
40 UISettings::values.theme = 41 UISettings::values.theme =
41 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); 42 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
43
44 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
45 Settings::values.frame_limit = ui->frame_limit->value();
42} 46}
43 47
44void ConfigureGeneral::changeEvent(QEvent* event) { 48void ConfigureGeneral::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 184fdd329..0bb91d64b 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -25,11 +25,31 @@
25 <item> 25 <item>
26 <layout class="QVBoxLayout" name="GeneralVerticalLayout"> 26 <layout class="QVBoxLayout" name="GeneralVerticalLayout">
27 <item> 27 <item>
28 <widget class="QCheckBox" name="toggle_deepscan"> 28 <layout class="QHBoxLayout" name="horizontalLayout_2">
29 <property name="text"> 29 <item>
30 <string>Search sub-directories for games</string> 30 <widget class="QCheckBox" name="toggle_frame_limit">
31 </property> 31 <property name="text">
32 </widget> 32 <string>Limit Speed Percent</string>
33 </property>
34 </widget>
35 </item>
36 <item>
37 <widget class="QSpinBox" name="frame_limit">
38 <property name="suffix">
39 <string>%</string>
40 </property>
41 <property name="minimum">
42 <number>1</number>
43 </property>
44 <property name="maximum">
45 <number>9999</number>
46 </property>
47 <property name="value">
48 <number>100</number>
49 </property>
50 </widget>
51 </item>
52 </layout>
33 </item> 53 </item>
34 <item> 54 <item>
35 <widget class="QCheckBox" name="toggle_check_exit"> 55 <widget class="QCheckBox" name="toggle_check_exit">
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2b17b250c..2c9e322c9 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
55 55
56 SetConfiguration(); 56 SetConfiguration();
57 57
58 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
59 connect(ui->bg_button, &QPushButton::clicked, this, [this] { 58 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
60 const QColor new_bg_color = QColorDialog::getColor(bg_color); 59 const QColor new_bg_color = QColorDialog::getColor(bg_color);
61 if (!new_bg_color.isValid()) { 60 if (!new_bg_color.isValid()) {
@@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() {
72 71
73 ui->resolution_factor_combobox->setCurrentIndex( 72 ui->resolution_factor_combobox->setCurrentIndex(
74 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); 73 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
75 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
76 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
77 ui->frame_limit->setValue(Settings::values.frame_limit);
78 ui->use_disk_shader_cache->setEnabled(runtime_lock); 74 ui->use_disk_shader_cache->setEnabled(runtime_lock);
79 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); 75 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
80 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 76 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
@@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() {
89void ConfigureGraphics::ApplyConfiguration() { 85void ConfigureGraphics::ApplyConfiguration() {
90 Settings::values.resolution_factor = 86 Settings::values.resolution_factor =
91 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); 87 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
92 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
93 Settings::values.frame_limit = ui->frame_limit->value();
94 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); 88 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
95 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); 89 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
96 Settings::values.use_asynchronous_gpu_emulation = 90 Settings::values.use_asynchronous_gpu_emulation =
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 15ab18ecd..0309ee300 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -23,33 +23,6 @@
23 </property> 23 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_2"> 24 <layout class="QVBoxLayout" name="verticalLayout_2">
25 <item> 25 <item>
26 <layout class="QHBoxLayout" name="horizontalLayout_2">
27 <item>
28 <widget class="QCheckBox" name="toggle_frame_limit">
29 <property name="text">
30 <string>Limit Speed Percent</string>
31 </property>
32 </widget>
33 </item>
34 <item>
35 <widget class="QSpinBox" name="frame_limit">
36 <property name="suffix">
37 <string>%</string>
38 </property>
39 <property name="minimum">
40 <number>1</number>
41 </property>
42 <property name="maximum">
43 <number>9999</number>
44 </property>
45 <property name="value">
46 <number>100</number>
47 </property>
48 </widget>
49 </item>
50 </layout>
51 </item>
52 <item>
53 <widget class="QCheckBox" name="use_disk_shader_cache"> 26 <widget class="QCheckBox" name="use_disk_shader_cache">
54 <property name="text"> 27 <property name="text">
55 <string>Use disk shader cache</string> 28 <string>Use disk shader cache</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7b70f307c..a968cfb5d 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -301,13 +301,16 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
301 }); 301 });
302 } 302 }
303 connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { 303 connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
304 QMessageBox::information(this, tr("Information"), 304 if (QMessageBox::information(
305 tr("After pressing OK, first move your joystick horizontally, " 305 this, tr("Information"),
306 "and then vertically.")); 306 tr("After pressing OK, first move your joystick horizontally, "
307 HandleClick( 307 "and then vertically."),
308 analog_map_stick[analog_id], 308 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
309 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, 309 HandleClick(
310 InputCommon::Polling::DeviceType::Analog); 310 analog_map_stick[analog_id],
311 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
312 InputCommon::Polling::DeviceType::Analog);
313 }
311 }); 314 });
312 } 315 }
313 316
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d18b96519..d5fab2f1f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -34,7 +34,6 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
34 return QObject::eventFilter(obj, event); 34 return QObject::eventFilter(obj, event);
35 35
36 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); 36 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
37 int rowCount = gamelist->tree_view->model()->rowCount();
38 QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); 37 QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower();
39 38
40 // If the searchfield's text hasn't changed special function keys get checked 39 // If the searchfield's text hasn't changed special function keys get checked
@@ -56,19 +55,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
56 // If there is only one result launch this game 55 // If there is only one result launch this game
57 case Qt::Key_Return: 56 case Qt::Key_Return:
58 case Qt::Key_Enter: { 57 case Qt::Key_Enter: {
59 QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); 58 if (gamelist->search_field->visible == 1) {
60 QModelIndex root_index = item_model->invisibleRootItem()->index(); 59 QString file_path = gamelist->getLastFilterResultItem();
61 QStandardItem* child_file; 60
62 QString file_path;
63 int resultCount = 0;
64 for (int i = 0; i < rowCount; ++i) {
65 if (!gamelist->tree_view->isRowHidden(i, root_index)) {
66 ++resultCount;
67 child_file = gamelist->item_model->item(i, 0);
68 file_path = child_file->data(GameListItemPath::FullPathRole).toString();
69 }
70 }
71 if (resultCount == 1) {
72 // To avoid loading error dialog loops while confirming them using enter 61 // To avoid loading error dialog loops while confirming them using enter
73 // Also users usually want to run a different game after closing one 62 // Also users usually want to run a different game after closing one
74 gamelist->search_field->edit_filter->clear(); 63 gamelist->search_field->edit_filter->clear();
@@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
88} 77}
89 78
90void GameListSearchField::setFilterResult(int visible, int total) { 79void GameListSearchField::setFilterResult(int visible, int total) {
80 this->visible = visible;
81 this->total = total;
82
91 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); 83 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
92} 84}
93 85
86QString GameList::getLastFilterResultItem() const {
87 QStandardItem* folder;
88 QStandardItem* child;
89 QString file_path;
90 const int folder_count = item_model->rowCount();
91 for (int i = 0; i < folder_count; ++i) {
92 folder = item_model->item(i, 0);
93 const QModelIndex folder_index = folder->index();
94 const int children_count = folder->rowCount();
95 for (int j = 0; j < children_count; ++j) {
96 if (!tree_view->isRowHidden(j, folder_index)) {
97 child = folder->child(j, 0);
98 file_path = child->data(GameListItemPath::FullPathRole).toString();
99 }
100 }
101 }
102 return file_path;
103}
104
94void GameListSearchField::clear() { 105void GameListSearchField::clear() {
95 edit_filter->clear(); 106 edit_filter->clear();
96} 107}
@@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
147 [&haystack](const QString& s) { return haystack.contains(s); }); 158 [&haystack](const QString& s) { return haystack.contains(s); });
148} 159}
149 160
161// Syncs the expanded state of Game Directories with settings to persist across sessions
162void GameList::onItemExpanded(const QModelIndex& item) {
163 const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
164 if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
165 type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)
166 item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded =
167 tree_view->isExpanded(item);
168}
169
150// Event in order to filter the gamelist after editing the searchfield 170// Event in order to filter the gamelist after editing the searchfield
151void GameList::onTextChanged(const QString& new_text) { 171void GameList::onTextChanged(const QString& new_text) {
152 const int row_count = tree_view->model()->rowCount(); 172 const int folder_count = tree_view->model()->rowCount();
153 const QString edit_filter_text = new_text.toLower(); 173 QString edit_filter_text = new_text.toLower();
154 const QModelIndex root_index = item_model->invisibleRootItem()->index(); 174 QStandardItem* folder;
175 QStandardItem* child;
176 int children_total = 0;
177 QModelIndex root_index = item_model->invisibleRootItem()->index();
155 178
156 // If the searchfield is empty every item is visible 179 // If the searchfield is empty every item is visible
157 // Otherwise the filter gets applied 180 // Otherwise the filter gets applied
158 if (edit_filter_text.isEmpty()) { 181 if (edit_filter_text.isEmpty()) {
159 for (int i = 0; i < row_count; ++i) { 182 for (int i = 0; i < folder_count; ++i) {
160 tree_view->setRowHidden(i, root_index, false); 183 folder = item_model->item(i, 0);
184 const QModelIndex folder_index = folder->index();
185 const int children_count = folder->rowCount();
186 for (int j = 0; j < children_count; ++j) {
187 ++children_total;
188 tree_view->setRowHidden(j, folder_index, false);
189 }
161 } 190 }
162 search_field->setFilterResult(row_count, row_count); 191 search_field->setFilterResult(children_total, children_total);
163 } else { 192 } else {
164 int result_count = 0; 193 int result_count = 0;
165 for (int i = 0; i < row_count; ++i) { 194 for (int i = 0; i < folder_count; ++i) {
166 const QStandardItem* child_file = item_model->item(i, 0); 195 folder = item_model->item(i, 0);
167 const QString file_path = 196 const QModelIndex folder_index = folder->index();
168 child_file->data(GameListItemPath::FullPathRole).toString().toLower(); 197 const int children_count = folder->rowCount();
169 const QString file_title = 198 for (int j = 0; j < children_count; ++j) {
170 child_file->data(GameListItemPath::TitleRole).toString().toLower(); 199 ++children_total;
171 const QString file_program_id = 200 const QStandardItem* child = folder->child(j, 0);
172 child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); 201 const QString file_path =
173 202 child->data(GameListItemPath::FullPathRole).toString().toLower();
174 // Only items which filename in combination with its title contains all words 203 const QString file_title =
175 // that are in the searchfield will be visible in the gamelist 204 child->data(GameListItemPath::TitleRole).toString().toLower();
176 // The search is case insensitive because of toLower() 205 const QString file_program_id =
177 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent 206 child->data(GameListItemPath::ProgramIdRole).toString().toLower();
178 // multiple conversions of edit_filter_text for each game in the gamelist 207
179 const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + 208 // Only items which filename in combination with its title contains all words
180 QLatin1Char{' '} + file_title; 209 // that are in the searchfield will be visible in the gamelist
181 if (ContainsAllWords(file_name, edit_filter_text) || 210 // The search is case insensitive because of toLower()
182 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { 211 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
183 tree_view->setRowHidden(i, root_index, false); 212 // multiple conversions of edit_filter_text for each game in the gamelist
184 ++result_count; 213 const QString file_name =
185 } else { 214 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
186 tree_view->setRowHidden(i, root_index, true); 215 file_title;
216 if (ContainsAllWords(file_name, edit_filter_text) ||
217 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
218 tree_view->setRowHidden(j, folder_index, false);
219 ++result_count;
220 } else {
221 tree_view->setRowHidden(j, folder_index, true);
222 }
223 search_field->setFilterResult(result_count, children_total);
187 } 224 }
188 search_field->setFilterResult(result_count, row_count); 225 }
226 }
227}
228
229void GameList::onUpdateThemedIcons() {
230 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
231 QStandardItem* child = item_model->invisibleRootItem()->child(i);
232
233 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
234 switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
235 case GameListItemType::SdmcDir:
236 child->setData(
237 QIcon::fromTheme(QStringLiteral("sd_card"))
238 .pixmap(icon_size)
239 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
240 Qt::DecorationRole);
241 break;
242 case GameListItemType::UserNandDir:
243 child->setData(
244 QIcon::fromTheme(QStringLiteral("chip"))
245 .pixmap(icon_size)
246 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
247 Qt::DecorationRole);
248 break;
249 case GameListItemType::SysNandDir:
250 child->setData(
251 QIcon::fromTheme(QStringLiteral("chip"))
252 .pixmap(icon_size)
253 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
254 Qt::DecorationRole);
255 break;
256 case GameListItemType::CustomDir: {
257 const UISettings::GameDir* game_dir =
258 child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
259 const QString icon_name = QFileInfo::exists(game_dir->path)
260 ? QStringLiteral("folder")
261 : QStringLiteral("bad_folder");
262 child->setData(
263 QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
264 icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
265 Qt::DecorationRole);
266 break;
267 }
268 case GameListItemType::AddDir:
269 child->setData(
270 QIcon::fromTheme(QStringLiteral("plus"))
271 .pixmap(icon_size)
272 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
273 Qt::DecorationRole);
274 break;
189 } 275 }
190 } 276 }
191} 277}
@@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
214 tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); 300 tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
215 tree_view->setSortingEnabled(true); 301 tree_view->setSortingEnabled(true);
216 tree_view->setEditTriggers(QHeaderView::NoEditTriggers); 302 tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
217 tree_view->setUniformRowHeights(true);
218 tree_view->setContextMenuPolicy(Qt::CustomContextMenu); 303 tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
219 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); 304 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
220 305
@@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
230 item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); 315 item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type"));
231 item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); 316 item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size"));
232 } 317 }
318 item_model->setSortRole(GameListItemPath::TitleRole);
233 319
320 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons);
234 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); 321 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
235 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); 322 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
323 connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded);
324 connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded);
236 325
237 // We must register all custom types with the Qt Automoc system so that we are able to use it 326 // We must register all custom types with the Qt Automoc system so that we are able to use
238 // with signals/slots. In this case, QList falls under the umbrells of custom types. 327 // it with signals/slots. In this case, QList falls under the umbrells of custom types.
239 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); 328 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
240 329
241 layout->setContentsMargins(0, 0, 0, 0); 330 layout->setContentsMargins(0, 0, 0, 0);
@@ -263,38 +352,68 @@ void GameList::clearFilter() {
263 search_field->clear(); 352 search_field->clear();
264} 353}
265 354
266void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { 355void GameList::AddDirEntry(GameListDir* entry_items) {
267 item_model->invisibleRootItem()->appendRow(entry_items); 356 item_model->invisibleRootItem()->appendRow(entry_items);
357 tree_view->setExpanded(
358 entry_items->index(),
359 entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded);
268} 360}
269 361
270void GameList::ValidateEntry(const QModelIndex& item) { 362void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) {
271 // We don't care about the individual QStandardItem that was selected, but its row. 363 parent->appendRow(entry_items);
272 const int row = item_model->itemFromIndex(item)->row(); 364}
273 const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
274 const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString();
275
276 if (file_path.isEmpty())
277 return;
278
279 if (!QFileInfo::exists(file_path))
280 return;
281 365
282 const QFileInfo file_info{file_path}; 366void GameList::ValidateEntry(const QModelIndex& item) {
283 if (file_info.isDir()) { 367 const auto selected = item.sibling(item.row(), 0);
284 const QDir dir{file_path}; 368
285 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); 369 switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
286 if (matching_main.size() == 1) { 370 case GameListItemType::Game: {
287 emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); 371 const QString file_path = selected.data(GameListItemPath::FullPathRole).toString();
372 if (file_path.isEmpty())
373 return;
374 const QFileInfo file_info(file_path);
375 if (!file_info.exists())
376 return;
377
378 if (file_info.isDir()) {
379 const QDir dir{file_path};
380 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
381 if (matching_main.size() == 1) {
382 emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
383 }
384 return;
288 } 385 }
289 return; 386
387 // Users usually want to run a different game after closing one
388 search_field->clear();
389 emit GameChosen(file_path);
390 break;
290 } 391 }
392 case GameListItemType::AddDir:
393 emit AddDirectory();
394 break;
395 }
396}
291 397
292 // Users usually want to run a diffrent game after closing one 398bool GameList::isEmpty() const {
293 search_field->clear(); 399 for (int i = 0; i < item_model->rowCount(); i++) {
294 emit GameChosen(file_path); 400 const QStandardItem* child = item_model->invisibleRootItem()->child(i);
401 const auto type = static_cast<GameListItemType>(child->type());
402 if (!child->hasChildren() &&
403 (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
404 type == GameListItemType::SysNandDir)) {
405 item_model->invisibleRootItem()->removeRow(child->row());
406 i--;
407 };
408 }
409 return !item_model->invisibleRootItem()->hasChildren();
295} 410}
296 411
297void GameList::DonePopulating(QStringList watch_list) { 412void GameList::DonePopulating(QStringList watch_list) {
413 emit ShowList(!isEmpty());
414
415 item_model->invisibleRootItem()->appendRow(new GameListAddDir());
416
298 // Clear out the old directories to watch for changes and add the new ones 417 // Clear out the old directories to watch for changes and add the new ones
299 auto watch_dirs = watcher->directories(); 418 auto watch_dirs = watcher->directories();
300 if (!watch_dirs.isEmpty()) { 419 if (!watch_dirs.isEmpty()) {
@@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) {
311 QCoreApplication::processEvents(); 430 QCoreApplication::processEvents();
312 } 431 }
313 tree_view->setEnabled(true); 432 tree_view->setEnabled(true);
314 int rowCount = tree_view->model()->rowCount(); 433 const int folder_count = tree_view->model()->rowCount();
315 search_field->setFilterResult(rowCount, rowCount); 434 int children_total = 0;
316 if (rowCount > 0) { 435 for (int i = 0; i < folder_count; ++i) {
436 children_total += item_model->item(i, 0)->rowCount();
437 }
438 search_field->setFilterResult(children_total, children_total);
439 if (children_total > 0) {
317 search_field->setFocus(); 440 search_field->setFocus();
318 } 441 }
319} 442}
@@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
323 if (!item.isValid()) 446 if (!item.isValid())
324 return; 447 return;
325 448
326 int row = item_model->itemFromIndex(item)->row(); 449 const auto selected = item.sibling(item.row(), 0);
327 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
328 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
329 std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
330
331 QMenu context_menu; 450 QMenu context_menu;
451 switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
452 case GameListItemType::Game:
453 AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
454 selected.data(GameListItemPath::FullPathRole).toString().toStdString());
455 break;
456 case GameListItemType::CustomDir:
457 AddPermDirPopup(context_menu, selected);
458 AddCustomDirPopup(context_menu, selected);
459 break;
460 case GameListItemType::SdmcDir:
461 case GameListItemType::UserNandDir:
462 case GameListItemType::SysNandDir:
463 AddPermDirPopup(context_menu, selected);
464 break;
465 }
466 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
467}
468
469void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
332 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 470 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
333 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); 471 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
334 QAction* open_transferable_shader_cache = 472 QAction* open_transferable_shader_cache =
@@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
344 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 482 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
345 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); 483 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
346 484
347 connect(open_save_location, &QAction::triggered, 485 connect(open_save_location, &QAction::triggered, [this, program_id]() {
348 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); 486 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData);
349 connect(open_lfs_location, &QAction::triggered, 487 });
350 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); 488 connect(open_lfs_location, &QAction::triggered, [this, program_id]() {
489 emit OpenFolderRequested(program_id, GameListOpenTarget::ModData);
490 });
351 connect(open_transferable_shader_cache, &QAction::triggered, 491 connect(open_transferable_shader_cache, &QAction::triggered,
352 [&]() { emit OpenTransferableShaderCacheRequested(program_id); }); 492 [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
353 connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); 493 connect(dump_romfs, &QAction::triggered,
354 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); 494 [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
355 connect(navigate_to_gamedb_entry, &QAction::triggered, 495 connect(copy_tid, &QAction::triggered,
356 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); 496 [this, program_id]() { emit CopyTIDRequested(program_id); });
357 connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); 497 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
498 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
499 });
500 connect(properties, &QAction::triggered,
501 [this, path]() { emit OpenPerGameGeneralRequested(path); });
502};
503
504void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
505 UISettings::GameDir& game_dir =
506 *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
507
508 QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders"));
509 QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory"));
510
511 deep_scan->setCheckable(true);
512 deep_scan->setChecked(game_dir.deep_scan);
513
514 connect(deep_scan, &QAction::triggered, [this, &game_dir] {
515 game_dir.deep_scan = !game_dir.deep_scan;
516 PopulateAsync(UISettings::values.game_dirs);
517 });
518 connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] {
519 UISettings::values.game_dirs.removeOne(game_dir);
520 item_model->invisibleRootItem()->removeRow(selected.row());
521 });
522}
358 523
359 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); 524void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
525 UISettings::GameDir& game_dir =
526 *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
527
528 QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up"));
529 QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down "));
530 QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location"));
531
532 const int row = selected.row();
533
534 move_up->setEnabled(row > 0);
535 move_down->setEnabled(row < item_model->rowCount() - 2);
536
537 connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] {
538 // find the indices of the items in settings and swap them
539 std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
540 UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
541 *selected.sibling(row - 1, 0)
542 .data(GameListDir::GameDirRole)
543 .value<UISettings::GameDir*>())]);
544 // move the treeview items
545 QList<QStandardItem*> item = item_model->takeRow(row);
546 item_model->invisibleRootItem()->insertRow(row - 1, item);
547 tree_view->setExpanded(selected, game_dir.expanded);
548 });
549
550 connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] {
551 // find the indices of the items in settings and swap them
552 std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
553 UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
554 *selected.sibling(row + 1, 0)
555 .data(GameListDir::GameDirRole)
556 .value<UISettings::GameDir*>())]);
557 // move the treeview items
558 const QList<QStandardItem*> item = item_model->takeRow(row);
559 item_model->invisibleRootItem()->insertRow(row + 1, item);
560 tree_view->setExpanded(selected, game_dir.expanded);
561 });
562
563 connect(open_directory_location, &QAction::triggered,
564 [this, game_dir] { emit OpenDirectory(game_dir.path); });
360} 565}
361 566
362void GameList::LoadCompatibilityList() { 567void GameList::LoadCompatibilityList() {
@@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() {
403 } 608 }
404} 609}
405 610
406void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { 611void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
407 const QFileInfo dir_info{dir_path};
408 if (!dir_info.exists() || !dir_info.isDir()) {
409 LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
410 search_field->setFilterResult(0, 0);
411 return;
412 }
413
414 tree_view->setEnabled(false); 612 tree_view->setEnabled(false);
415 613
416 // Update the columns in case UISettings has changed 614 // Update the columns in case UISettings has changed
@@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
433 631
434 // Delete any rows that might already exist if we're repopulating 632 // Delete any rows that might already exist if we're repopulating
435 item_model->removeRows(0, item_model->rowCount()); 633 item_model->removeRows(0, item_model->rowCount());
634 search_field->clear();
436 635
437 emit ShouldCancelWorker(); 636 emit ShouldCancelWorker();
438 637
439 GameListWorker* worker = 638 GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list);
440 new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
441 639
442 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 640 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
641 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
642 Qt::QueuedConnection);
443 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, 643 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
444 Qt::QueuedConnection); 644 Qt::QueuedConnection);
445 // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel 645 // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
446 // without delay. 646 // cancel without delay.
447 connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, 647 connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
448 Qt::DirectConnection); 648 Qt::DirectConnection);
449 649
@@ -471,10 +671,40 @@ const QStringList GameList::supported_file_extensions = {
471 QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; 671 QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
472 672
473void GameList::RefreshGameDirectory() { 673void GameList::RefreshGameDirectory() {
474 if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { 674 if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
475 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); 675 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
476 search_field->clear(); 676 PopulateAsync(UISettings::values.game_dirs);
477 PopulateAsync(UISettings::values.game_directory_path,
478 UISettings::values.game_directory_deepscan);
479 } 677 }
480} 678}
679
680GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
681 connect(parent, &GMainWindow::UpdateThemedIcons, this,
682 &GameListPlaceholder::onUpdateThemedIcons);
683
684 layout = new QVBoxLayout;
685 image = new QLabel;
686 text = new QLabel;
687 layout->setAlignment(Qt::AlignCenter);
688 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
689
690 text->setText(tr("Double-click to add a new folder to the game list"));
691 QFont font = text->font();
692 font.setPointSize(20);
693 text->setFont(font);
694 text->setAlignment(Qt::AlignHCenter);
695 image->setAlignment(Qt::AlignHCenter);
696
697 layout->addWidget(image);
698 layout->addWidget(text);
699 setLayout(layout);
700}
701
702GameListPlaceholder::~GameListPlaceholder() = default;
703
704void GameListPlaceholder::onUpdateThemedIcons() {
705 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
706}
707
708void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
709 emit GameListPlaceholder::AddDirectory();
710}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index f8f8bd6c5..878d94413 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -8,6 +8,7 @@
8#include <QHBoxLayout> 8#include <QHBoxLayout>
9#include <QLabel> 9#include <QLabel>
10#include <QLineEdit> 10#include <QLineEdit>
11#include <QList>
11#include <QModelIndex> 12#include <QModelIndex>
12#include <QSettings> 13#include <QSettings>
13#include <QStandardItem> 14#include <QStandardItem>
@@ -16,13 +17,16 @@
16#include <QToolButton> 17#include <QToolButton>
17#include <QTreeView> 18#include <QTreeView>
18#include <QVBoxLayout> 19#include <QVBoxLayout>
20#include <QVector>
19#include <QWidget> 21#include <QWidget>
20 22
21#include "common/common_types.h" 23#include "common/common_types.h"
24#include "uisettings.h"
22#include "yuzu/compatibility_list.h" 25#include "yuzu/compatibility_list.h"
23 26
24class GameListWorker; 27class GameListWorker;
25class GameListSearchField; 28class GameListSearchField;
29class GameListDir;
26class GMainWindow; 30class GMainWindow;
27 31
28namespace FileSys { 32namespace FileSys {
@@ -52,12 +56,14 @@ public:
52 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); 56 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
53 ~GameList() override; 57 ~GameList() override;
54 58
59 QString getLastFilterResultItem() const;
55 void clearFilter(); 60 void clearFilter();
56 void setFilterFocus(); 61 void setFilterFocus();
57 void setFilterVisible(bool visibility); 62 void setFilterVisible(bool visibility);
63 bool isEmpty() const;
58 64
59 void LoadCompatibilityList(); 65 void LoadCompatibilityList();
60 void PopulateAsync(const QString& dir_path, bool deep_scan); 66 void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
61 67
62 void SaveInterfaceLayout(); 68 void SaveInterfaceLayout();
63 void LoadInterfaceLayout(); 69 void LoadInterfaceLayout();
@@ -74,19 +80,29 @@ signals:
74 void NavigateToGamedbEntryRequested(u64 program_id, 80 void NavigateToGamedbEntryRequested(u64 program_id,
75 const CompatibilityList& compatibility_list); 81 const CompatibilityList& compatibility_list);
76 void OpenPerGameGeneralRequested(const std::string& file); 82 void OpenPerGameGeneralRequested(const std::string& file);
83 void OpenDirectory(const QString& directory);
84 void AddDirectory();
85 void ShowList(bool show);
77 86
78private slots: 87private slots:
88 void onItemExpanded(const QModelIndex& item);
79 void onTextChanged(const QString& new_text); 89 void onTextChanged(const QString& new_text);
80 void onFilterCloseClicked(); 90 void onFilterCloseClicked();
91 void onUpdateThemedIcons();
81 92
82private: 93private:
83 void AddEntry(const QList<QStandardItem*>& entry_items); 94 void AddDirEntry(GameListDir* entry_items);
95 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
84 void ValidateEntry(const QModelIndex& item); 96 void ValidateEntry(const QModelIndex& item);
85 void DonePopulating(QStringList watch_list); 97 void DonePopulating(QStringList watch_list);
86 98
87 void PopupContextMenu(const QPoint& menu_location);
88 void RefreshGameDirectory(); 99 void RefreshGameDirectory();
89 100
101 void PopupContextMenu(const QPoint& menu_location);
102 void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path);
103 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
104 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
105
90 std::shared_ptr<FileSys::VfsFilesystem> vfs; 106 std::shared_ptr<FileSys::VfsFilesystem> vfs;
91 FileSys::ManualContentProvider* provider; 107 FileSys::ManualContentProvider* provider;
92 GameListSearchField* search_field; 108 GameListSearchField* search_field;
@@ -102,3 +118,24 @@ private:
102}; 118};
103 119
104Q_DECLARE_METATYPE(GameListOpenTarget); 120Q_DECLARE_METATYPE(GameListOpenTarget);
121
122class GameListPlaceholder : public QWidget {
123 Q_OBJECT
124public:
125 explicit GameListPlaceholder(GMainWindow* parent = nullptr);
126 ~GameListPlaceholder();
127
128signals:
129 void AddDirectory();
130
131private slots:
132 void onUpdateThemedIcons();
133
134protected:
135 void mouseDoubleClickEvent(QMouseEvent* event) override;
136
137private:
138 QVBoxLayout* layout = nullptr;
139 QLabel* image = nullptr;
140 QLabel* text = nullptr;
141};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index ece534dd6..a8d888fee 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -10,6 +10,7 @@
10#include <utility> 10#include <utility>
11 11
12#include <QCoreApplication> 12#include <QCoreApplication>
13#include <QFileInfo>
13#include <QImage> 14#include <QImage>
14#include <QObject> 15#include <QObject>
15#include <QStandardItem> 16#include <QStandardItem>
@@ -22,6 +23,17 @@
22#include "yuzu/uisettings.h" 23#include "yuzu/uisettings.h"
23#include "yuzu/util/util.h" 24#include "yuzu/util/util.h"
24 25
26enum class GameListItemType {
27 Game = QStandardItem::UserType + 1,
28 CustomDir = QStandardItem::UserType + 2,
29 SdmcDir = QStandardItem::UserType + 3,
30 UserNandDir = QStandardItem::UserType + 4,
31 SysNandDir = QStandardItem::UserType + 5,
32 AddDir = QStandardItem::UserType + 6
33};
34
35Q_DECLARE_METATYPE(GameListItemType);
36
25/** 37/**
26 * Gets the default icon (for games without valid title metadata) 38 * Gets the default icon (for games without valid title metadata)
27 * @param size The desired width and height of the default icon. 39 * @param size The desired width and height of the default icon.
@@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) {
36class GameListItem : public QStandardItem { 48class GameListItem : public QStandardItem {
37 49
38public: 50public:
51 // used to access type from item index
52 static const int TypeRole = Qt::UserRole + 1;
53 static const int SortRole = Qt::UserRole + 2;
39 GameListItem() = default; 54 GameListItem() = default;
40 explicit GameListItem(const QString& string) : QStandardItem(string) {} 55 GameListItem(const QString& string) : QStandardItem(string) {
56 setData(string, SortRole);
57 }
41}; 58};
42 59
43/** 60/**
@@ -48,14 +65,15 @@ public:
48 */ 65 */
49class GameListItemPath : public GameListItem { 66class GameListItemPath : public GameListItem {
50public: 67public:
51 static const int FullPathRole = Qt::UserRole + 1; 68 static const int TitleRole = SortRole;
52 static const int TitleRole = Qt::UserRole + 2; 69 static const int FullPathRole = SortRole + 1;
53 static const int ProgramIdRole = Qt::UserRole + 3; 70 static const int ProgramIdRole = SortRole + 2;
54 static const int FileTypeRole = Qt::UserRole + 4; 71 static const int FileTypeRole = SortRole + 3;
55 72
56 GameListItemPath() = default; 73 GameListItemPath() = default;
57 GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, 74 GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
58 const QString& game_name, const QString& game_type, u64 program_id) { 75 const QString& game_name, const QString& game_type, u64 program_id) {
76 setData(type(), TypeRole);
59 setData(game_path, FullPathRole); 77 setData(game_path, FullPathRole);
60 setData(game_name, TitleRole); 78 setData(game_name, TitleRole);
61 setData(qulonglong(program_id), ProgramIdRole); 79 setData(qulonglong(program_id), ProgramIdRole);
@@ -72,6 +90,10 @@ public:
72 setData(picture, Qt::DecorationRole); 90 setData(picture, Qt::DecorationRole);
73 } 91 }
74 92
93 int type() const override {
94 return static_cast<int>(GameListItemType::Game);
95 }
96
75 QVariant data(int role) const override { 97 QVariant data(int role) const override {
76 if (role == Qt::DisplayRole) { 98 if (role == Qt::DisplayRole) {
77 std::string filename; 99 std::string filename;
@@ -103,9 +125,11 @@ public:
103class GameListItemCompat : public GameListItem { 125class GameListItemCompat : public GameListItem {
104 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) 126 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
105public: 127public:
106 static const int CompatNumberRole = Qt::UserRole + 1; 128 static const int CompatNumberRole = SortRole;
107 GameListItemCompat() = default; 129 GameListItemCompat() = default;
108 explicit GameListItemCompat(const QString& compatibility) { 130 explicit GameListItemCompat(const QString& compatibility) {
131 setData(type(), TypeRole);
132
109 struct CompatStatus { 133 struct CompatStatus {
110 QString color; 134 QString color;
111 const char* text; 135 const char* text;
@@ -135,6 +159,10 @@ public:
135 setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); 159 setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
136 } 160 }
137 161
162 int type() const override {
163 return static_cast<int>(GameListItemType::Game);
164 }
165
138 bool operator<(const QStandardItem& other) const override { 166 bool operator<(const QStandardItem& other) const override {
139 return data(CompatNumberRole) < other.data(CompatNumberRole); 167 return data(CompatNumberRole) < other.data(CompatNumberRole);
140 } 168 }
@@ -146,12 +174,12 @@ public:
146 * human-readable string representation will be displayed to the user. 174 * human-readable string representation will be displayed to the user.
147 */ 175 */
148class GameListItemSize : public GameListItem { 176class GameListItemSize : public GameListItem {
149
150public: 177public:
151 static const int SizeRole = Qt::UserRole + 1; 178 static const int SizeRole = SortRole;
152 179
153 GameListItemSize() = default; 180 GameListItemSize() = default;
154 explicit GameListItemSize(const qulonglong size_bytes) { 181 explicit GameListItemSize(const qulonglong size_bytes) {
182 setData(type(), TypeRole);
155 setData(size_bytes, SizeRole); 183 setData(size_bytes, SizeRole);
156 } 184 }
157 185
@@ -167,6 +195,10 @@ public:
167 } 195 }
168 } 196 }
169 197
198 int type() const override {
199 return static_cast<int>(GameListItemType::Game);
200 }
201
170 /** 202 /**
171 * This operator is, in practice, only used by the TreeView sorting systems. 203 * This operator is, in practice, only used by the TreeView sorting systems.
172 * Override it so that it will correctly sort by numerical value instead of by string 204 * Override it so that it will correctly sort by numerical value instead of by string
@@ -177,6 +209,82 @@ public:
177 } 209 }
178}; 210};
179 211
212class GameListDir : public GameListItem {
213public:
214 static const int GameDirRole = Qt::UserRole + 2;
215
216 explicit GameListDir(UISettings::GameDir& directory,
217 GameListItemType dir_type = GameListItemType::CustomDir)
218 : dir_type{dir_type} {
219 setData(type(), TypeRole);
220
221 UISettings::GameDir* game_dir = &directory;
222 setData(QVariant::fromValue(game_dir), GameDirRole);
223
224 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
225 switch (dir_type) {
226 case GameListItemType::SdmcDir:
227 setData(
228 QIcon::fromTheme(QStringLiteral("sd_card"))
229 .pixmap(icon_size)
230 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
231 Qt::DecorationRole);
232 setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
233 break;
234 case GameListItemType::UserNandDir:
235 setData(
236 QIcon::fromTheme(QStringLiteral("chip"))
237 .pixmap(icon_size)
238 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
239 Qt::DecorationRole);
240 setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
241 break;
242 case GameListItemType::SysNandDir:
243 setData(
244 QIcon::fromTheme(QStringLiteral("chip"))
245 .pixmap(icon_size)
246 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
247 Qt::DecorationRole);
248 setData(QObject::tr("System Titles"), Qt::DisplayRole);
249 break;
250 case GameListItemType::CustomDir:
251 const QString icon_name = QFileInfo::exists(game_dir->path)
252 ? QStringLiteral("folder")
253 : QStringLiteral("bad_folder");
254 setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
255 icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
256 Qt::DecorationRole);
257 setData(game_dir->path, Qt::DisplayRole);
258 break;
259 };
260 };
261
262 int type() const override {
263 return static_cast<int>(dir_type);
264 }
265
266private:
267 GameListItemType dir_type;
268};
269
270class GameListAddDir : public GameListItem {
271public:
272 explicit GameListAddDir() {
273 setData(type(), TypeRole);
274
275 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
276 setData(QIcon::fromTheme(QStringLiteral("plus"))
277 .pixmap(icon_size)
278 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
279 Qt::DecorationRole);
280 setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
281 }
282
283 int type() const override {
284 return static_cast<int>(GameListItemType::AddDir);
285 }
286};
287
180class GameList; 288class GameList;
181class QHBoxLayout; 289class QHBoxLayout;
182class QTreeView; 290class QTreeView;
@@ -208,6 +316,9 @@ private:
208 // EventFilter in order to process systemkeys while editing the searchfield 316 // EventFilter in order to process systemkeys while editing the searchfield
209 bool eventFilter(QObject* obj, QEvent* event) override; 317 bool eventFilter(QObject* obj, QEvent* event) override;
210 }; 318 };
319 int visible;
320 int total;
321
211 QHBoxLayout* layout_filter = nullptr; 322 QHBoxLayout* layout_filter = nullptr;
212 QTreeView* tree_view = nullptr; 323 QTreeView* tree_view = nullptr;
213 QLabel* label_filter = nullptr; 324 QLabel* label_filter = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 77f358630..fd21a9761 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -223,21 +223,37 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
223} // Anonymous namespace 223} // Anonymous namespace
224 224
225GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, 225GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
226 FileSys::ManualContentProvider* provider, QString dir_path, 226 FileSys::ManualContentProvider* provider,
227 bool deep_scan, const CompatibilityList& compatibility_list) 227 QVector<UISettings::GameDir>& game_dirs,
228 : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan), 228 const CompatibilityList& compatibility_list)
229 : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs),
229 compatibility_list(compatibility_list) {} 230 compatibility_list(compatibility_list) {}
230 231
231GameListWorker::~GameListWorker() = default; 232GameListWorker::~GameListWorker() = default;
232 233
233void GameListWorker::AddTitlesToGameList() { 234void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
234 const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( 235 using namespace FileSys;
235 Core::System::GetInstance().GetContentProvider()); 236
236 const auto installed_games = cache.ListEntriesFilterOrigin( 237 const auto& cache =
237 std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); 238 dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider());
239
240 std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games;
241 installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
242 ContentRecordType::Program);
243
244 if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) {
245 installed_games = cache.ListEntriesFilterOrigin(
246 ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program);
247 } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) {
248 installed_games = cache.ListEntriesFilterOrigin(
249 ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program);
250 } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) {
251 installed_games = cache.ListEntriesFilterOrigin(
252 ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program);
253 }
238 254
239 for (const auto& [slot, game] : installed_games) { 255 for (const auto& [slot, game] : installed_games) {
240 if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) 256 if (slot == ContentProviderUnionSlot::FrontendManual)
241 continue; 257 continue;
242 258
243 const auto file = cache.GetEntryUnparsed(game.title_id, game.type); 259 const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
@@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() {
250 u64 program_id = 0; 266 u64 program_id = 0;
251 loader->ReadProgramId(program_id); 267 loader->ReadProgramId(program_id);
252 268
253 const FileSys::PatchManager patch{program_id}; 269 const PatchManager patch{program_id};
254 const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); 270 const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
255 if (control != nullptr) 271 if (control != nullptr)
256 GetMetadataFromControlNCA(patch, *control, icon, name); 272 GetMetadataFromControlNCA(patch, *control, icon, name);
257 273
258 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, 274 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
259 compatibility_list, patch)); 275 compatibility_list, patch),
276 parent_dir);
260 } 277 }
261} 278}
262 279
263void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, 280void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
264 unsigned int recursion) { 281 unsigned int recursion, GameListDir* parent_dir) {
265 const auto callback = [this, target, recursion](u64* num_entries_out, 282 const auto callback = [this, target, recursion,
266 const std::string& directory, 283 parent_dir](u64* num_entries_out, const std::string& directory,
267 const std::string& virtual_name) -> bool { 284 const std::string& virtual_name) -> bool {
268 if (stop_processing) { 285 if (stop_processing) {
269 // Breaks the callback loop. 286 // Breaks the callback loop.
270 return false; 287 return false;
@@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
317 const FileSys::PatchManager patch{program_id}; 334 const FileSys::PatchManager patch{program_id};
318 335
319 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, 336 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
320 compatibility_list, patch)); 337 compatibility_list, patch),
338 parent_dir);
321 } 339 }
322 } else if (is_dir && recursion > 0) { 340 } else if (is_dir && recursion > 0) {
323 watch_list.append(QString::fromStdString(physical_name)); 341 watch_list.append(QString::fromStdString(physical_name));
324 ScanFileSystem(target, physical_name, recursion - 1); 342 ScanFileSystem(target, physical_name, recursion - 1, parent_dir);
325 } 343 }
326 344
327 return true; 345 return true;
@@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
332 350
333void GameListWorker::run() { 351void GameListWorker::run() {
334 stop_processing = false; 352 stop_processing = false;
335 watch_list.append(dir_path); 353
336 provider->ClearAllEntries(); 354 for (UISettings::GameDir& game_dir : game_dirs) {
337 ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), 355 if (game_dir.path == QStringLiteral("SDMC")) {
338 deep_scan ? 256 : 0); 356 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
339 AddTitlesToGameList(); 357 emit DirEntryReady({game_list_dir});
340 ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0); 358 AddTitlesToGameList(game_list_dir);
359 } else if (game_dir.path == QStringLiteral("UserNAND")) {
360 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
361 emit DirEntryReady({game_list_dir});
362 AddTitlesToGameList(game_list_dir);
363 } else if (game_dir.path == QStringLiteral("SysNAND")) {
364 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
365 emit DirEntryReady({game_list_dir});
366 AddTitlesToGameList(game_list_dir);
367 } else {
368 watch_list.append(game_dir.path);
369 auto* const game_list_dir = new GameListDir(game_dir);
370 emit DirEntryReady({game_list_dir});
371 provider->ClearAllEntries();
372 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
373 game_list_dir);
374 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
375 game_dir.deep_scan ? 256 : 0, game_list_dir);
376 }
377 };
378
341 emit Finished(watch_list); 379 emit Finished(watch_list);
342} 380}
343 381
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 7c3074af9..6e52fca89 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -14,6 +14,7 @@
14#include <QObject> 14#include <QObject>
15#include <QRunnable> 15#include <QRunnable>
16#include <QString> 16#include <QString>
17#include <QVector>
17 18
18#include "common/common_types.h" 19#include "common/common_types.h"
19#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
@@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable {
33 Q_OBJECT 34 Q_OBJECT
34 35
35public: 36public:
36 GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, 37 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
37 FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan, 38 FileSys::ManualContentProvider* provider,
38 const CompatibilityList& compatibility_list); 39 QVector<UISettings::GameDir>& game_dirs,
40 const CompatibilityList& compatibility_list);
39 ~GameListWorker() override; 41 ~GameListWorker() override;
40 42
41 /// Starts the processing of directory tree information. 43 /// Starts the processing of directory tree information.
@@ -48,31 +50,33 @@ signals:
48 /** 50 /**
49 * The `EntryReady` signal is emitted once an entry has been prepared and is ready 51 * The `EntryReady` signal is emitted once an entry has been prepared and is ready
50 * to be added to the game list. 52 * to be added to the game list.
51 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. 53 * @param entry_items a list with `QStandardItem`s that make up the columns of the new
54 * entry.
52 */ 55 */
53 void EntryReady(QList<QStandardItem*> entry_items); 56 void DirEntryReady(GameListDir* entry_items);
57 void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
54 58
55 /** 59 /**
56 * After the worker has traversed the game directory looking for entries, this signal is emitted 60 * After the worker has traversed the game directory looking for entries, this signal is
57 * with a list of folders that should be watched for changes as well. 61 * emitted with a list of folders that should be watched for changes as well.
58 */ 62 */
59 void Finished(QStringList watch_list); 63 void Finished(QStringList watch_list);
60 64
61private: 65private:
62 void AddTitlesToGameList(); 66 void AddTitlesToGameList(GameListDir* parent_dir);
63 67
64 enum class ScanTarget { 68 enum class ScanTarget {
65 FillManualContentProvider, 69 FillManualContentProvider,
66 PopulateGameList, 70 PopulateGameList,
67 }; 71 };
68 72
69 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); 73 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion,
74 GameListDir* parent_dir);
70 75
71 std::shared_ptr<FileSys::VfsFilesystem> vfs; 76 std::shared_ptr<FileSys::VfsFilesystem> vfs;
72 FileSys::ManualContentProvider* provider; 77 FileSys::ManualContentProvider* provider;
73 QStringList watch_list; 78 QStringList watch_list;
74 QString dir_path;
75 bool deep_scan;
76 const CompatibilityList& compatibility_list; 79 const CompatibilityList& compatibility_list;
80 QVector<UISettings::GameDir>& game_dirs;
77 std::atomic_bool stop_processing; 81 std::atomic_bool stop_processing;
78}; 82};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ac57229d5..6d249cb3e 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -216,8 +216,7 @@ GMainWindow::GMainWindow()
216 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 216 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
217 217
218 game_list->LoadCompatibilityList(); 218 game_list->LoadCompatibilityList();
219 game_list->PopulateAsync(UISettings::values.game_directory_path, 219 game_list->PopulateAsync(UISettings::values.game_dirs);
220 UISettings::values.game_directory_deepscan);
221 220
222 // Show one-time "callout" messages to the user 221 // Show one-time "callout" messages to the user
223 ShowTelemetryCallout(); 222 ShowTelemetryCallout();
@@ -427,6 +426,10 @@ void GMainWindow::InitializeWidgets() {
427 game_list = new GameList(vfs, provider.get(), this); 426 game_list = new GameList(vfs, provider.get(), this);
428 ui.horizontalLayout->addWidget(game_list); 427 ui.horizontalLayout->addWidget(game_list);
429 428
429 game_list_placeholder = new GameListPlaceholder(this);
430 ui.horizontalLayout->addWidget(game_list_placeholder);
431 game_list_placeholder->setVisible(false);
432
430 loading_screen = new LoadingScreen(this); 433 loading_screen = new LoadingScreen(this);
431 loading_screen->hide(); 434 loading_screen->hide();
432 ui.horizontalLayout->addWidget(loading_screen); 435 ui.horizontalLayout->addWidget(loading_screen);
@@ -660,6 +663,7 @@ void GMainWindow::RestoreUIState() {
660 663
661void GMainWindow::ConnectWidgetEvents() { 664void GMainWindow::ConnectWidgetEvents() {
662 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 665 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
666 connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
663 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); 667 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
664 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, 668 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
665 &GMainWindow::OnTransferableShaderCacheOpenFile); 669 &GMainWindow::OnTransferableShaderCacheOpenFile);
@@ -667,6 +671,11 @@ void GMainWindow::ConnectWidgetEvents() {
667 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 671 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
668 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 672 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
669 &GMainWindow::OnGameListNavigateToGamedbEntry); 673 &GMainWindow::OnGameListNavigateToGamedbEntry);
674 connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
675 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
676 &GMainWindow::OnGameListAddDirectory);
677 connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
678
670 connect(game_list, &GameList::OpenPerGameGeneralRequested, this, 679 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
671 &GMainWindow::OnGameListOpenPerGameProperties); 680 &GMainWindow::OnGameListOpenPerGameProperties);
672 681
@@ -684,8 +693,6 @@ void GMainWindow::ConnectMenuEvents() {
684 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); 693 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
685 connect(ui.action_Install_File_NAND, &QAction::triggered, this, 694 connect(ui.action_Install_File_NAND, &QAction::triggered, this,
686 &GMainWindow::OnMenuInstallToNAND); 695 &GMainWindow::OnMenuInstallToNAND);
687 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
688 &GMainWindow::OnMenuSelectGameListRoot);
689 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, 696 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
690 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); 697 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
691 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, 698 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
@@ -950,6 +957,7 @@ void GMainWindow::BootGame(const QString& filename) {
950 // Update the GUI 957 // Update the GUI
951 if (ui.action_Single_Window_Mode->isChecked()) { 958 if (ui.action_Single_Window_Mode->isChecked()) {
952 game_list->hide(); 959 game_list->hide();
960 game_list_placeholder->hide();
953 } 961 }
954 status_bar_update_timer.start(2000); 962 status_bar_update_timer.start(2000);
955 963
@@ -1007,7 +1015,10 @@ void GMainWindow::ShutdownGame() {
1007 render_window->hide(); 1015 render_window->hide();
1008 loading_screen->hide(); 1016 loading_screen->hide();
1009 loading_screen->Clear(); 1017 loading_screen->Clear();
1010 game_list->show(); 1018 if (game_list->isEmpty())
1019 game_list_placeholder->show();
1020 else
1021 game_list->show();
1011 game_list->setFilterFocus(); 1022 game_list->setFilterFocus();
1012 1023
1013 UpdateWindowTitle(); 1024 UpdateWindowTitle();
@@ -1298,6 +1309,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1298 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); 1309 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
1299} 1310}
1300 1311
1312void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1313 QString path;
1314 if (directory == QStringLiteral("SDMC")) {
1315 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
1316 "Nintendo/Contents/registered");
1317 } else if (directory == QStringLiteral("UserNAND")) {
1318 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1319 "user/Contents/registered");
1320 } else if (directory == QStringLiteral("SysNAND")) {
1321 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1322 "system/Contents/registered");
1323 } else {
1324 path = directory;
1325 }
1326 if (!QFileInfo::exists(path)) {
1327 QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!"));
1328 return;
1329 }
1330 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1331}
1332
1333void GMainWindow::OnGameListAddDirectory() {
1334 const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1335 if (dir_path.isEmpty())
1336 return;
1337 UISettings::GameDir game_dir{dir_path, false, true};
1338 if (!UISettings::values.game_dirs.contains(game_dir)) {
1339 UISettings::values.game_dirs.append(game_dir);
1340 game_list->PopulateAsync(UISettings::values.game_dirs);
1341 } else {
1342 LOG_WARNING(Frontend, "Selected directory is already in the game list");
1343 }
1344}
1345
1346void GMainWindow::OnGameListShowList(bool show) {
1347 if (emulation_running && ui.action_Single_Window_Mode->isChecked())
1348 return;
1349 game_list->setVisible(show);
1350 game_list_placeholder->setVisible(!show);
1351};
1352
1301void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { 1353void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1302 u64 title_id{}; 1354 u64 title_id{};
1303 const auto v_file = Core::GetGameFileFromPath(vfs, file); 1355 const auto v_file = Core::GetGameFileFromPath(vfs, file);
@@ -1316,8 +1368,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1316 1368
1317 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1369 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1318 if (reload) { 1370 if (reload) {
1319 game_list->PopulateAsync(UISettings::values.game_directory_path, 1371 game_list->PopulateAsync(UISettings::values.game_dirs);
1320 UISettings::values.game_directory_deepscan);
1321 } 1372 }
1322 1373
1323 config->Save(); 1374 config->Save();
@@ -1407,8 +1458,7 @@ void GMainWindow::OnMenuInstallToNAND() {
1407 const auto success = [this]() { 1458 const auto success = [this]() {
1408 QMessageBox::information(this, tr("Successfully Installed"), 1459 QMessageBox::information(this, tr("Successfully Installed"),
1409 tr("The file was successfully installed.")); 1460 tr("The file was successfully installed."));
1410 game_list->PopulateAsync(UISettings::values.game_directory_path, 1461 game_list->PopulateAsync(UISettings::values.game_dirs);
1411 UISettings::values.game_directory_deepscan);
1412 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + 1462 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
1413 DIR_SEP + "game_list"); 1463 DIR_SEP + "game_list");
1414 }; 1464 };
@@ -1533,14 +1583,6 @@ void GMainWindow::OnMenuInstallToNAND() {
1533 } 1583 }
1534} 1584}
1535 1585
1536void GMainWindow::OnMenuSelectGameListRoot() {
1537 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1538 if (!dir_path.isEmpty()) {
1539 UISettings::values.game_directory_path = dir_path;
1540 game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
1541 }
1542}
1543
1544void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { 1586void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
1545 const auto res = QMessageBox::information( 1587 const auto res = QMessageBox::information(
1546 this, tr("Changing Emulated Directory"), 1588 this, tr("Changing Emulated Directory"),
@@ -1559,8 +1601,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
1559 : FileUtil::UserPath::NANDDir, 1601 : FileUtil::UserPath::NANDDir,
1560 dir_path.toStdString()); 1602 dir_path.toStdString());
1561 Service::FileSystem::CreateFactories(*vfs); 1603 Service::FileSystem::CreateFactories(*vfs);
1562 game_list->PopulateAsync(UISettings::values.game_directory_path, 1604 game_list->PopulateAsync(UISettings::values.game_dirs);
1563 UISettings::values.game_directory_deepscan);
1564 } 1605 }
1565} 1606}
1566 1607
@@ -1724,11 +1765,11 @@ void GMainWindow::OnConfigure() {
1724 if (UISettings::values.enable_discord_presence != old_discord_presence) { 1765 if (UISettings::values.enable_discord_presence != old_discord_presence) {
1725 SetDiscordEnabled(UISettings::values.enable_discord_presence); 1766 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1726 } 1767 }
1768 emit UpdateThemedIcons();
1727 1769
1728 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1770 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1729 if (reload) { 1771 if (reload) {
1730 game_list->PopulateAsync(UISettings::values.game_directory_path, 1772 game_list->PopulateAsync(UISettings::values.game_dirs);
1731 UISettings::values.game_directory_deepscan);
1732 } 1773 }
1733 1774
1734 config->Save(); 1775 config->Save();
@@ -1992,8 +2033,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1992 Service::FileSystem::CreateFactories(*vfs); 2033 Service::FileSystem::CreateFactories(*vfs);
1993 2034
1994 if (behavior == ReinitializeKeyBehavior::Warning) { 2035 if (behavior == ReinitializeKeyBehavior::Warning) {
1995 game_list->PopulateAsync(UISettings::values.game_directory_path, 2036 game_list->PopulateAsync(UISettings::values.game_dirs);
1996 UISettings::values.game_directory_deepscan);
1997 } 2037 }
1998} 2038}
1999 2039
@@ -2158,7 +2198,6 @@ void GMainWindow::UpdateUITheme() {
2158 } 2198 }
2159 2199
2160 QIcon::setThemeSearchPaths(theme_paths); 2200 QIcon::setThemeSearchPaths(theme_paths);
2161 emit UpdateThemedIcons();
2162} 2201}
2163 2202
2164void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { 2203void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 501608ddc..7d16188cb 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -30,6 +30,7 @@ class ProfilerWidget;
30class QLabel; 30class QLabel;
31class WaitTreeWidget; 31class WaitTreeWidget;
32enum class GameListOpenTarget; 32enum class GameListOpenTarget;
33class GameListPlaceholder;
33 34
34namespace Core::Frontend { 35namespace Core::Frontend {
35struct SoftwareKeyboardParameters; 36struct SoftwareKeyboardParameters;
@@ -186,12 +187,13 @@ private slots:
186 void OnGameListCopyTID(u64 program_id); 187 void OnGameListCopyTID(u64 program_id);
187 void OnGameListNavigateToGamedbEntry(u64 program_id, 188 void OnGameListNavigateToGamedbEntry(u64 program_id,
188 const CompatibilityList& compatibility_list); 189 const CompatibilityList& compatibility_list);
190 void OnGameListOpenDirectory(const QString& directory);
191 void OnGameListAddDirectory();
192 void OnGameListShowList(bool show);
189 void OnGameListOpenPerGameProperties(const std::string& file); 193 void OnGameListOpenPerGameProperties(const std::string& file);
190 void OnMenuLoadFile(); 194 void OnMenuLoadFile();
191 void OnMenuLoadFolder(); 195 void OnMenuLoadFolder();
192 void OnMenuInstallToNAND(); 196 void OnMenuInstallToNAND();
193 /// Called whenever a user selects the "File->Select Game List Root" menu item
194 void OnMenuSelectGameListRoot();
195 /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card 197 /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card
196 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); 198 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
197 void OnMenuRecentFile(); 199 void OnMenuRecentFile();
@@ -223,6 +225,8 @@ private:
223 GameList* game_list; 225 GameList* game_list;
224 LoadingScreen* loading_screen; 226 LoadingScreen* loading_screen;
225 227
228 GameListPlaceholder* game_list_placeholder;
229
226 // Status bar elements 230 // Status bar elements
227 QLabel* message_label = nullptr; 231 QLabel* message_label = nullptr;
228 QLabel* emu_speed_label = nullptr; 232 QLabel* emu_speed_label = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index ffcabb495..a1ce3c0c3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -62,7 +62,6 @@
62 <addaction name="action_Load_File"/> 62 <addaction name="action_Load_File"/>
63 <addaction name="action_Load_Folder"/> 63 <addaction name="action_Load_Folder"/>
64 <addaction name="separator"/> 64 <addaction name="separator"/>
65 <addaction name="action_Select_Game_List_Root"/>
66 <addaction name="menu_recent_files"/> 65 <addaction name="menu_recent_files"/>
67 <addaction name="separator"/> 66 <addaction name="separator"/>
68 <addaction name="action_Select_NAND_Directory"/> 67 <addaction name="action_Select_NAND_Directory"/>
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index a62cd6911..c57290006 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -8,8 +8,10 @@
8#include <atomic> 8#include <atomic>
9#include <vector> 9#include <vector>
10#include <QByteArray> 10#include <QByteArray>
11#include <QMetaType>
11#include <QString> 12#include <QString>
12#include <QStringList> 13#include <QStringList>
14#include <QVector>
13#include "common/common_types.h" 15#include "common/common_types.h"
14 16
15namespace UISettings { 17namespace UISettings {
@@ -25,6 +27,18 @@ struct Shortcut {
25using Themes = std::array<std::pair<const char*, const char*>, 2>; 27using Themes = std::array<std::pair<const char*, const char*>, 2>;
26extern const Themes themes; 28extern const Themes themes;
27 29
30struct GameDir {
31 QString path;
32 bool deep_scan;
33 bool expanded;
34 bool operator==(const GameDir& rhs) const {
35 return path == rhs.path;
36 };
37 bool operator!=(const GameDir& rhs) const {
38 return !operator==(rhs);
39 };
40};
41
28struct Values { 42struct Values {
29 QByteArray geometry; 43 QByteArray geometry;
30 QByteArray state; 44 QByteArray state;
@@ -55,8 +69,9 @@ struct Values {
55 QString roms_path; 69 QString roms_path;
56 QString symbols_path; 70 QString symbols_path;
57 QString screenshot_path; 71 QString screenshot_path;
58 QString game_directory_path; 72 QString game_dir_deprecated;
59 bool game_directory_deepscan; 73 bool game_dir_deprecated_deepscan;
74 QVector<UISettings::GameDir> game_dirs;
60 QStringList recent_files; 75 QStringList recent_files;
61 76
62 QString theme; 77 QString theme;
@@ -84,3 +99,5 @@ struct Values {
84 99
85extern Values values; 100extern Values values;
86} // namespace UISettings 101} // namespace UISettings
102
103Q_DECLARE_METATYPE(UISettings::GameDir*);