summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
35 files changed, 1989 insertions, 743 deletions
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*);