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/core.cpp3
-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/kernel/vm_manager.cpp72
-rw-r--r--src/core/hle/kernel/vm_manager.h8
-rw-r--r--src/core/hle/service/am/am.cpp63
-rw-r--r--src/core/hle/service/am/am.h31
-rw-r--r--src/core/hle/service/am/applet_ae.cpp14
-rw-r--r--src/core/hle/service/am/applet_oe.cpp9
-rw-r--r--src/core/hle/service/am/applets/applets.cpp25
-rw-r--r--src/core/hle/service/am/applets/applets.h17
-rw-r--r--src/core/hle/service/am/applets/error.cpp7
-rw-r--r--src/core/hle/service/am/applets/error.h7
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp13
-rw-r--r--src/core/hle/service/am/applets/general_backend.h12
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp5
-rw-r--r--src/core/hle/service/am/applets/profile_select.h7
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp5
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h7
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp19
-rw-r--r--src/core/hle/service/am/applets/web_browser.h12
-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/hle/service/mii/mii_manager.cpp4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp2
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/video_core/engines/maxwell_3d.cpp12
-rw-r--r--src/video_core/engines/maxwell_3d.h2
-rw-r--r--src/video_core/engines/shader_bytecode.h9
-rw-r--r--src/video_core/gpu.cpp12
-rw-r--r--src/video_core/gpu.h9
-rw-r--r--src/video_core/gpu_asynch.cpp5
-rw-r--r--src/video_core/gpu_asynch.h5
-rw-r--r--src/video_core/gpu_synch.cpp5
-rw-r--r--src/video_core/gpu_synch.h5
-rw-r--r--src/video_core/gpu_thread.cpp8
-rw-r--r--src/video_core/gpu_thread.h3
-rw-r--r--src/video_core/morton.cpp116
-rw-r--r--src/video_core/morton.h3
-rw-r--r--src/video_core/renderer_base.h3
-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_rasterizer.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp68
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp930
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp89
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h5
-rw-r--r--src/video_core/shader/decode/conversion.cpp22
-rw-r--r--src/video_core/shader/decode/float_set_predicate.cpp9
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp19
-rw-r--r--src/video_core/surface.cpp5
-rw-r--r--src/yuzu/configuration/config.cpp45
-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.cpp117
-rw-r--r--src/yuzu/main.h11
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h21
77 files changed, 2344 insertions, 1205 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/core.cpp b/src/core/core.cpp
index 20d64f3b0..3d0978cbf 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -104,7 +104,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
104 return vfs->OpenFile(path, FileSys::Mode::Read); 104 return vfs->OpenFile(path, FileSys::Mode::Read);
105} 105}
106struct System::Impl { 106struct System::Impl {
107 explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {} 107 explicit Impl(System& system)
108 : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {}
108 109
109 Cpu& CurrentCpuCore() { 110 Cpu& CurrentCpuCore() {
110 return cpu_core_manager.GetCurrentCore(); 111 return cpu_core_manager.GetCurrentCore();
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/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 40cea1e7c..c7af87073 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -296,12 +296,6 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
296} 296}
297 297
298ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { 298ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
299 const auto end_addr = target + size;
300 const auto last_addr = end_addr - 1;
301 VAddr cur_addr = target;
302
303 ResultCode result = RESULT_SUCCESS;
304
305 // Check how much memory we've already mapped. 299 // Check how much memory we've already mapped.
306 const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); 300 const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size);
307 if (mapped_size_result.Failed()) { 301 if (mapped_size_result.Failed()) {
@@ -324,13 +318,16 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
324 318
325 // Keep track of the memory regions we unmap. 319 // Keep track of the memory regions we unmap.
326 std::vector<std::pair<u64, u64>> mapped_regions; 320 std::vector<std::pair<u64, u64>> mapped_regions;
321 ResultCode result = RESULT_SUCCESS;
327 322
328 // Iterate, trying to map memory. 323 // Iterate, trying to map memory.
329 { 324 {
330 cur_addr = target; 325 const auto end_addr = target + size;
326 const auto last_addr = end_addr - 1;
327 VAddr cur_addr = target;
331 328
332 auto iter = FindVMA(target); 329 auto iter = FindVMA(target);
333 ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); 330 ASSERT(iter != vma_map.end());
334 331
335 while (true) { 332 while (true) {
336 const auto& vma = iter->second; 333 const auto& vma = iter->second;
@@ -342,7 +339,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
342 const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); 339 const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
343 if (vma.state == MemoryState::Unmapped) { 340 if (vma.state == MemoryState::Unmapped) {
344 const auto map_res = 341 const auto map_res =
345 MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size, 0), 0, 342 MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0,
346 map_size, MemoryState::Heap, VMAPermission::ReadWrite); 343 map_size, MemoryState::Heap, VMAPermission::ReadWrite);
347 result = map_res.Code(); 344 result = map_res.Code();
348 if (result.IsError()) { 345 if (result.IsError()) {
@@ -360,7 +357,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
360 // Advance to the next block. 357 // Advance to the next block.
361 cur_addr = vma_end; 358 cur_addr = vma_end;
362 iter = FindVMA(cur_addr); 359 iter = FindVMA(cur_addr);
363 ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); 360 ASSERT(iter != vma_map.end());
364 } 361 }
365 } 362 }
366 363
@@ -368,7 +365,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
368 if (result.IsError()) { 365 if (result.IsError()) {
369 for (const auto [unmap_address, unmap_size] : mapped_regions) { 366 for (const auto [unmap_address, unmap_size] : mapped_regions) {
370 ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), 367 ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(),
371 "MapPhysicalMemory un-map on error"); 368 "Failed to unmap memory range.");
372 } 369 }
373 370
374 return result; 371 return result;
@@ -381,12 +378,6 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
381} 378}
382 379
383ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { 380ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
384 const auto end_addr = target + size;
385 const auto last_addr = end_addr - 1;
386 VAddr cur_addr = target;
387
388 ResultCode result = RESULT_SUCCESS;
389
390 // Check how much memory is currently mapped. 381 // Check how much memory is currently mapped.
391 const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); 382 const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size);
392 if (mapped_size_result.Failed()) { 383 if (mapped_size_result.Failed()) {
@@ -401,13 +392,16 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
401 392
402 // Keep track of the memory regions we unmap. 393 // Keep track of the memory regions we unmap.
403 std::vector<std::pair<u64, u64>> unmapped_regions; 394 std::vector<std::pair<u64, u64>> unmapped_regions;
395 ResultCode result = RESULT_SUCCESS;
404 396
405 // Try to unmap regions. 397 // Try to unmap regions.
406 { 398 {
407 cur_addr = target; 399 const auto end_addr = target + size;
400 const auto last_addr = end_addr - 1;
401 VAddr cur_addr = target;
408 402
409 auto iter = FindVMA(target); 403 auto iter = FindVMA(target);
410 ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); 404 ASSERT(iter != vma_map.end());
411 405
412 while (true) { 406 while (true) {
413 const auto& vma = iter->second; 407 const auto& vma = iter->second;
@@ -434,7 +428,7 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
434 // Advance to the next block. 428 // Advance to the next block.
435 cur_addr = vma_end; 429 cur_addr = vma_end;
436 iter = FindVMA(cur_addr); 430 iter = FindVMA(cur_addr);
437 ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); 431 ASSERT(iter != vma_map.end());
438 } 432 }
439 } 433 }
440 434
@@ -443,10 +437,12 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
443 if (result.IsError()) { 437 if (result.IsError()) {
444 for (const auto [map_address, map_size] : unmapped_regions) { 438 for (const auto [map_address, map_size] : unmapped_regions) {
445 const auto remap_res = 439 const auto remap_res =
446 MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size, 0), 0, 440 MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size,
447 map_size, MemoryState::Heap, VMAPermission::None); 441 MemoryState::Heap, VMAPermission::None);
448 ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error"); 442 ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block.");
449 } 443 }
444
445 return result;
450 } 446 }
451 447
452 // Update mapped amount 448 // Update mapped amount
@@ -757,20 +753,26 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre
757 // Always merge allocated memory blocks, even when they don't share the same backing block. 753 // Always merge allocated memory blocks, even when they don't share the same backing block.
758 if (left.type == VMAType::AllocatedMemoryBlock && 754 if (left.type == VMAType::AllocatedMemoryBlock &&
759 (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { 755 (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
756 const auto right_begin = right.backing_block->begin() + right.offset;
757 const auto right_end = right_begin + right.size;
758
760 // Check if we can save work. 759 // Check if we can save work.
761 if (left.offset == 0 && left.size == left.backing_block->size()) { 760 if (left.offset == 0 && left.size == left.backing_block->size()) {
762 // Fast case: left is an entire backing block. 761 // Fast case: left is an entire backing block.
763 left.backing_block->insert(left.backing_block->end(), 762 left.backing_block->insert(left.backing_block->end(), right_begin, right_end);
764 right.backing_block->begin() + right.offset,
765 right.backing_block->begin() + right.offset + right.size);
766 } else { 763 } else {
767 // Slow case: make a new memory block for left and right. 764 // Slow case: make a new memory block for left and right.
765 const auto left_begin = left.backing_block->begin() + left.offset;
766 const auto left_end = left_begin + left.size;
767 const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end));
768 const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end));
769
768 auto new_memory = std::make_shared<PhysicalMemory>(); 770 auto new_memory = std::make_shared<PhysicalMemory>();
769 new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset, 771 new_memory->reserve(left_size + right_size);
770 left.backing_block->begin() + left.offset + left.size); 772 new_memory->insert(new_memory->end(), left_begin, left_end);
771 new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset, 773 new_memory->insert(new_memory->end(), right_begin, right_end);
772 right.backing_block->begin() + right.offset + right.size); 774
773 left.backing_block = new_memory; 775 left.backing_block = std::move(new_memory);
774 left.offset = 0; 776 left.offset = 0;
775 } 777 }
776 778
@@ -965,7 +967,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
965 967
966 VAddr cur_addr = address; 968 VAddr cur_addr = address;
967 auto iter = FindVMA(cur_addr); 969 auto iter = FindVMA(cur_addr);
968 ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); 970 ASSERT(iter != vma_map.end());
969 971
970 while (true) { 972 while (true) {
971 const auto& vma = iter->second; 973 const auto& vma = iter->second;
@@ -986,7 +988,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
986 // Advance to the next block. 988 // Advance to the next block.
987 cur_addr = vma_end; 989 cur_addr = vma_end;
988 iter = std::next(iter); 990 iter = std::next(iter);
989 ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); 991 ASSERT(iter != vma_map.end());
990 } 992 }
991 993
992 return MakeResult(mapped_size); 994 return MakeResult(mapped_size);
@@ -1000,7 +1002,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad
1000 1002
1001 VAddr cur_addr = address; 1003 VAddr cur_addr = address;
1002 auto iter = FindVMA(cur_addr); 1004 auto iter = FindVMA(cur_addr);
1003 ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); 1005 ASSERT(iter != vma_map.end());
1004 1006
1005 while (true) { 1007 while (true) {
1006 const auto& vma = iter->second; 1008 const auto& vma = iter->second;
@@ -1029,7 +1031,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad
1029 // Advance to the next block. 1031 // Advance to the next block.
1030 cur_addr = vma_end; 1032 cur_addr = vma_end;
1031 iter = std::next(iter); 1033 iter = std::next(iter);
1032 ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); 1034 ASSERT(iter != vma_map.end());
1033 } 1035 }
1034 1036
1035 return MakeResult(mapped_size); 1037 return MakeResult(mapped_size);
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index b18cde619..850a7ebc3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -454,8 +454,8 @@ public:
454 454
455 /// Maps memory at a given address. 455 /// Maps memory at a given address.
456 /// 456 ///
457 /// @param addr The virtual address to map memory at. 457 /// @param target The virtual address to map memory at.
458 /// @param size The amount of memory to map. 458 /// @param size The amount of memory to map.
459 /// 459 ///
460 /// @note The destination address must lie within the Map region. 460 /// @note The destination address must lie within the Map region.
461 /// 461 ///
@@ -468,8 +468,8 @@ public:
468 468
469 /// Unmaps memory at a given address. 469 /// Unmaps memory at a given address.
470 /// 470 ///
471 /// @param addr The virtual address to unmap memory at. 471 /// @param target The virtual address to unmap memory at.
472 /// @param size The amount of memory to unmap. 472 /// @param size The amount of memory to unmap.
473 /// 473 ///
474 /// @note The destination address must lie within the Map region. 474 /// @note The destination address must lie within the Map region.
475 /// 475 ///
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index a192a1f5f..aa2c83937 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -56,7 +56,8 @@ struct LaunchParameters {
56}; 56};
57static_assert(sizeof(LaunchParameters) == 0x88); 57static_assert(sizeof(LaunchParameters) == 0x88);
58 58
59IWindowController::IWindowController() : ServiceFramework("IWindowController") { 59IWindowController::IWindowController(Core::System& system_)
60 : ServiceFramework("IWindowController"), system{system_} {
60 // clang-format off 61 // clang-format off
61 static const FunctionInfo functions[] = { 62 static const FunctionInfo functions[] = {
62 {0, nullptr, "CreateWindow"}, 63 {0, nullptr, "CreateWindow"},
@@ -75,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
75IWindowController::~IWindowController() = default; 76IWindowController::~IWindowController() = default;
76 77
77void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { 78void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
78 const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); 79 const u64 process_id = system.CurrentProcess()->GetProcessID();
79 80
80 LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); 81 LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id);
81 82
@@ -231,8 +232,9 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
231 232
232IDebugFunctions::~IDebugFunctions() = default; 233IDebugFunctions::~IDebugFunctions() = default;
233 234
234ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) 235ISelfController::ISelfController(Core::System& system_,
235 : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { 236 std::shared_ptr<NVFlinger::NVFlinger> nvflinger_)
237 : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) {
236 // clang-format off 238 // clang-format off
237 static const FunctionInfo functions[] = { 239 static const FunctionInfo functions[] = {
238 {0, nullptr, "Exit"}, 240 {0, nullptr, "Exit"},
@@ -280,7 +282,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
280 282
281 RegisterHandlers(functions); 283 RegisterHandlers(functions);
282 284
283 auto& kernel = Core::System::GetInstance().Kernel(); 285 auto& kernel = system_.Kernel();
284 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, 286 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
285 "ISelfController:LaunchableEvent"); 287 "ISelfController:LaunchableEvent");
286 288
@@ -501,8 +503,7 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest
501 rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); 503 rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable);
502} 504}
503 505
504AppletMessageQueue::AppletMessageQueue() { 506AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) {
505 auto& kernel = Core::System::GetInstance().Kernel();
506 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, 507 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
507 "AMMessageQueue:OnMessageRecieved"); 508 "AMMessageQueue:OnMessageRecieved");
508 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( 509 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(
@@ -937,9 +938,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
937 rb.Push(RESULT_SUCCESS); 938 rb.Push(RESULT_SUCCESS);
938} 939}
939 940
940ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) 941ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_)
941 : ServiceFramework("ILibraryAppletCreator"), 942 : ServiceFramework("ILibraryAppletCreator"), system{system_} {
942 current_process_title_id(current_process_title_id) {
943 static const FunctionInfo functions[] = { 943 static const FunctionInfo functions[] = {
944 {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, 944 {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
945 {1, nullptr, "TerminateAllLibraryApplets"}, 945 {1, nullptr, "TerminateAllLibraryApplets"},
@@ -961,8 +961,8 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
961 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", 961 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}",
962 static_cast<u32>(applet_id), applet_mode); 962 static_cast<u32>(applet_id), applet_mode);
963 963
964 const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; 964 const auto& applet_manager{system.GetAppletManager()};
965 const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id); 965 const auto applet = applet_manager.GetApplet(applet_id);
966 966
967 if (applet == nullptr) { 967 if (applet == nullptr) {
968 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); 968 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
@@ -999,8 +999,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
999 const auto handle{rp.Pop<Kernel::Handle>()}; 999 const auto handle{rp.Pop<Kernel::Handle>()};
1000 1000
1001 const auto transfer_mem = 1001 const auto transfer_mem =
1002 Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>( 1002 system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle);
1003 handle);
1004 1003
1005 if (transfer_mem == nullptr) { 1004 if (transfer_mem == nullptr) {
1006 LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); 1005 LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle);
@@ -1018,7 +1017,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
1018 rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); 1017 rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory)));
1019} 1018}
1020 1019
1021IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { 1020IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1021 : ServiceFramework("IApplicationFunctions"), system{system_} {
1022 // clang-format off 1022 // clang-format off
1023 static const FunctionInfo functions[] = { 1023 static const FunctionInfo functions[] = {
1024 {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, 1024 {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
@@ -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;
@@ -1175,7 +1180,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
1175 // Get supported languages from NACP, if possible 1180 // Get supported languages from NACP, if possible
1176 // Default to 0 (all languages supported) 1181 // Default to 0 (all languages supported)
1177 u32 supported_languages = 0; 1182 u32 supported_languages = 0;
1178 FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()}; 1183 FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
1179 1184
1180 const auto res = pm.GetControlMetadata(); 1185 const auto res = pm.GetControlMetadata();
1181 if (res.first != nullptr) { 1186 if (res.first != nullptr) {
@@ -1183,8 +1188,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
1183 } 1188 }
1184 1189
1185 // Call IApplicationManagerInterface implementation. 1190 // Call IApplicationManagerInterface implementation.
1186 auto& service_manager = Core::System::GetInstance().ServiceManager(); 1191 auto& service_manager = system.ServiceManager();
1187 auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2"); 1192 auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
1188 auto app_man = ns_am2->GetApplicationManagerInterface(); 1193 auto app_man = ns_am2->GetApplicationManagerInterface();
1189 1194
1190 // Get desired application language 1195 // Get desired application language
@@ -1256,8 +1261,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) {
1256 "new_journal={:016X}", 1261 "new_journal={:016X}",
1257 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); 1262 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
1258 1263
1259 FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, 1264 const auto title_id = system.CurrentProcess()->GetTitleID();
1260 {new_normal_size, new_journal_size}); 1265 FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size});
1261 1266
1262 IPC::ResponseBuilder rb{ctx, 4}; 1267 IPC::ResponseBuilder rb{ctx, 4};
1263 rb.Push(RESULT_SUCCESS); 1268 rb.Push(RESULT_SUCCESS);
@@ -1276,8 +1281,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
1276 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), 1281 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
1277 user_id[1], user_id[0]); 1282 user_id[1], user_id[0]);
1278 1283
1279 const auto size = 1284 const auto title_id = system.CurrentProcess()->GetTitleID();
1280 FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); 1285 const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id);
1281 1286
1282 IPC::ResponseBuilder rb{ctx, 6}; 1287 IPC::ResponseBuilder rb{ctx, 6};
1283 rb.Push(RESULT_SUCCESS); 1288 rb.Push(RESULT_SUCCESS);
@@ -1285,11 +1290,19 @@ 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>(system.Kernel());
1291 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on 1304 // Needed on game boot
1292 // game boot 1305 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
1293 1306
1294 std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); 1307 std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
1295 std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); 1308 std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6cb582483..28f870302 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -10,12 +10,15 @@
10#include "core/hle/kernel/writable_event.h" 10#include "core/hle/kernel/writable_event.h"
11#include "core/hle/service/service.h" 11#include "core/hle/service/service.h"
12 12
13namespace Service { 13namespace Kernel {
14namespace NVFlinger { 14class KernelCore;
15}
16
17namespace Service::NVFlinger {
15class NVFlinger; 18class NVFlinger;
16} 19}
17 20
18namespace AM { 21namespace Service::AM {
19 22
20enum SystemLanguage { 23enum SystemLanguage {
21 Japanese = 0, 24 Japanese = 0,
@@ -47,7 +50,7 @@ public:
47 PerformanceModeChanged = 31, 50 PerformanceModeChanged = 31,
48 }; 51 };
49 52
50 AppletMessageQueue(); 53 explicit AppletMessageQueue(Kernel::KernelCore& kernel);
51 ~AppletMessageQueue(); 54 ~AppletMessageQueue();
52 55
53 const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; 56 const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const;
@@ -65,12 +68,14 @@ private:
65 68
66class IWindowController final : public ServiceFramework<IWindowController> { 69class IWindowController final : public ServiceFramework<IWindowController> {
67public: 70public:
68 IWindowController(); 71 explicit IWindowController(Core::System& system_);
69 ~IWindowController() override; 72 ~IWindowController() override;
70 73
71private: 74private:
72 void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); 75 void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
73 void AcquireForegroundRights(Kernel::HLERequestContext& ctx); 76 void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
77
78 Core::System& system;
74}; 79};
75 80
76class IAudioController final : public ServiceFramework<IAudioController> { 81class IAudioController final : public ServiceFramework<IAudioController> {
@@ -113,7 +118,8 @@ public:
113 118
114class ISelfController final : public ServiceFramework<ISelfController> { 119class ISelfController final : public ServiceFramework<ISelfController> {
115public: 120public:
116 explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); 121 explicit ISelfController(Core::System& system_,
122 std::shared_ptr<NVFlinger::NVFlinger> nvflinger_);
117 ~ISelfController() override; 123 ~ISelfController() override;
118 124
119private: 125private:
@@ -208,7 +214,7 @@ private:
208 214
209class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { 215class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
210public: 216public:
211 ILibraryAppletCreator(u64 current_process_title_id); 217 explicit ILibraryAppletCreator(Core::System& system_);
212 ~ILibraryAppletCreator() override; 218 ~ILibraryAppletCreator() override;
213 219
214private: 220private:
@@ -216,12 +222,12 @@ private:
216 void CreateStorage(Kernel::HLERequestContext& ctx); 222 void CreateStorage(Kernel::HLERequestContext& ctx);
217 void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); 223 void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
218 224
219 u64 current_process_title_id; 225 Core::System& system;
220}; 226};
221 227
222class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 228class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
223public: 229public:
224 IApplicationFunctions(); 230 explicit IApplicationFunctions(Core::System& system_);
225 ~IApplicationFunctions() override; 231 ~IApplicationFunctions() override;
226 232
227private: 233private:
@@ -242,6 +248,10 @@ private:
242 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); 248 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
243 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); 249 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
244 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); 250 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
251 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
252
253 Kernel::EventPair gpu_error_detected_event;
254 Core::System& system;
245}; 255};
246 256
247class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { 257class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
@@ -275,5 +285,4 @@ public:
275void InstallInterfaces(SM::ServiceManager& service_manager, 285void InstallInterfaces(SM::ServiceManager& service_manager,
276 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); 286 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
277 287
278} // namespace AM 288} // namespace Service::AM
279} // namespace Service
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index a34368c8b..e454b77d8 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -50,7 +50,7 @@ private:
50 50
51 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 51 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
52 rb.Push(RESULT_SUCCESS); 52 rb.Push(RESULT_SUCCESS);
53 rb.PushIpcInterface<ISelfController>(nvflinger); 53 rb.PushIpcInterface<ISelfController>(system, nvflinger);
54 } 54 }
55 55
56 void GetWindowController(Kernel::HLERequestContext& ctx) { 56 void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -58,7 +58,7 @@ private:
58 58
59 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 59 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
60 rb.Push(RESULT_SUCCESS); 60 rb.Push(RESULT_SUCCESS);
61 rb.PushIpcInterface<IWindowController>(); 61 rb.PushIpcInterface<IWindowController>(system);
62 } 62 }
63 63
64 void GetAudioController(Kernel::HLERequestContext& ctx) { 64 void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -98,7 +98,7 @@ private:
98 98
99 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 99 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
100 rb.Push(RESULT_SUCCESS); 100 rb.Push(RESULT_SUCCESS);
101 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 101 rb.PushIpcInterface<ILibraryAppletCreator>(system);
102 } 102 }
103 103
104 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { 104 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -106,7 +106,7 @@ private:
106 106
107 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 107 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
108 rb.Push(RESULT_SUCCESS); 108 rb.Push(RESULT_SUCCESS);
109 rb.PushIpcInterface<IApplicationFunctions>(); 109 rb.PushIpcInterface<IApplicationFunctions>(system);
110 } 110 }
111 111
112 std::shared_ptr<NVFlinger::NVFlinger> nvflinger; 112 std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
@@ -154,7 +154,7 @@ private:
154 154
155 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 155 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
156 rb.Push(RESULT_SUCCESS); 156 rb.Push(RESULT_SUCCESS);
157 rb.PushIpcInterface<ISelfController>(nvflinger); 157 rb.PushIpcInterface<ISelfController>(system, nvflinger);
158 } 158 }
159 159
160 void GetWindowController(Kernel::HLERequestContext& ctx) { 160 void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -162,7 +162,7 @@ private:
162 162
163 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 163 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
164 rb.Push(RESULT_SUCCESS); 164 rb.Push(RESULT_SUCCESS);
165 rb.PushIpcInterface<IWindowController>(); 165 rb.PushIpcInterface<IWindowController>(system);
166 } 166 }
167 167
168 void GetAudioController(Kernel::HLERequestContext& ctx) { 168 void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -194,7 +194,7 @@ private:
194 194
195 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 195 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
196 rb.Push(RESULT_SUCCESS); 196 rb.Push(RESULT_SUCCESS);
197 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 197 rb.PushIpcInterface<ILibraryAppletCreator>(system);
198 } 198 }
199 199
200 void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { 200 void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index 5d53ef113..a2ffaa440 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -4,7 +4,6 @@
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/hle/ipc_helpers.h" 6#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/process.h"
8#include "core/hle/service/am/am.h" 7#include "core/hle/service/am/am.h"
9#include "core/hle/service/am/applet_oe.h" 8#include "core/hle/service/am/applet_oe.h"
10#include "core/hle/service/nvflinger/nvflinger.h" 9#include "core/hle/service/nvflinger/nvflinger.h"
@@ -64,7 +63,7 @@ private:
64 63
65 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 64 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
66 rb.Push(RESULT_SUCCESS); 65 rb.Push(RESULT_SUCCESS);
67 rb.PushIpcInterface<IWindowController>(); 66 rb.PushIpcInterface<IWindowController>(system);
68 } 67 }
69 68
70 void GetSelfController(Kernel::HLERequestContext& ctx) { 69 void GetSelfController(Kernel::HLERequestContext& ctx) {
@@ -72,7 +71,7 @@ private:
72 71
73 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 72 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
74 rb.Push(RESULT_SUCCESS); 73 rb.Push(RESULT_SUCCESS);
75 rb.PushIpcInterface<ISelfController>(nvflinger); 74 rb.PushIpcInterface<ISelfController>(system, nvflinger);
76 } 75 }
77 76
78 void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { 77 void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
@@ -88,7 +87,7 @@ private:
88 87
89 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 88 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
90 rb.Push(RESULT_SUCCESS); 89 rb.Push(RESULT_SUCCESS);
91 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 90 rb.PushIpcInterface<ILibraryAppletCreator>(system);
92 } 91 }
93 92
94 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { 93 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -96,7 +95,7 @@ private:
96 95
97 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 96 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
98 rb.Push(RESULT_SUCCESS); 97 rb.Push(RESULT_SUCCESS);
99 rb.PushIpcInterface<IApplicationFunctions>(); 98 rb.PushIpcInterface<IApplicationFunctions>(system);
100 } 99 }
101 100
102 std::shared_ptr<NVFlinger::NVFlinger> nvflinger; 101 std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 6bdba2468..d2e35362f 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -23,8 +23,7 @@
23 23
24namespace Service::AM::Applets { 24namespace Service::AM::Applets {
25 25
26AppletDataBroker::AppletDataBroker() { 26AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
27 auto& kernel = Core::System::GetInstance().Kernel();
28 state_changed_event = Kernel::WritableEvent::CreateEventPair( 27 state_changed_event = Kernel::WritableEvent::CreateEventPair(
29 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); 28 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
30 pop_out_data_event = Kernel::WritableEvent::CreateEventPair( 29 pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
@@ -121,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent(
121 return state_changed_event.readable; 120 return state_changed_event.readable;
122} 121}
123 122
124Applet::Applet() = default; 123Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
125 124
126Applet::~Applet() = default; 125Applet::~Applet() = default;
127 126
@@ -154,7 +153,7 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
154 153
155AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; 154AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
156 155
157AppletManager::AppletManager() = default; 156AppletManager::AppletManager(Core::System& system_) : system{system_} {}
158 157
159AppletManager::~AppletManager() = default; 158AppletManager::~AppletManager() = default;
160 159
@@ -216,28 +215,28 @@ void AppletManager::ClearAll() {
216 frontend = {}; 215 frontend = {};
217} 216}
218 217
219std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { 218std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
220 switch (id) { 219 switch (id) {
221 case AppletId::Auth: 220 case AppletId::Auth:
222 return std::make_shared<Auth>(*frontend.parental_controls); 221 return std::make_shared<Auth>(system, *frontend.parental_controls);
223 case AppletId::Error: 222 case AppletId::Error:
224 return std::make_shared<Error>(*frontend.error); 223 return std::make_shared<Error>(system, *frontend.error);
225 case AppletId::ProfileSelect: 224 case AppletId::ProfileSelect:
226 return std::make_shared<ProfileSelect>(*frontend.profile_select); 225 return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
227 case AppletId::SoftwareKeyboard: 226 case AppletId::SoftwareKeyboard:
228 return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); 227 return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
229 case AppletId::PhotoViewer: 228 case AppletId::PhotoViewer:
230 return std::make_shared<PhotoViewer>(*frontend.photo_viewer); 229 return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
231 case AppletId::LibAppletShop: 230 case AppletId::LibAppletShop:
232 return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id, 231 return std::make_shared<WebBrowser>(system, *frontend.web_browser,
233 frontend.e_commerce.get()); 232 frontend.e_commerce.get());
234 case AppletId::LibAppletOff: 233 case AppletId::LibAppletOff:
235 return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id); 234 return std::make_shared<WebBrowser>(system, *frontend.web_browser);
236 default: 235 default:
237 UNIMPLEMENTED_MSG( 236 UNIMPLEMENTED_MSG(
238 "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", 237 "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
239 static_cast<u8>(id)); 238 static_cast<u8>(id));
240 return std::make_shared<StubApplet>(id); 239 return std::make_shared<StubApplet>(system, id);
241 } 240 }
242} 241}
243 242
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index adc973dad..764c3418c 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -12,6 +12,10 @@
12 12
13union ResultCode; 13union ResultCode;
14 14
15namespace Core {
16class System;
17}
18
15namespace Core::Frontend { 19namespace Core::Frontend {
16class ECommerceApplet; 20class ECommerceApplet;
17class ErrorApplet; 21class ErrorApplet;
@@ -22,6 +26,10 @@ class SoftwareKeyboardApplet;
22class WebBrowserApplet; 26class WebBrowserApplet;
23} // namespace Core::Frontend 27} // namespace Core::Frontend
24 28
29namespace Kernel {
30class KernelCore;
31}
32
25namespace Service::AM { 33namespace Service::AM {
26 34
27class IStorage; 35class IStorage;
@@ -53,7 +61,7 @@ enum class AppletId : u32 {
53 61
54class AppletDataBroker final { 62class AppletDataBroker final {
55public: 63public:
56 AppletDataBroker(); 64 explicit AppletDataBroker(Kernel::KernelCore& kernel_);
57 ~AppletDataBroker(); 65 ~AppletDataBroker();
58 66
59 struct RawChannelData { 67 struct RawChannelData {
@@ -108,7 +116,7 @@ private:
108 116
109class Applet { 117class Applet {
110public: 118public:
111 Applet(); 119 explicit Applet(Kernel::KernelCore& kernel_);
112 virtual ~Applet(); 120 virtual ~Applet();
113 121
114 virtual void Initialize(); 122 virtual void Initialize();
@@ -179,7 +187,7 @@ struct AppletFrontendSet {
179 187
180class AppletManager { 188class AppletManager {
181public: 189public:
182 AppletManager(); 190 explicit AppletManager(Core::System& system_);
183 ~AppletManager(); 191 ~AppletManager();
184 192
185 void SetAppletFrontendSet(AppletFrontendSet set); 193 void SetAppletFrontendSet(AppletFrontendSet set);
@@ -187,10 +195,11 @@ public:
187 void SetDefaultAppletsIfMissing(); 195 void SetDefaultAppletsIfMissing();
188 void ClearAll(); 196 void ClearAll();
189 197
190 std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const; 198 std::shared_ptr<Applet> GetApplet(AppletId id) const;
191 199
192private: 200private:
193 AppletFrontendSet frontend; 201 AppletFrontendSet frontend;
202 Core::System& system;
194}; 203};
195 204
196} // namespace Applets 205} // namespace Applets
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
index af3a900f8..a7db26725 100644
--- a/src/core/hle/service/am/applets/error.cpp
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -85,7 +85,8 @@ ResultCode Decode64BitError(u64 error) {
85 85
86} // Anonymous namespace 86} // Anonymous namespace
87 87
88Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} 88Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
89 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
89 90
90Error::~Error() = default; 91Error::~Error() = default;
91 92
@@ -145,8 +146,8 @@ void Error::Execute() {
145 } 146 }
146 147
147 const auto callback = [this] { DisplayCompleted(); }; 148 const auto callback = [this] { DisplayCompleted(); };
148 const auto title_id = Core::CurrentProcess()->GetTitleID(); 149 const auto title_id = system.CurrentProcess()->GetTitleID();
149 const auto& reporter{Core::System::GetInstance().GetReporter()}; 150 const auto& reporter{system.GetReporter()};
150 151
151 switch (mode) { 152 switch (mode) {
152 case ErrorAppletMode::ShowError: 153 case ErrorAppletMode::ShowError:
diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h
index a3590d181..a105cdb0c 100644
--- a/src/core/hle/service/am/applets/error.h
+++ b/src/core/hle/service/am/applets/error.h
@@ -7,6 +7,10 @@
7#include "core/hle/result.h" 7#include "core/hle/result.h"
8#include "core/hle/service/am/applets/applets.h" 8#include "core/hle/service/am/applets/applets.h"
9 9
10namespace Core {
11class System;
12}
13
10namespace Service::AM::Applets { 14namespace Service::AM::Applets {
11 15
12enum class ErrorAppletMode : u8 { 16enum class ErrorAppletMode : u8 {
@@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 {
21 25
22class Error final : public Applet { 26class Error final : public Applet {
23public: 27public:
24 explicit Error(const Core::Frontend::ErrorApplet& frontend); 28 explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_);
25 ~Error() override; 29 ~Error() override;
26 30
27 void Initialize() override; 31 void Initialize() override;
@@ -42,6 +46,7 @@ private:
42 std::unique_ptr<ErrorArguments> args; 46 std::unique_ptr<ErrorArguments> args;
43 47
44 bool complete = false; 48 bool complete = false;
49 Core::System& system;
45}; 50};
46 51
47} // namespace Service::AM::Applets 52} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index e0def8dff..328438a1d 100644
--- a/src/core/hle/service/am/applets/general_backend.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -37,7 +37,8 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
37 } 37 }
38} 38}
39 39
40Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} 40Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
41 : Applet{system_.Kernel()}, frontend(frontend_) {}
41 42
42Auth::~Auth() = default; 43Auth::~Auth() = default;
43 44
@@ -151,7 +152,8 @@ void Auth::AuthFinished(bool successful) {
151 broker.SignalStateChanged(); 152 broker.SignalStateChanged();
152} 153}
153 154
154PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} 155PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
156 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
155 157
156PhotoViewer::~PhotoViewer() = default; 158PhotoViewer::~PhotoViewer() = default;
157 159
@@ -185,7 +187,7 @@ void PhotoViewer::Execute() {
185 const auto callback = [this] { ViewFinished(); }; 187 const auto callback = [this] { ViewFinished(); };
186 switch (mode) { 188 switch (mode) {
187 case PhotoViewerAppletMode::CurrentApp: 189 case PhotoViewerAppletMode::CurrentApp:
188 frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback); 190 frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback);
189 break; 191 break;
190 case PhotoViewerAppletMode::AllApps: 192 case PhotoViewerAppletMode::AllApps:
191 frontend.ShowAllPhotos(callback); 193 frontend.ShowAllPhotos(callback);
@@ -200,7 +202,8 @@ void PhotoViewer::ViewFinished() {
200 broker.SignalStateChanged(); 202 broker.SignalStateChanged();
201} 203}
202 204
203StubApplet::StubApplet(AppletId id) : id(id) {} 205StubApplet::StubApplet(Core::System& system_, AppletId id_)
206 : Applet{system_.Kernel()}, id(id_), system{system_} {}
204 207
205StubApplet::~StubApplet() = default; 208StubApplet::~StubApplet() = default;
206 209
@@ -209,7 +212,7 @@ void StubApplet::Initialize() {
209 Applet::Initialize(); 212 Applet::Initialize();
210 213
211 const auto data = broker.PeekDataToAppletForDebug(); 214 const auto data = broker.PeekDataToAppletForDebug();
212 Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport( 215 system.GetReporter().SaveUnimplementedAppletReport(
213 static_cast<u32>(id), common_args.arguments_version, common_args.library_version, 216 static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
214 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, 217 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
215 data.normal, data.interactive); 218 data.normal, data.interactive);
diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h
index 0da252044..cfa2df369 100644
--- a/src/core/hle/service/am/applets/general_backend.h
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -6,6 +6,10 @@
6 6
7#include "core/hle/service/am/applets/applets.h" 7#include "core/hle/service/am/applets/applets.h"
8 8
9namespace Core {
10class System;
11}
12
9namespace Service::AM::Applets { 13namespace Service::AM::Applets {
10 14
11enum class AuthAppletType : u32 { 15enum class AuthAppletType : u32 {
@@ -16,7 +20,7 @@ enum class AuthAppletType : u32 {
16 20
17class Auth final : public Applet { 21class Auth final : public Applet {
18public: 22public:
19 explicit Auth(Core::Frontend::ParentalControlsApplet& frontend); 23 explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_);
20 ~Auth() override; 24 ~Auth() override;
21 25
22 void Initialize() override; 26 void Initialize() override;
@@ -45,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 {
45 49
46class PhotoViewer final : public Applet { 50class PhotoViewer final : public Applet {
47public: 51public:
48 explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend); 52 explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_);
49 ~PhotoViewer() override; 53 ~PhotoViewer() override;
50 54
51 void Initialize() override; 55 void Initialize() override;
@@ -60,11 +64,12 @@ private:
60 const Core::Frontend::PhotoViewerApplet& frontend; 64 const Core::Frontend::PhotoViewerApplet& frontend;
61 bool complete = false; 65 bool complete = false;
62 PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; 66 PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp;
67 Core::System& system;
63}; 68};
64 69
65class StubApplet final : public Applet { 70class StubApplet final : public Applet {
66public: 71public:
67 explicit StubApplet(AppletId id); 72 explicit StubApplet(Core::System& system_, AppletId id_);
68 ~StubApplet() override; 73 ~StubApplet() override;
69 74
70 void Initialize() override; 75 void Initialize() override;
@@ -76,6 +81,7 @@ public:
76 81
77private: 82private:
78 AppletId id; 83 AppletId id;
84 Core::System& system;
79}; 85};
80 86
81} // namespace Service::AM::Applets 87} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 57b5419e8..3eba696ca 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -15,8 +15,9 @@ namespace Service::AM::Applets {
15 15
16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; 16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
17 17
18ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) 18ProfileSelect::ProfileSelect(Core::System& system_,
19 : frontend(frontend) {} 19 const Core::Frontend::ProfileSelectApplet& frontend_)
20 : Applet{system_.Kernel()}, frontend(frontend_) {}
20 21
21ProfileSelect::~ProfileSelect() = default; 22ProfileSelect::~ProfileSelect() = default;
22 23
diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h
index 563cd744a..16364ead7 100644
--- a/src/core/hle/service/am/applets/profile_select.h
+++ b/src/core/hle/service/am/applets/profile_select.h
@@ -11,6 +11,10 @@
11#include "core/hle/result.h" 11#include "core/hle/result.h"
12#include "core/hle/service/am/applets/applets.h" 12#include "core/hle/service/am/applets/applets.h"
13 13
14namespace Core {
15class System;
16}
17
14namespace Service::AM::Applets { 18namespace Service::AM::Applets {
15 19
16struct UserSelectionConfig { 20struct UserSelectionConfig {
@@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco
29 33
30class ProfileSelect final : public Applet { 34class ProfileSelect final : public Applet {
31public: 35public:
32 explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend); 36 explicit ProfileSelect(Core::System& system_,
37 const Core::Frontend::ProfileSelectApplet& frontend_);
33 ~ProfileSelect() override; 38 ~ProfileSelect() override;
34 39
35 void Initialize() override; 40 void Initialize() override;
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index e197990f7..748559cd0 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -39,8 +39,9 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
39 return params; 39 return params;
40} 40}
41 41
42SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) 42SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
43 : frontend(frontend) {} 43 const Core::Frontend::SoftwareKeyboardApplet& frontend_)
44 : Applet{system_.Kernel()}, frontend(frontend_) {}
44 45
45SoftwareKeyboard::~SoftwareKeyboard() = default; 46SoftwareKeyboard::~SoftwareKeyboard() = default;
46 47
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index 0fbc43e51..ef4801fc6 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -16,6 +16,10 @@
16 16
17union ResultCode; 17union ResultCode;
18 18
19namespace Core {
20class System;
21}
22
19namespace Service::AM::Applets { 23namespace Service::AM::Applets {
20 24
21enum class KeysetDisable : u32 { 25enum class KeysetDisable : u32 {
@@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz
55 59
56class SoftwareKeyboard final : public Applet { 60class SoftwareKeyboard final : public Applet {
57public: 61public:
58 explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend); 62 explicit SoftwareKeyboard(Core::System& system_,
63 const Core::Frontend::SoftwareKeyboardApplet& frontend_);
59 ~SoftwareKeyboard() override; 64 ~SoftwareKeyboard() override;
60 65
61 void Initialize() override; 66 void Initialize() override;
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index f3c9fef0e..32283e819 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -190,8 +190,9 @@ std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>&
190 return out; 190 return out;
191} 191}
192 192
193FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { 193FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
194 const auto& installed{Core::System::GetInstance().GetContentProvider()}; 194 FileSys::ContentRecordType type) {
195 const auto& installed{system.GetContentProvider()};
195 const auto res = installed.GetEntry(title_id, type); 196 const auto res = installed.GetEntry(title_id, type);
196 197
197 if (res != nullptr) { 198 if (res != nullptr) {
@@ -207,10 +208,10 @@ FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordTyp
207 208
208} // Anonymous namespace 209} // Anonymous namespace
209 210
210WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, 211WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
211 Core::Frontend::ECommerceApplet* frontend_e_commerce) 212 Core::Frontend::ECommerceApplet* frontend_e_commerce_)
212 : frontend(frontend), frontend_e_commerce(frontend_e_commerce), 213 : Applet{system_.Kernel()}, frontend(frontend_),
213 current_process_title_id(current_process_title_id) {} 214 frontend_e_commerce(frontend_e_commerce_), system{system_} {}
214 215
215WebBrowser::~WebBrowser() = default; 216WebBrowser::~WebBrowser() = default;
216 217
@@ -266,7 +267,7 @@ void WebBrowser::UnpackRomFS() {
266 ASSERT(offline_romfs != nullptr); 267 ASSERT(offline_romfs != nullptr);
267 const auto dir = 268 const auto dir =
268 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); 269 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
269 const auto& vfs{Core::System::GetInstance().GetFilesystem()}; 270 const auto& vfs{system.GetFilesystem()};
270 const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); 271 const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
271 FileSys::VfsRawCopyD(dir, temp_dir); 272 FileSys::VfsRawCopyD(dir, temp_dir);
272 273
@@ -470,10 +471,10 @@ void WebBrowser::InitializeOffline() {
470 } 471 }
471 472
472 if (title_id == 0) { 473 if (title_id == 0) {
473 title_id = current_process_title_id; 474 title_id = system.CurrentProcess()->GetTitleID();
474 } 475 }
475 476
476 offline_romfs = GetApplicationRomFS(title_id, type); 477 offline_romfs = GetApplicationRomFS(system, title_id, type);
477 if (offline_romfs == nullptr) { 478 if (offline_romfs == nullptr) {
478 status = ResultCode(-1); 479 status = ResultCode(-1);
479 LOG_ERROR(Service_AM, "Failed to find offline data for request!"); 480 LOG_ERROR(Service_AM, "Failed to find offline data for request!");
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 870f57b64..8d4027411 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -9,6 +9,10 @@
9#include "core/hle/service/am/am.h" 9#include "core/hle/service/am/am.h"
10#include "core/hle/service/am/applets/applets.h" 10#include "core/hle/service/am/applets/applets.h"
11 11
12namespace Core {
13class System;
14}
15
12namespace Service::AM::Applets { 16namespace Service::AM::Applets {
13 17
14enum class ShimKind : u32; 18enum class ShimKind : u32;
@@ -17,8 +21,8 @@ enum class WebArgTLVType : u16;
17 21
18class WebBrowser final : public Applet { 22class WebBrowser final : public Applet {
19public: 23public:
20 WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, 24 WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
21 Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr); 25 Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
22 26
23 ~WebBrowser() override; 27 ~WebBrowser() override;
24 28
@@ -59,8 +63,6 @@ private:
59 bool unpacked = false; 63 bool unpacked = false;
60 ResultCode status = RESULT_SUCCESS; 64 ResultCode status = RESULT_SUCCESS;
61 65
62 u64 current_process_title_id;
63
64 ShimKind kind; 66 ShimKind kind;
65 std::map<WebArgTLVType, std::vector<u8>> args; 67 std::map<WebArgTLVType, std::vector<u8>> args;
66 68
@@ -74,6 +76,8 @@ private:
74 std::optional<u128> user_id; 76 std::optional<u128> user_id;
75 std::optional<bool> shop_full_display; 77 std::optional<bool> shop_full_display;
76 std::string shop_extra_parameter; 78 std::string shop_extra_parameter;
79
80 Core::System& system;
77}; 81};
78 82
79} // namespace Service::AM::Applets 83} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 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/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 131b01d62..8d0353075 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -175,6 +175,10 @@ MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) {
175} // namespace 175} // namespace
176 176
177std::ostream& operator<<(std::ostream& os, Source source) { 177std::ostream& operator<<(std::ostream& os, Source source) {
178 if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) {
179 return os << "[UNKNOWN SOURCE]";
180 }
181
178 os << SOURCE_NAMES.at(static_cast<std::size_t>(source)); 182 os << SOURCE_NAMES.at(static_cast<std::size_t>(source));
179 return os; 183 return os;
180} 184}
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 76494f0b7..926a1285d 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -37,7 +37,7 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
37 transform, crop_rect}; 37 transform, crop_rect};
38 38
39 system.GetPerfStats().EndGameFrame(); 39 system.GetPerfStats().EndGameFrame();
40 system.GPU().SwapBuffers(framebuffer); 40 system.GPU().SwapBuffers(&framebuffer);
41} 41}
42 42
43} // namespace Service::Nvidia::Devices 43} // namespace Service::Nvidia::Devices
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/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 6a5a4f5c4..f5158d219 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -249,16 +249,10 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
249 executing_macro = 0; 249 executing_macro = 0;
250 250
251 // Lookup the macro offset 251 // Lookup the macro offset
252 const u32 entry{(method - MacroRegistersStart) >> 1}; 252 const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size();
253 const auto& search{macro_offsets.find(entry)};
254 if (search == macro_offsets.end()) {
255 LOG_CRITICAL(HW_GPU, "macro not found for method 0x{:X}!", method);
256 UNREACHABLE();
257 return;
258 }
259 253
260 // Execute the current macro. 254 // Execute the current macro.
261 macro_interpreter.Execute(search->second, std::move(parameters)); 255 macro_interpreter.Execute(macro_positions[entry], std::move(parameters));
262} 256}
263 257
264void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { 258void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
@@ -421,7 +415,7 @@ void Maxwell3D::ProcessMacroUpload(u32 data) {
421} 415}
422 416
423void Maxwell3D::ProcessMacroBind(u32 data) { 417void Maxwell3D::ProcessMacroBind(u32 data) {
424 macro_offsets[regs.macros.entry] = data; 418 macro_positions[regs.macros.entry++] = data;
425} 419}
426 420
427void Maxwell3D::ProcessQueryGet() { 421void Maxwell3D::ProcessQueryGet() {
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 1ee982b76..0184342a0 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1270,7 +1270,7 @@ private:
1270 MemoryManager& memory_manager; 1270 MemoryManager& memory_manager;
1271 1271
1272 /// Start offsets of each macro in macro_memory 1272 /// Start offsets of each macro in macro_memory
1273 std::unordered_map<u32, u32> macro_offsets; 1273 std::array<u32, 0x80> macro_positions = {};
1274 1274
1275 /// Memory for macro code 1275 /// Memory for macro code
1276 MacroMemory macro_memory; 1276 MacroMemory macro_memory;
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index bc8c2a1c5..c3678b9ea 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -886,6 +886,7 @@ union Instruction {
886 union { 886 union {
887 BitField<0, 3, u64> pred0; 887 BitField<0, 3, u64> pred0;
888 BitField<3, 3, u64> pred3; 888 BitField<3, 3, u64> pred3;
889 BitField<6, 1, u64> neg_b;
889 BitField<7, 1, u64> abs_a; 890 BitField<7, 1, u64> abs_a;
890 BitField<39, 3, u64> pred39; 891 BitField<39, 3, u64> pred39;
891 BitField<42, 1, u64> neg_pred; 892 BitField<42, 1, u64> neg_pred;
@@ -1019,7 +1020,6 @@ union Instruction {
1019 } iset; 1020 } iset;
1020 1021
1021 union { 1022 union {
1022 BitField<41, 2, u64> selector; // i2i and i2f only
1023 BitField<45, 1, u64> negate_a; 1023 BitField<45, 1, u64> negate_a;
1024 BitField<49, 1, u64> abs_a; 1024 BitField<49, 1, u64> abs_a;
1025 BitField<10, 2, Register::Size> src_size; 1025 BitField<10, 2, Register::Size> src_size;
@@ -1045,6 +1045,13 @@ union Instruction {
1045 } 1045 }
1046 } f2f; 1046 } f2f;
1047 1047
1048 union {
1049 BitField<41, 2, u64> selector;
1050 } int_src;
1051
1052 union {
1053 BitField<41, 1, u64> selector;
1054 } float_src;
1048 } conversion; 1055 } conversion;
1049 1056
1050 union { 1057 union {
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 8d9db45f5..2c47541cb 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -17,18 +17,6 @@
17 17
18namespace Tegra { 18namespace Tegra {
19 19
20u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
21 switch (format) {
22 case PixelFormat::ABGR8:
23 case PixelFormat::BGRA8:
24 return 4;
25 default:
26 return 4;
27 }
28
29 UNREACHABLE();
30}
31
32GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async) 20GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async)
33 : system{system}, renderer{renderer}, is_async{is_async} { 21 : system{system}, renderer{renderer}, is_async{is_async} {
34 auto& rasterizer{renderer.Rasterizer()}; 22 auto& rasterizer{renderer.Rasterizer()};
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 544340ecd..78bc0601a 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -95,14 +95,10 @@ class DebugContext;
95struct FramebufferConfig { 95struct FramebufferConfig {
96 enum class PixelFormat : u32 { 96 enum class PixelFormat : u32 {
97 ABGR8 = 1, 97 ABGR8 = 1,
98 RGB565 = 4,
98 BGRA8 = 5, 99 BGRA8 = 5,
99 }; 100 };
100 101
101 /**
102 * Returns the number of bytes per pixel.
103 */
104 static u32 BytesPerPixel(PixelFormat format);
105
106 VAddr address; 102 VAddr address;
107 u32 offset; 103 u32 offset;
108 u32 width; 104 u32 width;
@@ -253,8 +249,7 @@ public:
253 virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; 249 virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0;
254 250
255 /// Swap buffers (render frame) 251 /// Swap buffers (render frame)
256 virtual void SwapBuffers( 252 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
257 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
258 253
259 /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory 254 /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
260 virtual void FlushRegion(CacheAddr addr, u64 size) = 0; 255 virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index ea67be831..f2a3a390e 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -23,9 +23,8 @@ void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
23 gpu_thread.SubmitList(std::move(entries)); 23 gpu_thread.SubmitList(std::move(entries));
24} 24}
25 25
26void GPUAsynch::SwapBuffers( 26void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
27 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { 27 gpu_thread.SwapBuffers(framebuffer);
28 gpu_thread.SwapBuffers(std::move(framebuffer));
29} 28}
30 29
31void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) { 30void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) {
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index 36377d677..a12f9bac4 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -14,15 +14,14 @@ class RendererBase;
14namespace VideoCommon { 14namespace VideoCommon {
15 15
16/// Implementation of GPU interface that runs the GPU asynchronously 16/// Implementation of GPU interface that runs the GPU asynchronously
17class GPUAsynch : public Tegra::GPU { 17class GPUAsynch final : public Tegra::GPU {
18public: 18public:
19 explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer); 19 explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer);
20 ~GPUAsynch() override; 20 ~GPUAsynch() override;
21 21
22 void Start() override; 22 void Start() override;
23 void PushGPUEntries(Tegra::CommandList&& entries) override; 23 void PushGPUEntries(Tegra::CommandList&& entries) override;
24 void SwapBuffers( 24 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
25 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
26 void FlushRegion(CacheAddr addr, u64 size) override; 25 void FlushRegion(CacheAddr addr, u64 size) override;
27 void InvalidateRegion(CacheAddr addr, u64 size) override; 26 void InvalidateRegion(CacheAddr addr, u64 size) override;
28 void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; 27 void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index d4ead9c47..d48221077 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -19,9 +19,8 @@ void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
19 dma_pusher->DispatchCalls(); 19 dma_pusher->DispatchCalls();
20} 20}
21 21
22void GPUSynch::SwapBuffers( 22void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
23 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { 23 renderer.SwapBuffers(framebuffer);
24 renderer.SwapBuffers(std::move(framebuffer));
25} 24}
26 25
27void GPUSynch::FlushRegion(CacheAddr addr, u64 size) { 26void GPUSynch::FlushRegion(CacheAddr addr, u64 size) {
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 07bcc47f1..5eb1c461c 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -13,15 +13,14 @@ class RendererBase;
13namespace VideoCommon { 13namespace VideoCommon {
14 14
15/// Implementation of GPU interface that runs the GPU synchronously 15/// Implementation of GPU interface that runs the GPU synchronously
16class GPUSynch : public Tegra::GPU { 16class GPUSynch final : public Tegra::GPU {
17public: 17public:
18 explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer); 18 explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer);
19 ~GPUSynch() override; 19 ~GPUSynch() override;
20 20
21 void Start() override; 21 void Start() override;
22 void PushGPUEntries(Tegra::CommandList&& entries) override; 22 void PushGPUEntries(Tegra::CommandList&& entries) override;
23 void SwapBuffers( 23 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
24 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
25 void FlushRegion(CacheAddr addr, u64 size) override; 24 void FlushRegion(CacheAddr addr, u64 size) override;
26 void InvalidateRegion(CacheAddr addr, u64 size) override; 25 void InvalidateRegion(CacheAddr addr, u64 size) override;
27 void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; 26 void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index b441e92b0..5f039e4fd 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -39,7 +39,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
39 dma_pusher.Push(std::move(submit_list->entries)); 39 dma_pusher.Push(std::move(submit_list->entries));
40 dma_pusher.DispatchCalls(); 40 dma_pusher.DispatchCalls();
41 } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { 41 } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
42 renderer.SwapBuffers(std::move(data->framebuffer)); 42 renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
43 } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { 43 } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
44 renderer.Rasterizer().FlushRegion(data->addr, data->size); 44 renderer.Rasterizer().FlushRegion(data->addr, data->size);
45 } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { 45 } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
@@ -78,9 +78,9 @@ void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
78 system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence); 78 system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence);
79} 79}
80 80
81void ThreadManager::SwapBuffers( 81void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
82 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { 82 PushCommand(SwapBuffersCommand(framebuffer ? *framebuffer
83 PushCommand(SwapBuffersCommand(std::move(framebuffer))); 83 : std::optional<const Tegra::FramebufferConfig>{}));
84} 84}
85 85
86void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { 86void ThreadManager::FlushRegion(CacheAddr addr, u64 size) {
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 1d9d0c39e..3ae0ec9f3 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -110,8 +110,7 @@ public:
110 void SubmitList(Tegra::CommandList&& entries); 110 void SubmitList(Tegra::CommandList&& entries);
111 111
112 /// Swap buffers (render frame) 112 /// Swap buffers (render frame)
113 void SwapBuffers( 113 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
114 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer);
115 114
116 /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory 115 /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
117 void FlushRegion(CacheAddr addr, u64 size); 116 void FlushRegion(CacheAddr addr, u64 size);
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index 3e91cbc83..084f85e67 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -25,8 +25,8 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
25 25
26 // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual 26 // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
27 // pixel values. 27 // pixel values.
28 const u32 tile_size_x{GetDefaultBlockWidth(format)}; 28 constexpr u32 tile_size_x{GetDefaultBlockWidth(format)};
29 const u32 tile_size_y{GetDefaultBlockHeight(format)}; 29 constexpr u32 tile_size_y{GetDefaultBlockHeight(format)};
30 30
31 if constexpr (morton_to_linear) { 31 if constexpr (morton_to_linear) {
32 Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, 32 Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel,
@@ -186,99 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
186 return morton_to_linear_fns[static_cast<std::size_t>(format)]; 186 return morton_to_linear_fns[static_cast<std::size_t>(format)];
187} 187}
188 188
189static u32 MortonInterleave128(u32 x, u32 y) {
190 // 128x128 Z-Order coordinate from 2D coordinates
191 static constexpr u32 xlut[] = {
192 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
193 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
194 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
195 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
196 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
197 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
198 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
199 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
200 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
201 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
202 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
203 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
204 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
205 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
206 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
207 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
208 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
209 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
210 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
211 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
212 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
213 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
214 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
215 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
216 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
217 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
218 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
219 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
220 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
221 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
222 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
223 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
224 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
225 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
226 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
227 };
228 static constexpr u32 ylut[] = {
229 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
230 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
231 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
232 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
233 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
234 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
235 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
236 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
237 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
238 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
239 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
240 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
241 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
242 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
243 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
244 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
245 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
246 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
247 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
248 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
249 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
250 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
251 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
252 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
253 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
254 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
255 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
256 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
257 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
258 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
259 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
260 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
261 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
262 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
263 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
264 };
265 return xlut[x % 128] + ylut[y % 128];
266}
267
268static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
269 // Calculates the offset of the position of the pixel in Morton order
270 // Framebuffer images are split into 128x128 tiles.
271
272 constexpr u32 block_height = 128;
273 const u32 coarse_x = x & ~127;
274
275 const u32 i = MortonInterleave128(x, y);
276
277 const u32 offset = coarse_x * block_height;
278
279 return (i + offset) * bytes_per_pixel;
280}
281
282void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, 189void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride,
283 u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, 190 u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
284 u8* buffer, u8* addr) { 191 u8* buffer, u8* addr) {
@@ -286,23 +193,4 @@ void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stri
286 tile_width_spacing, buffer, addr); 193 tile_width_spacing, buffer, addr);
287} 194}
288 195
289void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
290 u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) {
291 const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear;
292 u8* data_ptrs[2];
293 for (u32 y = 0; y < height; ++y) {
294 for (u32 x = 0; x < width; ++x) {
295 const u32 coarse_y = y & ~127;
296 const u32 morton_offset =
297 GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
298 const u32 linear_pixel_index = (x + y * width) * linear_bytes_per_pixel;
299
300 data_ptrs[morton_to_linear ? 1 : 0] = morton_data + morton_offset;
301 data_ptrs[morton_to_linear ? 0 : 1] = &linear_data[linear_pixel_index];
302
303 std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
304 }
305 }
306}
307
308} // namespace VideoCore 196} // namespace VideoCore
diff --git a/src/video_core/morton.h b/src/video_core/morton.h
index ee5b45555..b714a7e3f 100644
--- a/src/video_core/morton.h
+++ b/src/video_core/morton.h
@@ -15,7 +15,4 @@ void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat forma
15 u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, 15 u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
16 u8* buffer, u8* addr); 16 u8* buffer, u8* addr);
17 17
18void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
19 u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data);
20
21} // namespace VideoCore 18} // namespace VideoCore
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 1d54c3723..af1bebc4f 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -36,8 +36,7 @@ public:
36 virtual ~RendererBase(); 36 virtual ~RendererBase();
37 37
38 /// Swap buffers (render frame) 38 /// Swap buffers (render frame)
39 virtual void SwapBuffers( 39 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
40 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
41 40
42 /// Initialize the renderer 41 /// Initialize the renderer
43 virtual bool Init() = 0; 42 virtual bool Init() = 0;
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_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index bb09ecd52..01d89f47d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -537,8 +537,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
537 texture_cache.MarkDepthBufferInUse(); 537 texture_cache.MarkDepthBufferInUse();
538 538
539 fbkey.zeta = depth_surface; 539 fbkey.zeta = depth_surface;
540 fbkey.stencil_enable = regs.stencil_enable && 540 fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
541 depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
542 } 541 }
543 542
544 texture_cache.GuardRenderTargets(false); 543 texture_cache.GuardRenderTargets(false);
@@ -577,16 +576,15 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, boo
577 if (depth_surface) { 576 if (depth_surface) {
578 const auto& params = depth_surface->GetSurfaceParams(); 577 const auto& params = depth_surface->GetSurfaceParams();
579 switch (params.type) { 578 switch (params.type) {
580 case VideoCore::Surface::SurfaceType::Depth: { 579 case VideoCore::Surface::SurfaceType::Depth:
581 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); 580 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
582 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 581 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
583 break; 582 break;
584 } 583 case VideoCore::Surface::SurfaceType::DepthStencil:
585 case VideoCore::Surface::SurfaceType::DepthStencil: { 584 depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
586 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
587 break; 585 break;
588 } 586 default:
589 default: { UNIMPLEMENTED(); } 587 UNIMPLEMENTED();
590 } 588 }
591 } else { 589 } else {
592 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 590 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
@@ -639,6 +637,7 @@ void RasterizerOpenGL::Clear() {
639 ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); 637 ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!");
640 use_stencil = true; 638 use_stencil = true;
641 clear_state.stencil.test_enabled = true; 639 clear_state.stencil.test_enabled = true;
640
642 if (regs.clear_flags.stencil) { 641 if (regs.clear_flags.stencil) {
643 // Stencil affects the clear so fill it with the used masks 642 // Stencil affects the clear so fill it with the used masks
644 clear_state.stencil.front.test_func = GL_ALWAYS; 643 clear_state.stencil.front.test_func = GL_ALWAYS;
@@ -1119,9 +1118,12 @@ void RasterizerOpenGL::SyncStencilTestState() {
1119 if (!maxwell3d.dirty.stencil_test) { 1118 if (!maxwell3d.dirty.stencil_test) {
1120 return; 1119 return;
1121 } 1120 }
1122 const auto& regs = maxwell3d.regs; 1121 maxwell3d.dirty.stencil_test = false;
1123 1122
1123 const auto& regs = maxwell3d.regs;
1124 state.stencil.test_enabled = regs.stencil_enable != 0; 1124 state.stencil.test_enabled = regs.stencil_enable != 0;
1125 state.MarkDirtyStencilState();
1126
1125 if (!regs.stencil_enable) { 1127 if (!regs.stencil_enable) {
1126 return; 1128 return;
1127 } 1129 }
@@ -1150,8 +1152,6 @@ void RasterizerOpenGL::SyncStencilTestState() {
1150 state.stencil.back.action_depth_fail = GL_KEEP; 1152 state.stencil.back.action_depth_fail = GL_KEEP;
1151 state.stencil.back.action_depth_pass = GL_KEEP; 1153 state.stencil.back.action_depth_pass = GL_KEEP;
1152 } 1154 }
1153 state.MarkDirtyStencilState();
1154 maxwell3d.dirty.stencil_test = false;
1155} 1155}
1156 1156
1157void RasterizerOpenGL::SyncColorMask() { 1157void RasterizerOpenGL::SyncColorMask() {
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index cf6a5cddf..909ccb82c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -348,23 +348,16 @@ Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params,
348} 348}
349 349
350std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { 350std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) {
351 GLuint handle{}; 351 const auto [entry, is_cache_miss] = programs.try_emplace(variant);
352 if (program_type == ProgramType::Geometry) { 352 auto& program = entry->second;
353 handle = GetGeometryShader(variant); 353 if (is_cache_miss) {
354 } else { 354 program = TryLoadProgram(variant);
355 const auto [entry, is_cache_miss] = programs.try_emplace(variant); 355 if (!program) {
356 auto& program = entry->second; 356 program = SpecializeShader(code, entries, program_type, variant);
357 if (is_cache_miss) { 357 disk_cache.SaveUsage(GetUsage(variant));
358 program = TryLoadProgram(variant);
359 if (!program) {
360 program = SpecializeShader(code, entries, program_type, variant);
361 disk_cache.SaveUsage(GetUsage(variant));
362 }
363
364 LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
365 } 358 }
366 359
367 handle = program->handle; 360 LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
368 } 361 }
369 362
370 auto base_bindings = variant.base_bindings; 363 auto base_bindings = variant.base_bindings;
@@ -375,52 +368,9 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVar
375 base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); 368 base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size());
376 base_bindings.sampler += static_cast<u32>(entries.samplers.size()); 369 base_bindings.sampler += static_cast<u32>(entries.samplers.size());
377 370
378 return {handle, base_bindings}; 371 return {program->handle, base_bindings};
379} 372}
380 373
381GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) {
382 const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant);
383 auto& programs = entry->second;
384
385 switch (variant.primitive_mode) {
386 case GL_POINTS:
387 return LazyGeometryProgram(programs.points, variant);
388 case GL_LINES:
389 case GL_LINE_STRIP:
390 return LazyGeometryProgram(programs.lines, variant);
391 case GL_LINES_ADJACENCY:
392 case GL_LINE_STRIP_ADJACENCY:
393 return LazyGeometryProgram(programs.lines_adjacency, variant);
394 case GL_TRIANGLES:
395 case GL_TRIANGLE_STRIP:
396 case GL_TRIANGLE_FAN:
397 return LazyGeometryProgram(programs.triangles, variant);
398 case GL_TRIANGLES_ADJACENCY:
399 case GL_TRIANGLE_STRIP_ADJACENCY:
400 return LazyGeometryProgram(programs.triangles_adjacency, variant);
401 default:
402 UNREACHABLE_MSG("Unknown primitive mode.");
403 return LazyGeometryProgram(programs.points, variant);
404 }
405}
406
407GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program,
408 const ProgramVariant& variant) {
409 if (target_program) {
410 return target_program->handle;
411 }
412 const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode);
413 target_program = TryLoadProgram(variant);
414 if (!target_program) {
415 target_program = SpecializeShader(code, entries, program_type, variant);
416 disk_cache.SaveUsage(GetUsage(variant));
417 }
418
419 LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
420
421 return target_program->handle;
422};
423
424CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { 374CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const {
425 const auto found = precompiled_programs.find(GetUsage(variant)); 375 const auto found = precompiled_programs.find(GetUsage(variant));
426 if (found == precompiled_programs.end()) { 376 if (found == precompiled_programs.end()) {
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 2c8faf855..de195cc5d 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -86,22 +86,6 @@ private:
86 explicit CachedShader(const ShaderParameters& params, ProgramType program_type, 86 explicit CachedShader(const ShaderParameters& params, ProgramType program_type,
87 GLShader::ProgramResult result); 87 GLShader::ProgramResult result);
88 88
89 // Geometry programs. These are needed because GLSL needs an input topology but it's not
90 // declared by the hardware. Workaround this issue by generating a different shader per input
91 // topology class.
92 struct GeometryPrograms {
93 CachedProgram points;
94 CachedProgram lines;
95 CachedProgram lines_adjacency;
96 CachedProgram triangles;
97 CachedProgram triangles_adjacency;
98 };
99
100 GLuint GetGeometryShader(const ProgramVariant& variant);
101
102 /// Generates a geometry shader or returns one that already exists.
103 GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant);
104
105 CachedProgram TryLoadProgram(const ProgramVariant& variant) const; 89 CachedProgram TryLoadProgram(const ProgramVariant& variant) const;
106 90
107 ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; 91 ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const;
@@ -117,11 +101,6 @@ private:
117 std::size_t shader_length{}; 101 std::size_t shader_length{};
118 102
119 std::unordered_map<ProgramVariant, CachedProgram> programs; 103 std::unordered_map<ProgramVariant, CachedProgram> programs;
120 std::unordered_map<ProgramVariant, GeometryPrograms> geometry_programs;
121
122 std::unordered_map<u32, GLuint> cbuf_resource_cache;
123 std::unordered_map<u32, GLuint> gmem_resource_cache;
124 std::unordered_map<u32, GLint> uniform_cache;
125}; 104};
126 105
127class ShaderCacheOpenGL final : public RasterizerCache<Shader> { 106class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
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/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index a05cef3b9..af9684839 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -101,9 +101,7 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::Syst
101 101
102RendererOpenGL::~RendererOpenGL() = default; 102RendererOpenGL::~RendererOpenGL() = default;
103 103
104void RendererOpenGL::SwapBuffers( 104void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
105 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
106
107 system.GetPerfStats().EndSystemFrame(); 105 system.GetPerfStats().EndSystemFrame();
108 106
109 // Maintain the rasterizer's state as a priority 107 // Maintain the rasterizer's state as a priority
@@ -113,9 +111,9 @@ void RendererOpenGL::SwapBuffers(
113 111
114 if (framebuffer) { 112 if (framebuffer) {
115 // If framebuffer is provided, reload it from memory to a texture 113 // If framebuffer is provided, reload it from memory to a texture
116 if (screen_info.texture.width != (GLsizei)framebuffer->get().width || 114 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
117 screen_info.texture.height != (GLsizei)framebuffer->get().height || 115 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
118 screen_info.texture.pixel_format != framebuffer->get().pixel_format) { 116 screen_info.texture.pixel_format != framebuffer->pixel_format) {
119 // Reallocate texture if the framebuffer size has changed. 117 // Reallocate texture if the framebuffer size has changed.
120 // This is expected to not happen very often and hence should not be a 118 // This is expected to not happen very often and hence should not be a
121 // performance problem. 119 // performance problem.
@@ -149,43 +147,43 @@ void RendererOpenGL::SwapBuffers(
149 * Loads framebuffer from emulated memory into the active OpenGL texture. 147 * Loads framebuffer from emulated memory into the active OpenGL texture.
150 */ 148 */
151void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { 149void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
152 const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)};
153 const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
154 const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
155
156 // Framebuffer orientation handling 150 // Framebuffer orientation handling
157 framebuffer_transform_flags = framebuffer.transform_flags; 151 framebuffer_transform_flags = framebuffer.transform_flags;
158 framebuffer_crop_rect = framebuffer.crop_rect; 152 framebuffer_crop_rect = framebuffer.crop_rect;
159 153
160 // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default 154 const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
161 // only allows rows to have a memory alignement of 4. 155 if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) {
162 ASSERT(framebuffer.stride % 4 == 0); 156 return;
163 157 }
164 if (!rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) {
165 // Reset the screen info's display texture to its own permanent texture
166 screen_info.display_texture = screen_info.texture.resource.handle;
167
168 rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes);
169
170 constexpr u32 linear_bpp = 4;
171 VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear,
172 framebuffer.width, framebuffer.height, bytes_per_pixel,
173 linear_bpp, Memory::GetPointer(framebuffer_addr),
174 gl_framebuffer_data.data());
175
176 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
177 158
178 // Update existing texture 159 // Reset the screen info's display texture to its own permanent texture
179 // TODO: Test what happens on hardware when you change the framebuffer dimensions so that 160 screen_info.display_texture = screen_info.texture.resource.handle;
180 // they differ from the LCD resolution.
181 // TODO: Applications could theoretically crash yuzu here by specifying too large
182 // framebuffer sizes. We should make sure that this cannot happen.
183 glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width,
184 framebuffer.height, screen_info.texture.gl_format,
185 screen_info.texture.gl_type, gl_framebuffer_data.data());
186 161
187 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 162 const auto pixel_format{
188 } 163 VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
164 const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
165 const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
166 const auto host_ptr{Memory::GetPointer(framebuffer_addr)};
167 rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes);
168
169 // TODO(Rodrigo): Read this from HLE
170 constexpr u32 block_height_log2 = 4;
171 VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format,
172 framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1,
173 gl_framebuffer_data.data(), host_ptr);
174
175 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
176
177 // Update existing texture
178 // TODO: Test what happens on hardware when you change the framebuffer dimensions so that
179 // they differ from the LCD resolution.
180 // TODO: Applications could theoretically crash yuzu here by specifying too large
181 // framebuffer sizes. We should make sure that this cannot happen.
182 glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width,
183 framebuffer.height, screen_info.texture.gl_format,
184 screen_info.texture.gl_type, gl_framebuffer_data.data());
185
186 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
189} 187}
190 188
191/** 189/**
@@ -276,22 +274,29 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
276 texture.height = framebuffer.height; 274 texture.height = framebuffer.height;
277 texture.pixel_format = framebuffer.pixel_format; 275 texture.pixel_format = framebuffer.pixel_format;
278 276
277 const auto pixel_format{
278 VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
279 const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
280 gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel);
281
279 GLint internal_format; 282 GLint internal_format;
280 switch (framebuffer.pixel_format) { 283 switch (framebuffer.pixel_format) {
281 case Tegra::FramebufferConfig::PixelFormat::ABGR8: 284 case Tegra::FramebufferConfig::PixelFormat::ABGR8:
282 internal_format = GL_RGBA8; 285 internal_format = GL_RGBA8;
283 texture.gl_format = GL_RGBA; 286 texture.gl_format = GL_RGBA;
284 texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; 287 texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
285 gl_framebuffer_data.resize(texture.width * texture.height * 4); 288 break;
289 case Tegra::FramebufferConfig::PixelFormat::RGB565:
290 internal_format = GL_RGB565;
291 texture.gl_format = GL_RGB;
292 texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
286 break; 293 break;
287 default: 294 default:
288 internal_format = GL_RGBA8; 295 internal_format = GL_RGBA8;
289 texture.gl_format = GL_RGBA; 296 texture.gl_format = GL_RGBA;
290 texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; 297 texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
291 gl_framebuffer_data.resize(texture.width * texture.height * 4); 298 UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
292 LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}", 299 static_cast<u32>(framebuffer.pixel_format));
293 static_cast<u32>(framebuffer.pixel_format));
294 UNREACHABLE();
295 } 300 }
296 301
297 texture.resource.Release(); 302 texture.resource.Release();
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 4aebf2321..9bd086368 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -43,14 +43,13 @@ struct ScreenInfo {
43 TextureInfo texture; 43 TextureInfo texture;
44}; 44};
45 45
46class RendererOpenGL : public VideoCore::RendererBase { 46class RendererOpenGL final : public VideoCore::RendererBase {
47public: 47public:
48 explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); 48 explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system);
49 ~RendererOpenGL() override; 49 ~RendererOpenGL() override;
50 50
51 /// Swap buffers (render frame) 51 /// Swap buffers (render frame)
52 void SwapBuffers( 52 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
53 std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
54 53
55 /// Initialize the renderer 54 /// Initialize the renderer
56 bool Init() override; 55 bool Init() override;
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index 8973fbefa..32facd6ba 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -14,6 +14,12 @@ using Tegra::Shader::Instruction;
14using Tegra::Shader::OpCode; 14using Tegra::Shader::OpCode;
15using Tegra::Shader::Register; 15using Tegra::Shader::Register;
16 16
17namespace {
18constexpr OperationCode GetFloatSelector(u64 selector) {
19 return selector == 0 ? OperationCode::FCastHalf0 : OperationCode::FCastHalf1;
20}
21} // Anonymous namespace
22
17u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { 23u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
18 const Instruction instr = {program_code[pc]}; 24 const Instruction instr = {program_code[pc]};
19 const auto opcode = OpCode::Decode(instr); 25 const auto opcode = OpCode::Decode(instr);
@@ -22,7 +28,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
22 case OpCode::Id::I2I_R: 28 case OpCode::Id::I2I_R:
23 case OpCode::Id::I2I_C: 29 case OpCode::Id::I2I_C:
24 case OpCode::Id::I2I_IMM: { 30 case OpCode::Id::I2I_IMM: {
25 UNIMPLEMENTED_IF(instr.conversion.selector); 31 UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0);
26 UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word); 32 UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word);
27 UNIMPLEMENTED_IF(instr.alu.saturate_d); 33 UNIMPLEMENTED_IF(instr.alu.saturate_d);
28 34
@@ -57,8 +63,8 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
57 case OpCode::Id::I2F_R: 63 case OpCode::Id::I2F_R:
58 case OpCode::Id::I2F_C: 64 case OpCode::Id::I2F_C:
59 case OpCode::Id::I2F_IMM: { 65 case OpCode::Id::I2F_IMM: {
66 UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0);
60 UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long); 67 UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long);
61 UNIMPLEMENTED_IF(instr.conversion.selector);
62 UNIMPLEMENTED_IF_MSG(instr.generates_cc, 68 UNIMPLEMENTED_IF_MSG(instr.generates_cc,
63 "Condition codes generation in I2F is not implemented"); 69 "Condition codes generation in I2F is not implemented");
64 70
@@ -113,8 +119,10 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
113 }(); 119 }();
114 120
115 if (instr.conversion.src_size == Register::Size::Short) { 121 if (instr.conversion.src_size == Register::Size::Short) {
116 // TODO: figure where extract is sey in the encoding 122 value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE,
117 value = Operation(OperationCode::FCastHalf0, PRECISE, value); 123 std::move(value));
124 } else {
125 ASSERT(instr.conversion.float_src.selector == 0);
118 } 126 }
119 127
120 value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); 128 value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a);
@@ -169,8 +177,10 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
169 }(); 177 }();
170 178
171 if (instr.conversion.src_size == Register::Size::Short) { 179 if (instr.conversion.src_size == Register::Size::Short) {
172 // TODO: figure where extract is sey in the encoding 180 value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE,
173 value = Operation(OperationCode::FCastHalf0, PRECISE, value); 181 std::move(value));
182 } else {
183 ASSERT(instr.conversion.float_src.selector == 0);
174 } 184 }
175 185
176 value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); 186 value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a);
diff --git a/src/video_core/shader/decode/float_set_predicate.cpp b/src/video_core/shader/decode/float_set_predicate.cpp
index 34854fcca..200c2c983 100644
--- a/src/video_core/shader/decode/float_set_predicate.cpp
+++ b/src/video_core/shader/decode/float_set_predicate.cpp
@@ -17,8 +17,8 @@ using Tegra::Shader::Pred;
17u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { 17u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) {
18 const Instruction instr = {program_code[pc]}; 18 const Instruction instr = {program_code[pc]};
19 19
20 const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0, 20 Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0,
21 instr.fsetp.neg_a != 0); 21 instr.fsetp.neg_a != 0);
22 Node op_b = [&]() { 22 Node op_b = [&]() {
23 if (instr.is_b_imm) { 23 if (instr.is_b_imm) {
24 return GetImmediate19(instr); 24 return GetImmediate19(instr);
@@ -28,12 +28,13 @@ u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) {
28 return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()); 28 return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
29 } 29 }
30 }(); 30 }();
31 op_b = GetOperandAbsNegFloat(op_b, instr.fsetp.abs_b, false); 31 op_b = GetOperandAbsNegFloat(std::move(op_b), instr.fsetp.abs_b, instr.fsetp.neg_b);
32 32
33 // We can't use the constant predicate as destination. 33 // We can't use the constant predicate as destination.
34 ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); 34 ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
35 35
36 const Node predicate = GetPredicateComparisonFloat(instr.fsetp.cond, op_a, op_b); 36 const Node predicate =
37 GetPredicateComparisonFloat(instr.fsetp.cond, std::move(op_a), std::move(op_b));
37 const Node second_pred = GetPredicate(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); 38 const Node second_pred = GetPredicate(instr.fsetp.pred39, instr.fsetp.neg_pred != 0);
38 39
39 const OperationCode combiner = GetPredicateCombiner(instr.fsetp.op); 40 const OperationCode combiner = GetPredicateCombiner(instr.fsetp.op);
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index afea33e5f..840694527 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -42,9 +42,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
42 cond = instr.hsetp2.reg.cond; 42 cond = instr.hsetp2.reg.cond;
43 h_and = instr.hsetp2.reg.h_and; 43 h_and = instr.hsetp2.reg.h_and;
44 op_b = 44 op_b =
45 UnpackHalfFloat(GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.reg.abs_b, 45 GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b),
46 instr.hsetp2.reg.negate_b), 46 instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b);
47 instr.hsetp2.reg.type_b);
48 break; 47 break;
49 default: 48 default:
50 UNREACHABLE(); 49 UNREACHABLE();
@@ -52,22 +51,22 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
52 } 51 }
53 52
54 const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); 53 const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op);
55 const Node combined_pred = GetPredicate(instr.hsetp2.pred3, instr.hsetp2.neg_pred); 54 const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred);
56 55
57 const auto Write = [&](u64 dest, Node src) { 56 const auto Write = [&](u64 dest, Node src) {
58 SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); 57 SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred));
59 }; 58 };
60 59
61 const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); 60 const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b);
62 const u64 first = instr.hsetp2.pred0; 61 const u64 first = instr.hsetp2.pred3;
63 const u64 second = instr.hsetp2.pred39; 62 const u64 second = instr.hsetp2.pred0;
64 if (h_and) { 63 if (h_and) {
65 const Node joined = Operation(OperationCode::LogicalAnd2, comparison); 64 Node joined = Operation(OperationCode::LogicalAnd2, comparison);
66 Write(first, joined); 65 Write(first, joined);
67 Write(second, Operation(OperationCode::LogicalNegate, joined)); 66 Write(second, Operation(OperationCode::LogicalNegate, std::move(joined)));
68 } else { 67 } else {
69 Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0u))); 68 Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U)));
70 Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1u))); 69 Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U)));
71 } 70 }
72 71
73 return pc; 72 return pc;
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index c50f6354d..4ceb219be 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -445,11 +445,12 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat
445 switch (format) { 445 switch (format) {
446 case Tegra::FramebufferConfig::PixelFormat::ABGR8: 446 case Tegra::FramebufferConfig::PixelFormat::ABGR8:
447 return PixelFormat::ABGR8U; 447 return PixelFormat::ABGR8U;
448 case Tegra::FramebufferConfig::PixelFormat::RGB565:
449 return PixelFormat::B5G6R5U;
448 case Tegra::FramebufferConfig::PixelFormat::BGRA8: 450 case Tegra::FramebufferConfig::PixelFormat::BGRA8:
449 return PixelFormat::BGRA8; 451 return PixelFormat::BGRA8;
450 default: 452 default:
451 LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); 453 UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
452 UNREACHABLE();
453 return PixelFormat::ABGR8U; 454 return PixelFormat::ABGR8U;
454 } 455 }
455} 456}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 5d0fb3f9f..f594106bf 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -516,10 +516,38 @@ void Config::ReadPathValues() {
516 516
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.game_directory_path = 519 UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
520 UISettings::values.game_dir_deprecated =
520 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); 521 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
521 UISettings::values.game_directory_deepscan = 522 UISettings::values.game_dir_deprecated_deepscan =
522 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 }
523 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); 551 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
524 552
525 qt_config->endGroup(); 553 qt_config->endGroup();
@@ -898,10 +926,15 @@ void Config::SavePathValues() {
898 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); 926 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
899 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); 927 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
900 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); 928 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
901 WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, 929 qt_config->beginWriteArray(QStringLiteral("gamedirs"));
902 QStringLiteral(".")); 930 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
903 WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, 931 qt_config->setArrayIndex(i);
904 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();
905 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); 938 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
906 939
907 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 a7c656fdb..8304c6517 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -6,6 +6,9 @@
6#include <clocale> 6#include <clocale>
7#include <memory> 7#include <memory>
8#include <thread> 8#include <thread>
9#ifdef __APPLE__
10#include <unistd.h> // for chdir
11#endif
9 12
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 13// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "applets/error.h" 14#include "applets/error.h"
@@ -119,6 +122,7 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
119#endif 122#endif
120 123
121#ifdef _WIN32 124#ifdef _WIN32
125#include <windows.h>
122extern "C" { 126extern "C" {
123// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable 127// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
124// graphics 128// graphics
@@ -215,8 +219,7 @@ GMainWindow::GMainWindow()
215 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 219 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
216 220
217 game_list->LoadCompatibilityList(); 221 game_list->LoadCompatibilityList();
218 game_list->PopulateAsync(UISettings::values.game_directory_path, 222 game_list->PopulateAsync(UISettings::values.game_dirs);
219 UISettings::values.game_directory_deepscan);
220 223
221 // Show one-time "callout" messages to the user 224 // Show one-time "callout" messages to the user
222 ShowTelemetryCallout(); 225 ShowTelemetryCallout();
@@ -426,6 +429,10 @@ void GMainWindow::InitializeWidgets() {
426 game_list = new GameList(vfs, provider.get(), this); 429 game_list = new GameList(vfs, provider.get(), this);
427 ui.horizontalLayout->addWidget(game_list); 430 ui.horizontalLayout->addWidget(game_list);
428 431
432 game_list_placeholder = new GameListPlaceholder(this);
433 ui.horizontalLayout->addWidget(game_list_placeholder);
434 game_list_placeholder->setVisible(false);
435
429 loading_screen = new LoadingScreen(this); 436 loading_screen = new LoadingScreen(this);
430 loading_screen->hide(); 437 loading_screen->hide();
431 ui.horizontalLayout->addWidget(loading_screen); 438 ui.horizontalLayout->addWidget(loading_screen);
@@ -659,6 +666,7 @@ void GMainWindow::RestoreUIState() {
659 666
660void GMainWindow::ConnectWidgetEvents() { 667void GMainWindow::ConnectWidgetEvents() {
661 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 668 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
669 connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
662 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); 670 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
663 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, 671 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
664 &GMainWindow::OnTransferableShaderCacheOpenFile); 672 &GMainWindow::OnTransferableShaderCacheOpenFile);
@@ -666,6 +674,11 @@ void GMainWindow::ConnectWidgetEvents() {
666 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 674 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
667 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 675 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
668 &GMainWindow::OnGameListNavigateToGamedbEntry); 676 &GMainWindow::OnGameListNavigateToGamedbEntry);
677 connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
678 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
679 &GMainWindow::OnGameListAddDirectory);
680 connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
681
669 connect(game_list, &GameList::OpenPerGameGeneralRequested, this, 682 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
670 &GMainWindow::OnGameListOpenPerGameProperties); 683 &GMainWindow::OnGameListOpenPerGameProperties);
671 684
@@ -683,8 +696,6 @@ void GMainWindow::ConnectMenuEvents() {
683 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); 696 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
684 connect(ui.action_Install_File_NAND, &QAction::triggered, this, 697 connect(ui.action_Install_File_NAND, &QAction::triggered, this,
685 &GMainWindow::OnMenuInstallToNAND); 698 &GMainWindow::OnMenuInstallToNAND);
686 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
687 &GMainWindow::OnMenuSelectGameListRoot);
688 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, 699 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
689 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); 700 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
690 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, 701 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
@@ -747,6 +758,18 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
747 } 758 }
748} 759}
749 760
761void GMainWindow::PreventOSSleep() {
762#ifdef _WIN32
763 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
764#endif
765}
766
767void GMainWindow::AllowOSSleep() {
768#ifdef _WIN32
769 SetThreadExecutionState(ES_CONTINUOUS);
770#endif
771}
772
750QStringList GMainWindow::GetUnsupportedGLExtensions() { 773QStringList GMainWindow::GetUnsupportedGLExtensions() {
751 QStringList unsupported_ext; 774 QStringList unsupported_ext;
752 775
@@ -937,6 +960,7 @@ void GMainWindow::BootGame(const QString& filename) {
937 // Update the GUI 960 // Update the GUI
938 if (ui.action_Single_Window_Mode->isChecked()) { 961 if (ui.action_Single_Window_Mode->isChecked()) {
939 game_list->hide(); 962 game_list->hide();
963 game_list_placeholder->hide();
940 } 964 }
941 status_bar_update_timer.start(2000); 965 status_bar_update_timer.start(2000);
942 966
@@ -966,6 +990,8 @@ void GMainWindow::BootGame(const QString& filename) {
966} 990}
967 991
968void GMainWindow::ShutdownGame() { 992void GMainWindow::ShutdownGame() {
993 AllowOSSleep();
994
969 discord_rpc->Pause(); 995 discord_rpc->Pause();
970 emu_thread->RequestStop(); 996 emu_thread->RequestStop();
971 997
@@ -992,7 +1018,10 @@ void GMainWindow::ShutdownGame() {
992 render_window->hide(); 1018 render_window->hide();
993 loading_screen->hide(); 1019 loading_screen->hide();
994 loading_screen->Clear(); 1020 loading_screen->Clear();
995 game_list->show(); 1021 if (game_list->isEmpty())
1022 game_list_placeholder->show();
1023 else
1024 game_list->show();
996 game_list->setFilterFocus(); 1025 game_list->setFilterFocus();
997 1026
998 UpdateWindowTitle(); 1027 UpdateWindowTitle();
@@ -1283,6 +1312,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1283 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); 1312 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
1284} 1313}
1285 1314
1315void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1316 QString path;
1317 if (directory == QStringLiteral("SDMC")) {
1318 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
1319 "Nintendo/Contents/registered");
1320 } else if (directory == QStringLiteral("UserNAND")) {
1321 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1322 "user/Contents/registered");
1323 } else if (directory == QStringLiteral("SysNAND")) {
1324 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1325 "system/Contents/registered");
1326 } else {
1327 path = directory;
1328 }
1329 if (!QFileInfo::exists(path)) {
1330 QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!"));
1331 return;
1332 }
1333 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1334}
1335
1336void GMainWindow::OnGameListAddDirectory() {
1337 const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1338 if (dir_path.isEmpty())
1339 return;
1340 UISettings::GameDir game_dir{dir_path, false, true};
1341 if (!UISettings::values.game_dirs.contains(game_dir)) {
1342 UISettings::values.game_dirs.append(game_dir);
1343 game_list->PopulateAsync(UISettings::values.game_dirs);
1344 } else {
1345 LOG_WARNING(Frontend, "Selected directory is already in the game list");
1346 }
1347}
1348
1349void GMainWindow::OnGameListShowList(bool show) {
1350 if (emulation_running && ui.action_Single_Window_Mode->isChecked())
1351 return;
1352 game_list->setVisible(show);
1353 game_list_placeholder->setVisible(!show);
1354};
1355
1286void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { 1356void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1287 u64 title_id{}; 1357 u64 title_id{};
1288 const auto v_file = Core::GetGameFileFromPath(vfs, file); 1358 const auto v_file = Core::GetGameFileFromPath(vfs, file);
@@ -1301,8 +1371,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1301 1371
1302 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1372 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1303 if (reload) { 1373 if (reload) {
1304 game_list->PopulateAsync(UISettings::values.game_directory_path, 1374 game_list->PopulateAsync(UISettings::values.game_dirs);
1305 UISettings::values.game_directory_deepscan);
1306 } 1375 }
1307 1376
1308 config->Save(); 1377 config->Save();
@@ -1392,8 +1461,7 @@ void GMainWindow::OnMenuInstallToNAND() {
1392 const auto success = [this]() { 1461 const auto success = [this]() {
1393 QMessageBox::information(this, tr("Successfully Installed"), 1462 QMessageBox::information(this, tr("Successfully Installed"),
1394 tr("The file was successfully installed.")); 1463 tr("The file was successfully installed."));
1395 game_list->PopulateAsync(UISettings::values.game_directory_path, 1464 game_list->PopulateAsync(UISettings::values.game_dirs);
1396 UISettings::values.game_directory_deepscan);
1397 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + 1465 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
1398 DIR_SEP + "game_list"); 1466 DIR_SEP + "game_list");
1399 }; 1467 };
@@ -1518,14 +1586,6 @@ void GMainWindow::OnMenuInstallToNAND() {
1518 } 1586 }
1519} 1587}
1520 1588
1521void GMainWindow::OnMenuSelectGameListRoot() {
1522 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1523 if (!dir_path.isEmpty()) {
1524 UISettings::values.game_directory_path = dir_path;
1525 game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
1526 }
1527}
1528
1529void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { 1589void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
1530 const auto res = QMessageBox::information( 1590 const auto res = QMessageBox::information(
1531 this, tr("Changing Emulated Directory"), 1591 this, tr("Changing Emulated Directory"),
@@ -1544,8 +1604,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
1544 : FileUtil::UserPath::NANDDir, 1604 : FileUtil::UserPath::NANDDir,
1545 dir_path.toStdString()); 1605 dir_path.toStdString());
1546 Service::FileSystem::CreateFactories(*vfs); 1606 Service::FileSystem::CreateFactories(*vfs);
1547 game_list->PopulateAsync(UISettings::values.game_directory_path, 1607 game_list->PopulateAsync(UISettings::values.game_dirs);
1548 UISettings::values.game_directory_deepscan);
1549 } 1608 }
1550} 1609}
1551 1610
@@ -1567,6 +1626,8 @@ void GMainWindow::OnMenuRecentFile() {
1567} 1626}
1568 1627
1569void GMainWindow::OnStartGame() { 1628void GMainWindow::OnStartGame() {
1629 PreventOSSleep();
1630
1570 emu_thread->SetRunning(true); 1631 emu_thread->SetRunning(true);
1571 1632
1572 qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( 1633 qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
@@ -1598,6 +1659,8 @@ void GMainWindow::OnPauseGame() {
1598 ui.action_Pause->setEnabled(false); 1659 ui.action_Pause->setEnabled(false);
1599 ui.action_Stop->setEnabled(true); 1660 ui.action_Stop->setEnabled(true);
1600 ui.action_Capture_Screenshot->setEnabled(false); 1661 ui.action_Capture_Screenshot->setEnabled(false);
1662
1663 AllowOSSleep();
1601} 1664}
1602 1665
1603void GMainWindow::OnStopGame() { 1666void GMainWindow::OnStopGame() {
@@ -1705,11 +1768,11 @@ void GMainWindow::OnConfigure() {
1705 if (UISettings::values.enable_discord_presence != old_discord_presence) { 1768 if (UISettings::values.enable_discord_presence != old_discord_presence) {
1706 SetDiscordEnabled(UISettings::values.enable_discord_presence); 1769 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1707 } 1770 }
1771 emit UpdateThemedIcons();
1708 1772
1709 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1773 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1710 if (reload) { 1774 if (reload) {
1711 game_list->PopulateAsync(UISettings::values.game_directory_path, 1775 game_list->PopulateAsync(UISettings::values.game_dirs);
1712 UISettings::values.game_directory_deepscan);
1713 } 1776 }
1714 1777
1715 config->Save(); 1778 config->Save();
@@ -1973,8 +2036,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1973 Service::FileSystem::CreateFactories(*vfs); 2036 Service::FileSystem::CreateFactories(*vfs);
1974 2037
1975 if (behavior == ReinitializeKeyBehavior::Warning) { 2038 if (behavior == ReinitializeKeyBehavior::Warning) {
1976 game_list->PopulateAsync(UISettings::values.game_directory_path, 2039 game_list->PopulateAsync(UISettings::values.game_dirs);
1977 UISettings::values.game_directory_deepscan);
1978 } 2040 }
1979} 2041}
1980 2042
@@ -2139,7 +2201,6 @@ void GMainWindow::UpdateUITheme() {
2139 } 2201 }
2140 2202
2141 QIcon::setThemeSearchPaths(theme_paths); 2203 QIcon::setThemeSearchPaths(theme_paths);
2142 emit UpdateThemedIcons();
2143} 2204}
2144 2205
2145void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { 2206void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
@@ -2168,6 +2229,14 @@ int main(int argc, char* argv[]) {
2168 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); 2229 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
2169 QCoreApplication::setApplicationName(QStringLiteral("yuzu")); 2230 QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
2170 2231
2232#ifdef __APPLE__
2233 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
2234 // But since we require the working directory to be the executable path for the location of the
2235 // user folder in the Qt Frontend, we need to cd into that working directory
2236 const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
2237 chdir(bin_path.c_str());
2238#endif
2239
2171 // Enables the core to make the qt created contexts current on std::threads 2240 // Enables the core to make the qt created contexts current on std::threads
2172 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 2241 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
2173 QApplication app(argc, argv); 2242 QApplication app(argc, argv);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 1137bbc7a..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;
@@ -130,6 +131,9 @@ private:
130 void ConnectWidgetEvents(); 131 void ConnectWidgetEvents();
131 void ConnectMenuEvents(); 132 void ConnectMenuEvents();
132 133
134 void PreventOSSleep();
135 void AllowOSSleep();
136
133 QStringList GetUnsupportedGLExtensions(); 137 QStringList GetUnsupportedGLExtensions();
134 bool LoadROM(const QString& filename); 138 bool LoadROM(const QString& filename);
135 void BootGame(const QString& filename); 139 void BootGame(const QString& filename);
@@ -183,12 +187,13 @@ private slots:
183 void OnGameListCopyTID(u64 program_id); 187 void OnGameListCopyTID(u64 program_id);
184 void OnGameListNavigateToGamedbEntry(u64 program_id, 188 void OnGameListNavigateToGamedbEntry(u64 program_id,
185 const CompatibilityList& compatibility_list); 189 const CompatibilityList& compatibility_list);
190 void OnGameListOpenDirectory(const QString& directory);
191 void OnGameListAddDirectory();
192 void OnGameListShowList(bool show);
186 void OnGameListOpenPerGameProperties(const std::string& file); 193 void OnGameListOpenPerGameProperties(const std::string& file);
187 void OnMenuLoadFile(); 194 void OnMenuLoadFile();
188 void OnMenuLoadFolder(); 195 void OnMenuLoadFolder();
189 void OnMenuInstallToNAND(); 196 void OnMenuInstallToNAND();
190 /// Called whenever a user selects the "File->Select Game List Root" menu item
191 void OnMenuSelectGameListRoot();
192 /// 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
193 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); 198 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
194 void OnMenuRecentFile(); 199 void OnMenuRecentFile();
@@ -220,6 +225,8 @@ private:
220 GameList* game_list; 225 GameList* game_list;
221 LoadingScreen* loading_screen; 226 LoadingScreen* loading_screen;
222 227
228 GameListPlaceholder* game_list_placeholder;
229
223 // Status bar elements 230 // Status bar elements
224 QLabel* message_label = nullptr; 231 QLabel* message_label = nullptr;
225 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*);