diff options
Diffstat (limited to 'src')
35 files changed, 1989 insertions, 743 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5462decee..877a9e353 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -70,6 +70,8 @@ add_library(core STATIC | |||
| 70 | file_sys/sdmc_factory.h | 70 | file_sys/sdmc_factory.h |
| 71 | file_sys/submission_package.cpp | 71 | file_sys/submission_package.cpp |
| 72 | file_sys/submission_package.h | 72 | file_sys/submission_package.h |
| 73 | file_sys/system_archive/mii_model.cpp | ||
| 74 | file_sys/system_archive/mii_model.h | ||
| 73 | file_sys/system_archive/ng_word.cpp | 75 | file_sys/system_archive/ng_word.cpp |
| 74 | file_sys/system_archive/ng_word.h | 76 | file_sys/system_archive/ng_word.h |
| 75 | file_sys/system_archive/system_archive.cpp | 77 | file_sys/system_archive/system_archive.cpp |
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 6dd633363..46aceec3d 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -37,6 +37,7 @@ | |||
| 37 | namespace Core::Crypto { | 37 | namespace Core::Crypto { |
| 38 | 38 | ||
| 39 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 39 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 40 | constexpr u64 FULL_TICKET_SIZE = 0x400; | ||
| 40 | 41 | ||
| 41 | using namespace Common; | 42 | using 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 | ||
| 59 | namespace { | ||
| 60 | template <std::size_t Size> | ||
| 61 | bool 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 | |||
| 66 | u64 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 | |||
| 81 | u64 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 | |||
| 95 | SignatureType 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 | |||
| 109 | TicketData& 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 | |||
| 123 | const 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 | |||
| 137 | u64 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 | |||
| 144 | Ticket 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 | |||
| 58 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 152 | Key128 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 | ||
| 232 | RSAKeyPair<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 | |||
| 138 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { | 253 | Key128 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 | ||
| 240 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { | 355 | std::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 | ||
| 308 | std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | 423 | std::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); | 982 | void 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 | ||
| 1022 | void 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 | |||
| 902 | void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { | 1035 | void 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 | ||
| 1133 | const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const { | ||
| 1134 | return common_tickets; | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { | ||
| 1138 | return personal_tickets; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | bool 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 | |||
| 1157 | bool 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 | |||
| 1000 | const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { | 1173 | const 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; | |||
| 30 | using Key128 = std::array<u8, 0x10>; | 32 | using Key128 = std::array<u8, 0x10>; |
| 31 | using Key256 = std::array<u8, 0x20>; | 33 | using Key256 = std::array<u8, 0x20>; |
| 32 | using SHA256Hash = std::array<u8, 0x20>; | 34 | using SHA256Hash = std::array<u8, 0x20>; |
| 33 | using TicketRaw = std::array<u8, 0x400>; | 35 | |
| 36 | enum 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 | |||
| 45 | u64 GetSignatureTypeDataSize(SignatureType type); | ||
| 46 | u64 GetSignatureTypePaddingSize(SignatureType type); | ||
| 47 | |||
| 48 | enum class TitleKeyType : u8 { | ||
| 49 | Common = 0, | ||
| 50 | Personalized = 1, | ||
| 51 | }; | ||
| 52 | |||
| 53 | struct 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 | }; | ||
| 75 | static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); | ||
| 76 | |||
| 77 | struct RSA4096Ticket { | ||
| 78 | SignatureType sig_type; | ||
| 79 | std::array<u8, 0x200> sig_data; | ||
| 80 | INSERT_PADDING_BYTES(0x3C); | ||
| 81 | TicketData data; | ||
| 82 | }; | ||
| 83 | |||
| 84 | struct RSA2048Ticket { | ||
| 85 | SignatureType sig_type; | ||
| 86 | std::array<u8, 0x100> sig_data; | ||
| 87 | INSERT_PADDING_BYTES(0x3C); | ||
| 88 | TicketData data; | ||
| 89 | }; | ||
| 90 | |||
| 91 | struct ECDSATicket { | ||
| 92 | SignatureType sig_type; | ||
| 93 | std::array<u8, 0x3C> sig_data; | ||
| 94 | INSERT_PADDING_BYTES(0x40); | ||
| 95 | TicketData data; | ||
| 96 | }; | ||
| 97 | |||
| 98 | struct 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 | ||
| 35 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 109 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| 36 | static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); | 110 | static_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 | ||
| 120 | template <size_t bit_size, size_t byte_size> | ||
| 121 | bool 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 | |||
| 127 | template <size_t bit_size, size_t byte_size> | ||
| 128 | bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs, | ||
| 129 | const RSAKeyPair<bit_size, byte_size>& rhs) { | ||
| 130 | return !(lhs == rhs); | ||
| 131 | } | ||
| 132 | |||
| 46 | enum class KeyCategory : u8 { | 133 | enum 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 | |||
| 164 | private: | 259 | private: |
| 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 | |||
| 195 | std::optional<Key128> DeriveSDSeed(); | 297 | std::optional<Key128> DeriveSDSeed(); |
| 196 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); | 298 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); |
| 197 | 299 | ||
| 198 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); | 300 | std::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) |
| 202 | std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | 304 | std::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 | |||
| 8 | namespace FileSys::SystemArchive { | ||
| 9 | |||
| 10 | namespace MiiModelData { | ||
| 11 | |||
| 12 | constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00, | ||
| 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| 14 | constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00, | ||
| 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| 16 | |||
| 17 | constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD; | ||
| 18 | constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD; | ||
| 19 | constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD; | ||
| 20 | constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD; | ||
| 21 | constexpr auto SHAPE_HIGH = NFSR_STANDARD; | ||
| 22 | constexpr auto SHAPE_MID = NFSR_STANDARD; | ||
| 23 | |||
| 24 | } // namespace MiiModelData | ||
| 25 | |||
| 26 | VirtualDir 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 | |||
| 9 | namespace FileSys::SystemArchive { | ||
| 10 | |||
| 11 | VirtualDir 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 { | |||
| 24 | constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ | 25 | constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ |
| 25 | {0x0100000000000800, "CertStore", nullptr}, | 26 | {0x0100000000000800, "CertStore", nullptr}, |
| 26 | {0x0100000000000801, "ErrorMessage", nullptr}, | 27 | {0x0100000000000801, "ErrorMessage", nullptr}, |
| 27 | {0x0100000000000802, "MiiModel", nullptr}, | 28 | {0x0100000000000802, "MiiModel", &MiiModel}, |
| 28 | {0x0100000000000803, "BrowserDll", nullptr}, | 29 | {0x0100000000000803, "BrowserDll", nullptr}, |
| 29 | {0x0100000000000804, "Help", nullptr}, | 30 | {0x0100000000000804, "Help", nullptr}, |
| 30 | {0x0100000000000805, "SharedFont", nullptr}, | 31 | {0x0100000000000805, "SharedFont", nullptr}, |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index a192a1f5f..111633ba3 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -1057,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF | |||
| 1057 | {120, nullptr, "ExecuteProgram"}, | 1057 | {120, nullptr, "ExecuteProgram"}, |
| 1058 | {121, nullptr, "ClearUserChannel"}, | 1058 | {121, nullptr, "ClearUserChannel"}, |
| 1059 | {122, nullptr, "UnpopToUserChannel"}, | 1059 | {122, nullptr, "UnpopToUserChannel"}, |
| 1060 | {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, | ||
| 1060 | {500, nullptr, "StartContinuousRecordingFlushForDebug"}, | 1061 | {500, nullptr, "StartContinuousRecordingFlushForDebug"}, |
| 1061 | {1000, nullptr, "CreateMovieMaker"}, | 1062 | {1000, nullptr, "CreateMovieMaker"}, |
| 1062 | {1001, nullptr, "PrepareForJit"}, | 1063 | {1001, nullptr, "PrepareForJit"}, |
| @@ -1064,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF | |||
| 1064 | // clang-format on | 1065 | // clang-format on |
| 1065 | 1066 | ||
| 1066 | RegisterHandlers(functions); | 1067 | RegisterHandlers(functions); |
| 1068 | |||
| 1069 | auto& kernel = Core::System::GetInstance().Kernel(); | ||
| 1070 | gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair( | ||
| 1071 | kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); | ||
| 1067 | } | 1072 | } |
| 1068 | 1073 | ||
| 1069 | IApplicationFunctions::~IApplicationFunctions() = default; | 1074 | IApplicationFunctions::~IApplicationFunctions() = default; |
| @@ -1285,6 +1290,14 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { | |||
| 1285 | rb.Push(size.journal); | 1290 | rb.Push(size.journal); |
| 1286 | } | 1291 | } |
| 1287 | 1292 | ||
| 1293 | void 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 | |||
| 1288 | void InstallInterfaces(SM::ServiceManager& service_manager, | 1301 | void InstallInterfaces(SM::ServiceManager& service_manager, |
| 1289 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { | 1302 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { |
| 1290 | auto message_queue = std::make_shared<AppletMessageQueue>(); | 1303 | auto message_queue = std::make_shared<AppletMessageQueue>(); |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6cb582483..cbc9da7b6 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -242,6 +242,9 @@ private: | |||
| 242 | void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); | 242 | void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); |
| 243 | void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); | 243 | void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); |
| 244 | void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); | 244 | void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); |
| 245 | void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); | ||
| 246 | |||
| 247 | Kernel::EventPair gpu_error_detected_event; | ||
| 245 | }; | 248 | }; |
| 246 | 249 | ||
| 247 | class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { | 250 | class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { |
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 5b0b7f17e..f162249ed 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -165,15 +165,15 @@ public: | |||
| 165 | static const FunctionInfo functions[] = { | 165 | static const FunctionInfo functions[] = { |
| 166 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, | 166 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, |
| 167 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, | 167 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, |
| 168 | {2, nullptr, "GetAudioDeviceOutputVolume"}, | 168 | {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"}, |
| 169 | {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, | 169 | {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, |
| 170 | {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, | 170 | {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, |
| 171 | {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, | 171 | {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, |
| 172 | {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, | 172 | {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, |
| 173 | {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, | 173 | {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, |
| 174 | {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, | 174 | {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"}, |
| 175 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, | 175 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, |
| 176 | {11, nullptr, "QueryAudioDeviceInputEvent"}, | 176 | {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, |
| 177 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, | 177 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, |
| 178 | {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, | 178 | {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, |
| 179 | }; | 179 | }; |
| @@ -183,6 +183,10 @@ public: | |||
| 183 | buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, | 183 | buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, |
| 184 | "IAudioOutBufferReleasedEvent"); | 184 | "IAudioOutBufferReleasedEvent"); |
| 185 | 185 | ||
| 186 | // Should be similar to audio_output_device_switch_event | ||
| 187 | audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair( | ||
| 188 | kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent"); | ||
| 189 | |||
| 186 | // Should only be signalled when an audio output device has been changed, example: speaker | 190 | // Should only be signalled when an audio output device has been changed, example: speaker |
| 187 | // to headset | 191 | // to headset |
| 188 | audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( | 192 | audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( |
| @@ -246,6 +250,19 @@ private: | |||
| 246 | rb.Push(RESULT_SUCCESS); | 250 | rb.Push(RESULT_SUCCESS); |
| 247 | } | 251 | } |
| 248 | 252 | ||
| 253 | void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { | ||
| 254 | IPC::RequestParser rp{ctx}; | ||
| 255 | |||
| 256 | const auto device_name_buffer = ctx.ReadBuffer(); | ||
| 257 | const std::string name = Common::StringFromBuffer(device_name_buffer); | ||
| 258 | |||
| 259 | LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); | ||
| 260 | |||
| 261 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 262 | rb.Push(RESULT_SUCCESS); | ||
| 263 | rb.Push(1.0f); | ||
| 264 | } | ||
| 265 | |||
| 249 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { | 266 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { |
| 250 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 267 | LOG_WARNING(Service_Audio, "(STUBBED) called"); |
| 251 | 268 | ||
| @@ -279,6 +296,15 @@ private: | |||
| 279 | rb.Push<u32>(1); | 296 | rb.Push<u32>(1); |
| 280 | } | 297 | } |
| 281 | 298 | ||
| 299 | // Should be similar to QueryAudioDeviceOutputEvent | ||
| 300 | void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { | ||
| 301 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||
| 302 | |||
| 303 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 304 | rb.Push(RESULT_SUCCESS); | ||
| 305 | rb.PushCopyObjects(audio_input_device_switch_event.readable); | ||
| 306 | } | ||
| 307 | |||
| 282 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { | 308 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { |
| 283 | LOG_DEBUG(Service_Audio, "called"); | 309 | LOG_DEBUG(Service_Audio, "called"); |
| 284 | 310 | ||
| @@ -289,6 +315,7 @@ private: | |||
| 289 | 315 | ||
| 290 | u32_le revision = 0; | 316 | u32_le revision = 0; |
| 291 | Kernel::EventPair buffer_event; | 317 | Kernel::EventPair buffer_event; |
| 318 | Kernel::EventPair audio_input_device_switch_event; | ||
| 292 | Kernel::EventPair audio_output_device_switch_event; | 319 | Kernel::EventPair audio_output_device_switch_event; |
| 293 | 320 | ||
| 294 | }; // namespace Audio | 321 | }; // namespace Audio |
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 6701cb913..af70d174d 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp | |||
| @@ -2,32 +2,37 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/hle/ipc_helpers.h" | ||
| 5 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 6 | 8 | ||
| 7 | namespace Service::ES { | 9 | namespace Service::ES { |
| 8 | 10 | ||
| 11 | constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2}; | ||
| 12 | constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; | ||
| 13 | |||
| 9 | class ETicket final : public ServiceFramework<ETicket> { | 14 | class ETicket final : public ServiceFramework<ETicket> { |
| 10 | public: | 15 | public: |
| 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 | |||
| 64 | private: | ||
| 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 | ||
| 57 | void InstallInterfaces(SM::ServiceManager& service_manager) { | 267 | void 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 | |||
| 639 | void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { | 640 | void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { |
| 640 | can_controllers_vibrate = can_vibrate; | 641 | can_controllers_vibrate = can_vibrate; |
| 641 | } | 642 | } |
| 642 | 643 | ||
| 644 | bool Controller_NPad::IsVibrationEnabled() const { | ||
| 645 | return can_controllers_vibrate; | ||
| 646 | } | ||
| 647 | |||
| 643 | void Controller_NPad::ClearAllConnectedControllers() { | 648 | void 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 | |||
| 651 | void Controller_NPad::DisconnectAllConnectedControllers() { | 657 | void 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 | ||
| 682 | void 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 | |||
| 694 | void 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 | |||
| 682 | void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { | 703 | void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { |
| 683 | IPC::RequestParser rp{ctx}; | 704 | IPC::RequestParser rp{ctx}; |
| 684 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 705 | const auto applet_resource_user_id{rp.Pop<u64>()}; |
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 28260ef1b..2fd6d9fc7 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -114,6 +114,8 @@ private: | |||
| 114 | void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); | 114 | void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); |
| 115 | void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); | 115 | void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); |
| 116 | void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); | 116 | void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); |
| 117 | void PermitVibration(Kernel::HLERequestContext& ctx); | ||
| 118 | void IsVibrationPermitted(Kernel::HLERequestContext& ctx); | ||
| 117 | void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); | 119 | void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); |
| 118 | void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); | 120 | void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); |
| 119 | void StopSixAxisSensor(Kernel::HLERequestContext& ctx); | 121 | void StopSixAxisSensor(Kernel::HLERequestContext& ctx); |
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 5d4c3e6ea..cfe0771e2 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -5,8 +5,8 @@ | |||
| 5 | #include <ctime> | 5 | #include <ctime> |
| 6 | #include <fstream> | 6 | #include <fstream> |
| 7 | 7 | ||
| 8 | #include <fmt/chrono.h> | ||
| 8 | #include <fmt/format.h> | 9 | #include <fmt/format.h> |
| 9 | #include <fmt/time.h> | ||
| 10 | #include <json.hpp> | 10 | #include <json.hpp> |
| 11 | 11 | ||
| 12 | #include "common/file_util.h" | 12 | #include "common/file_util.h" |
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 03d434b28..4f59a87b4 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp | |||
| @@ -14,12 +14,22 @@ | |||
| 14 | namespace OpenGL { | 14 | namespace OpenGL { |
| 15 | 15 | ||
| 16 | namespace { | 16 | namespace { |
| 17 | |||
| 17 | template <typename T> | 18 | template <typename T> |
| 18 | T GetInteger(GLenum pname) { | 19 | T 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 | |||
| 25 | bool 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 | ||
| 25 | Device::Device() { | 35 | Device::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 | ||
| 37 | Device::Device(std::nullptr_t) { | 52 | Device::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 | ||
| 47 | bool Device::TestVariableAoffi() { | 63 | bool 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. |
| 50 | uniform sampler2D tex; | 66 | uniform sampler2D tex; |
| 51 | uniform ivec2 variable_offset; | 67 | uniform ivec2 variable_offset; |
| 52 | out vec4 output_attribute; | 68 | out vec4 output_attribute; |
| 53 | void main() { | 69 | void 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 | ||
| 67 | bool Device::TestComponentIndexingBug() { | 74 | bool 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 |
| 70 | layout (std430, binding = 0) buffer OutputBuffer { | 76 | layout (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 | ||
| 120 | bool Device::TestPreciseBug() { | ||
| 121 | return !TestProgram(R"(#version 430 core | ||
| 122 | in vec3 coords; | ||
| 123 | out float out_value; | ||
| 124 | uniform sampler2DShadow tex; | ||
| 125 | void 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 | |||
| 49 | private: | 53 | private: |
| 50 | static bool TestVariableAoffi(); | 54 | static bool TestVariableAoffi(); |
| 51 | static bool TestComponentIndexingBug(); | 55 | static bool TestComponentIndexingBug(); |
| 56 | static bool TestPreciseBug(); | ||
| 52 | 57 | ||
| 53 | std::size_t uniform_buffer_alignment{}; | 58 | std::size_t uniform_buffer_alignment{}; |
| 54 | std::size_t shader_storage_alignment{}; | 59 | std::size_t shader_storage_alignment{}; |
| @@ -58,6 +63,7 @@ private: | |||
| 58 | bool has_vertex_viewport_layer{}; | 63 | bool has_vertex_viewport_layer{}; |
| 59 | bool has_variable_aoffi{}; | 64 | bool has_variable_aoffi{}; |
| 60 | bool has_component_indexing_bug{}; | 65 | bool has_component_indexing_bug{}; |
| 66 | bool has_precise_bug{}; | ||
| 61 | }; | 67 | }; |
| 62 | 68 | ||
| 63 | } // namespace OpenGL | 69 | } // namespace OpenGL |
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 359d58cbe..a5cc1a86f 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp | |||
| @@ -39,7 +39,7 @@ using namespace VideoCommon::Shader; | |||
| 39 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; | 39 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; |
| 40 | using Operation = const OperationNode&; | 40 | using Operation = const OperationNode&; |
| 41 | 41 | ||
| 42 | enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; | 42 | enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat }; |
| 43 | 43 | ||
| 44 | struct TextureAoffi {}; | 44 | struct TextureAoffi {}; |
| 45 | using TextureArgument = std::pair<Type, Node>; | 45 | using TextureArgument = std::pair<Type, Node>; |
| @@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>; | |||
| 48 | constexpr u32 MAX_CONSTBUFFER_ELEMENTS = | 48 | constexpr u32 MAX_CONSTBUFFER_ELEMENTS = |
| 49 | static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); | 49 | static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); |
| 50 | 50 | ||
| 51 | class ShaderWriter { | 51 | class ShaderWriter final { |
| 52 | public: | 52 | public: |
| 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 | ||
| 96 | class Expression final { | ||
| 97 | public: | ||
| 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 | |||
| 219 | private: | ||
| 220 | std::string code; | ||
| 221 | Type type{}; | ||
| 222 | }; | ||
| 223 | |||
| 224 | constexpr 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. |
| 97 | constexpr const char* GetSwizzle(u32 element) { | 245 | constexpr 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 | ||
| 137 | constexpr Attribute::Index ToGenericAttribute(u32 value) { | 285 | constexpr 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 | ||
| 141 | u32 GetGenericAttributeIndex(Attribute::Index index) { | 289 | u32 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 | ||
| 2023 | std::string GetCommonDeclarations() { | 2127 | std::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 | ||
| 2045 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, | 2141 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0456248ac..f594106bf 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -517,10 +517,37 @@ void Config::ReadPathValues() { | |||
| 517 | UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); | 517 | UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); |
| 518 | UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); | 518 | UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); |
| 519 | UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); | 519 | UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); |
| 520 | UISettings::values.game_directory_path = | 520 | UISettings::values.game_dir_deprecated = |
| 521 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); | 521 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); |
| 522 | UISettings::values.game_directory_deepscan = | 522 | UISettings::values.game_dir_deprecated_deepscan = |
| 523 | ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); | 523 | ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); |
| 524 | const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs")); | ||
| 525 | for (int i = 0; i < gamedirs_size; ++i) { | ||
| 526 | qt_config->setArrayIndex(i); | ||
| 527 | UISettings::GameDir game_dir; | ||
| 528 | game_dir.path = ReadSetting(QStringLiteral("path")).toString(); | ||
| 529 | game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool(); | ||
| 530 | game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool(); | ||
| 531 | UISettings::values.game_dirs.append(game_dir); | ||
| 532 | } | ||
| 533 | qt_config->endArray(); | ||
| 534 | // create NAND and SD card directories if empty, these are not removable through the UI, | ||
| 535 | // also carries over old game list settings if present | ||
| 536 | if (UISettings::values.game_dirs.isEmpty()) { | ||
| 537 | UISettings::GameDir game_dir; | ||
| 538 | game_dir.path = QStringLiteral("SDMC"); | ||
| 539 | game_dir.expanded = true; | ||
| 540 | UISettings::values.game_dirs.append(game_dir); | ||
| 541 | game_dir.path = QStringLiteral("UserNAND"); | ||
| 542 | UISettings::values.game_dirs.append(game_dir); | ||
| 543 | game_dir.path = QStringLiteral("SysNAND"); | ||
| 544 | UISettings::values.game_dirs.append(game_dir); | ||
| 545 | if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) { | ||
| 546 | game_dir.path = UISettings::values.game_dir_deprecated; | ||
| 547 | game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan; | ||
| 548 | UISettings::values.game_dirs.append(game_dir); | ||
| 549 | } | ||
| 550 | } | ||
| 524 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); | 551 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); |
| 525 | 552 | ||
| 526 | qt_config->endGroup(); | 553 | qt_config->endGroup(); |
| @@ -899,10 +926,15 @@ void Config::SavePathValues() { | |||
| 899 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); | 926 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); |
| 900 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); | 927 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); |
| 901 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); | 928 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); |
| 902 | WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, | 929 | qt_config->beginWriteArray(QStringLiteral("gamedirs")); |
| 903 | QStringLiteral(".")); | 930 | for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { |
| 904 | WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, | 931 | qt_config->setArrayIndex(i); |
| 905 | false); | 932 | const auto& game_dir = UISettings::values.game_dirs[i]; |
| 933 | WriteSetting(QStringLiteral("path"), game_dir.path); | ||
| 934 | WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false); | ||
| 935 | WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true); | ||
| 936 | } | ||
| 937 | qt_config->endArray(); | ||
| 906 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); | 938 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); |
| 907 | 939 | ||
| 908 | qt_config->endGroup(); | 940 | qt_config->endGroup(); |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index e636964e3..775e3f2ea 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -68,12 +68,14 @@ void ConfigureDialog::RetranslateUI() { | |||
| 68 | ui->tabWidget->setCurrentIndex(old_index); | 68 | ui->tabWidget->setCurrentIndex(old_index); |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | Q_DECLARE_METATYPE(QList<QWidget*>); | ||
| 72 | |||
| 71 | void ConfigureDialog::PopulateSelectionList() { | 73 | void 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 | ||
| 27 | ConfigureGeneral::~ConfigureGeneral() = default; | 26 | ConfigureGeneral::~ConfigureGeneral() = default; |
| 28 | 27 | ||
| 29 | void ConfigureGeneral::SetConfiguration() { | 28 | void 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 | ||
| 36 | void ConfigureGeneral::ApplyConfiguration() { | 38 | void 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 | ||
| 44 | void ConfigureGeneral::changeEvent(QEvent* event) { | 48 | void 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() { | |||
| 89 | void ConfigureGraphics::ApplyConfiguration() { | 85 | void 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 | ||
| 90 | void GameListSearchField::setFilterResult(int visible, int total) { | 79 | void 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 | ||
| 86 | QString 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 | |||
| 94 | void GameListSearchField::clear() { | 105 | void 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 | ||
| 162 | void 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 |
| 151 | void GameList::onTextChanged(const QString& new_text) { | 171 | void 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 | |||
| 229 | void 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 | ||
| 266 | void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { | 355 | void 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 | ||
| 270 | void GameList::ValidateEntry(const QModelIndex& item) { | 362 | void 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}; | 366 | void 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 | 398 | bool 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 | ||
| 297 | void GameList::DonePopulating(QStringList watch_list) { | 412 | void 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 | |||
| 469 | void 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 | |||
| 504 | void 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)); | 524 | void 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 | ||
| 362 | void GameList::LoadCompatibilityList() { | 567 | void GameList::LoadCompatibilityList() { |
| @@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() { | |||
| 403 | } | 608 | } |
| 404 | } | 609 | } |
| 405 | 610 | ||
| 406 | void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { | 611 | void 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 | ||
| 473 | void GameList::RefreshGameDirectory() { | 673 | void 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 | |||
| 680 | GameListPlaceholder::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 | |||
| 702 | GameListPlaceholder::~GameListPlaceholder() = default; | ||
| 703 | |||
| 704 | void GameListPlaceholder::onUpdateThemedIcons() { | ||
| 705 | image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); | ||
| 706 | } | ||
| 707 | |||
| 708 | void 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 | ||
| 24 | class GameListWorker; | 27 | class GameListWorker; |
| 25 | class GameListSearchField; | 28 | class GameListSearchField; |
| 29 | class GameListDir; | ||
| 26 | class GMainWindow; | 30 | class GMainWindow; |
| 27 | 31 | ||
| 28 | namespace FileSys { | 32 | namespace 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 | ||
| 78 | private slots: | 87 | private 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 | ||
| 82 | private: | 93 | private: |
| 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 | ||
| 104 | Q_DECLARE_METATYPE(GameListOpenTarget); | 120 | Q_DECLARE_METATYPE(GameListOpenTarget); |
| 121 | |||
| 122 | class GameListPlaceholder : public QWidget { | ||
| 123 | Q_OBJECT | ||
| 124 | public: | ||
| 125 | explicit GameListPlaceholder(GMainWindow* parent = nullptr); | ||
| 126 | ~GameListPlaceholder(); | ||
| 127 | |||
| 128 | signals: | ||
| 129 | void AddDirectory(); | ||
| 130 | |||
| 131 | private slots: | ||
| 132 | void onUpdateThemedIcons(); | ||
| 133 | |||
| 134 | protected: | ||
| 135 | void mouseDoubleClickEvent(QMouseEvent* event) override; | ||
| 136 | |||
| 137 | private: | ||
| 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 | ||
| 26 | enum 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 | |||
| 35 | Q_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) { | |||
| 36 | class GameListItem : public QStandardItem { | 48 | class GameListItem : public QStandardItem { |
| 37 | 49 | ||
| 38 | public: | 50 | public: |
| 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 | */ |
| 49 | class GameListItemPath : public GameListItem { | 66 | class GameListItemPath : public GameListItem { |
| 50 | public: | 67 | public: |
| 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: | |||
| 103 | class GameListItemCompat : public GameListItem { | 125 | class GameListItemCompat : public GameListItem { |
| 104 | Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) | 126 | Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) |
| 105 | public: | 127 | public: |
| 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 | */ |
| 148 | class GameListItemSize : public GameListItem { | 176 | class GameListItemSize : public GameListItem { |
| 149 | |||
| 150 | public: | 177 | public: |
| 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 | ||
| 212 | class GameListDir : public GameListItem { | ||
| 213 | public: | ||
| 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 | |||
| 266 | private: | ||
| 267 | GameListItemType dir_type; | ||
| 268 | }; | ||
| 269 | |||
| 270 | class GameListAddDir : public GameListItem { | ||
| 271 | public: | ||
| 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 | |||
| 180 | class GameList; | 288 | class GameList; |
| 181 | class QHBoxLayout; | 289 | class QHBoxLayout; |
| 182 | class QTreeView; | 290 | class 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 | ||
| 225 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, | 225 | GameListWorker::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 | ||
| 231 | GameListWorker::~GameListWorker() = default; | 232 | GameListWorker::~GameListWorker() = default; |
| 232 | 233 | ||
| 233 | void GameListWorker::AddTitlesToGameList() { | 234 | void 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 | ||
| 263 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, | 280 | void 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 | ||
| 333 | void GameListWorker::run() { | 351 | void 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 | ||
| 35 | public: | 36 | public: |
| 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 | ||
| 61 | private: | 65 | private: |
| 62 | void AddTitlesToGameList(); | 66 | void AddTitlesToGameList(GameListDir* parent_dir); |
| 63 | 67 | ||
| 64 | enum class ScanTarget { | 68 | enum class ScanTarget { |
| 65 | FillManualContentProvider, | 69 | FillManualContentProvider, |
| 66 | PopulateGameList, | 70 | PopulateGameList, |
| 67 | }; | 71 | }; |
| 68 | 72 | ||
| 69 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); | 73 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, |
| 74 | GameListDir* parent_dir); | ||
| 70 | 75 | ||
| 71 | std::shared_ptr<FileSys::VfsFilesystem> vfs; | 76 | std::shared_ptr<FileSys::VfsFilesystem> vfs; |
| 72 | FileSys::ManualContentProvider* provider; | 77 | FileSys::ManualContentProvider* provider; |
| 73 | QStringList watch_list; | 78 | QStringList watch_list; |
| 74 | QString dir_path; | ||
| 75 | bool deep_scan; | ||
| 76 | const CompatibilityList& compatibility_list; | 79 | const CompatibilityList& compatibility_list; |
| 80 | QVector<UISettings::GameDir>& game_dirs; | ||
| 77 | std::atomic_bool stop_processing; | 81 | std::atomic_bool stop_processing; |
| 78 | }; | 82 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ac57229d5..6d249cb3e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -216,8 +216,7 @@ GMainWindow::GMainWindow() | |||
| 216 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); | 216 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); |
| 217 | 217 | ||
| 218 | game_list->LoadCompatibilityList(); | 218 | game_list->LoadCompatibilityList(); |
| 219 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 219 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 220 | UISettings::values.game_directory_deepscan); | ||
| 221 | 220 | ||
| 222 | // Show one-time "callout" messages to the user | 221 | // Show one-time "callout" messages to the user |
| 223 | ShowTelemetryCallout(); | 222 | ShowTelemetryCallout(); |
| @@ -427,6 +426,10 @@ void GMainWindow::InitializeWidgets() { | |||
| 427 | game_list = new GameList(vfs, provider.get(), this); | 426 | game_list = new GameList(vfs, provider.get(), this); |
| 428 | ui.horizontalLayout->addWidget(game_list); | 427 | ui.horizontalLayout->addWidget(game_list); |
| 429 | 428 | ||
| 429 | game_list_placeholder = new GameListPlaceholder(this); | ||
| 430 | ui.horizontalLayout->addWidget(game_list_placeholder); | ||
| 431 | game_list_placeholder->setVisible(false); | ||
| 432 | |||
| 430 | loading_screen = new LoadingScreen(this); | 433 | loading_screen = new LoadingScreen(this); |
| 431 | loading_screen->hide(); | 434 | loading_screen->hide(); |
| 432 | ui.horizontalLayout->addWidget(loading_screen); | 435 | ui.horizontalLayout->addWidget(loading_screen); |
| @@ -660,6 +663,7 @@ void GMainWindow::RestoreUIState() { | |||
| 660 | 663 | ||
| 661 | void GMainWindow::ConnectWidgetEvents() { | 664 | void GMainWindow::ConnectWidgetEvents() { |
| 662 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); | 665 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); |
| 666 | connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); | ||
| 663 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 667 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| 664 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, | 668 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, |
| 665 | &GMainWindow::OnTransferableShaderCacheOpenFile); | 669 | &GMainWindow::OnTransferableShaderCacheOpenFile); |
| @@ -667,6 +671,11 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 667 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 671 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 668 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 672 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 669 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 673 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| 674 | connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); | ||
| 675 | connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, | ||
| 676 | &GMainWindow::OnGameListAddDirectory); | ||
| 677 | connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); | ||
| 678 | |||
| 670 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, | 679 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, |
| 671 | &GMainWindow::OnGameListOpenPerGameProperties); | 680 | &GMainWindow::OnGameListOpenPerGameProperties); |
| 672 | 681 | ||
| @@ -684,8 +693,6 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 684 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); | 693 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); |
| 685 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, | 694 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, |
| 686 | &GMainWindow::OnMenuInstallToNAND); | 695 | &GMainWindow::OnMenuInstallToNAND); |
| 687 | connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, | ||
| 688 | &GMainWindow::OnMenuSelectGameListRoot); | ||
| 689 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, | 696 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, |
| 690 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); | 697 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); |
| 691 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, | 698 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, |
| @@ -950,6 +957,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 950 | // Update the GUI | 957 | // Update the GUI |
| 951 | if (ui.action_Single_Window_Mode->isChecked()) { | 958 | if (ui.action_Single_Window_Mode->isChecked()) { |
| 952 | game_list->hide(); | 959 | game_list->hide(); |
| 960 | game_list_placeholder->hide(); | ||
| 953 | } | 961 | } |
| 954 | status_bar_update_timer.start(2000); | 962 | status_bar_update_timer.start(2000); |
| 955 | 963 | ||
| @@ -1007,7 +1015,10 @@ void GMainWindow::ShutdownGame() { | |||
| 1007 | render_window->hide(); | 1015 | render_window->hide(); |
| 1008 | loading_screen->hide(); | 1016 | loading_screen->hide(); |
| 1009 | loading_screen->Clear(); | 1017 | loading_screen->Clear(); |
| 1010 | game_list->show(); | 1018 | if (game_list->isEmpty()) |
| 1019 | game_list_placeholder->show(); | ||
| 1020 | else | ||
| 1021 | game_list->show(); | ||
| 1011 | game_list->setFilterFocus(); | 1022 | game_list->setFilterFocus(); |
| 1012 | 1023 | ||
| 1013 | UpdateWindowTitle(); | 1024 | UpdateWindowTitle(); |
| @@ -1298,6 +1309,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | |||
| 1298 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); | 1309 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); |
| 1299 | } | 1310 | } |
| 1300 | 1311 | ||
| 1312 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { | ||
| 1313 | QString path; | ||
| 1314 | if (directory == QStringLiteral("SDMC")) { | ||
| 1315 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + | ||
| 1316 | "Nintendo/Contents/registered"); | ||
| 1317 | } else if (directory == QStringLiteral("UserNAND")) { | ||
| 1318 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 1319 | "user/Contents/registered"); | ||
| 1320 | } else if (directory == QStringLiteral("SysNAND")) { | ||
| 1321 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 1322 | "system/Contents/registered"); | ||
| 1323 | } else { | ||
| 1324 | path = directory; | ||
| 1325 | } | ||
| 1326 | if (!QFileInfo::exists(path)) { | ||
| 1327 | QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); | ||
| 1328 | return; | ||
| 1329 | } | ||
| 1330 | QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | void GMainWindow::OnGameListAddDirectory() { | ||
| 1334 | const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); | ||
| 1335 | if (dir_path.isEmpty()) | ||
| 1336 | return; | ||
| 1337 | UISettings::GameDir game_dir{dir_path, false, true}; | ||
| 1338 | if (!UISettings::values.game_dirs.contains(game_dir)) { | ||
| 1339 | UISettings::values.game_dirs.append(game_dir); | ||
| 1340 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1341 | } else { | ||
| 1342 | LOG_WARNING(Frontend, "Selected directory is already in the game list"); | ||
| 1343 | } | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | void GMainWindow::OnGameListShowList(bool show) { | ||
| 1347 | if (emulation_running && ui.action_Single_Window_Mode->isChecked()) | ||
| 1348 | return; | ||
| 1349 | game_list->setVisible(show); | ||
| 1350 | game_list_placeholder->setVisible(!show); | ||
| 1351 | }; | ||
| 1352 | |||
| 1301 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | 1353 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { |
| 1302 | u64 title_id{}; | 1354 | u64 title_id{}; |
| 1303 | const auto v_file = Core::GetGameFileFromPath(vfs, file); | 1355 | const auto v_file = Core::GetGameFileFromPath(vfs, file); |
| @@ -1316,8 +1368,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 1316 | 1368 | ||
| 1317 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | 1369 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); |
| 1318 | if (reload) { | 1370 | if (reload) { |
| 1319 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1371 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1320 | UISettings::values.game_directory_deepscan); | ||
| 1321 | } | 1372 | } |
| 1322 | 1373 | ||
| 1323 | config->Save(); | 1374 | config->Save(); |
| @@ -1407,8 +1458,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1407 | const auto success = [this]() { | 1458 | const auto success = [this]() { |
| 1408 | QMessageBox::information(this, tr("Successfully Installed"), | 1459 | QMessageBox::information(this, tr("Successfully Installed"), |
| 1409 | tr("The file was successfully installed.")); | 1460 | tr("The file was successfully installed.")); |
| 1410 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1461 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1411 | UISettings::values.game_directory_deepscan); | ||
| 1412 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + | 1462 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + |
| 1413 | DIR_SEP + "game_list"); | 1463 | DIR_SEP + "game_list"); |
| 1414 | }; | 1464 | }; |
| @@ -1533,14 +1583,6 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1533 | } | 1583 | } |
| 1534 | } | 1584 | } |
| 1535 | 1585 | ||
| 1536 | void GMainWindow::OnMenuSelectGameListRoot() { | ||
| 1537 | QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); | ||
| 1538 | if (!dir_path.isEmpty()) { | ||
| 1539 | UISettings::values.game_directory_path = dir_path; | ||
| 1540 | game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan); | ||
| 1541 | } | ||
| 1542 | } | ||
| 1543 | |||
| 1544 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { | 1586 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { |
| 1545 | const auto res = QMessageBox::information( | 1587 | const auto res = QMessageBox::information( |
| 1546 | this, tr("Changing Emulated Directory"), | 1588 | this, tr("Changing Emulated Directory"), |
| @@ -1559,8 +1601,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) | |||
| 1559 | : FileUtil::UserPath::NANDDir, | 1601 | : FileUtil::UserPath::NANDDir, |
| 1560 | dir_path.toStdString()); | 1602 | dir_path.toStdString()); |
| 1561 | Service::FileSystem::CreateFactories(*vfs); | 1603 | Service::FileSystem::CreateFactories(*vfs); |
| 1562 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1604 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1563 | UISettings::values.game_directory_deepscan); | ||
| 1564 | } | 1605 | } |
| 1565 | } | 1606 | } |
| 1566 | 1607 | ||
| @@ -1724,11 +1765,11 @@ void GMainWindow::OnConfigure() { | |||
| 1724 | if (UISettings::values.enable_discord_presence != old_discord_presence) { | 1765 | if (UISettings::values.enable_discord_presence != old_discord_presence) { |
| 1725 | SetDiscordEnabled(UISettings::values.enable_discord_presence); | 1766 | SetDiscordEnabled(UISettings::values.enable_discord_presence); |
| 1726 | } | 1767 | } |
| 1768 | emit UpdateThemedIcons(); | ||
| 1727 | 1769 | ||
| 1728 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | 1770 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); |
| 1729 | if (reload) { | 1771 | if (reload) { |
| 1730 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1772 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1731 | UISettings::values.game_directory_deepscan); | ||
| 1732 | } | 1773 | } |
| 1733 | 1774 | ||
| 1734 | config->Save(); | 1775 | config->Save(); |
| @@ -1992,8 +2033,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 1992 | Service::FileSystem::CreateFactories(*vfs); | 2033 | Service::FileSystem::CreateFactories(*vfs); |
| 1993 | 2034 | ||
| 1994 | if (behavior == ReinitializeKeyBehavior::Warning) { | 2035 | if (behavior == ReinitializeKeyBehavior::Warning) { |
| 1995 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 2036 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1996 | UISettings::values.game_directory_deepscan); | ||
| 1997 | } | 2037 | } |
| 1998 | } | 2038 | } |
| 1999 | 2039 | ||
| @@ -2158,7 +2198,6 @@ void GMainWindow::UpdateUITheme() { | |||
| 2158 | } | 2198 | } |
| 2159 | 2199 | ||
| 2160 | QIcon::setThemeSearchPaths(theme_paths); | 2200 | QIcon::setThemeSearchPaths(theme_paths); |
| 2161 | emit UpdateThemedIcons(); | ||
| 2162 | } | 2201 | } |
| 2163 | 2202 | ||
| 2164 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { | 2203 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 501608ddc..7d16188cb 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -30,6 +30,7 @@ class ProfilerWidget; | |||
| 30 | class QLabel; | 30 | class QLabel; |
| 31 | class WaitTreeWidget; | 31 | class WaitTreeWidget; |
| 32 | enum class GameListOpenTarget; | 32 | enum class GameListOpenTarget; |
| 33 | class GameListPlaceholder; | ||
| 33 | 34 | ||
| 34 | namespace Core::Frontend { | 35 | namespace Core::Frontend { |
| 35 | struct SoftwareKeyboardParameters; | 36 | struct SoftwareKeyboardParameters; |
| @@ -186,12 +187,13 @@ private slots: | |||
| 186 | void OnGameListCopyTID(u64 program_id); | 187 | void OnGameListCopyTID(u64 program_id); |
| 187 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 188 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 188 | const CompatibilityList& compatibility_list); | 189 | const CompatibilityList& compatibility_list); |
| 190 | void OnGameListOpenDirectory(const QString& directory); | ||
| 191 | void OnGameListAddDirectory(); | ||
| 192 | void OnGameListShowList(bool show); | ||
| 189 | void OnGameListOpenPerGameProperties(const std::string& file); | 193 | void OnGameListOpenPerGameProperties(const std::string& file); |
| 190 | void OnMenuLoadFile(); | 194 | void OnMenuLoadFile(); |
| 191 | void OnMenuLoadFolder(); | 195 | void OnMenuLoadFolder(); |
| 192 | void OnMenuInstallToNAND(); | 196 | void OnMenuInstallToNAND(); |
| 193 | /// Called whenever a user selects the "File->Select Game List Root" menu item | ||
| 194 | void OnMenuSelectGameListRoot(); | ||
| 195 | /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card | 197 | /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card |
| 196 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); | 198 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); |
| 197 | void OnMenuRecentFile(); | 199 | void OnMenuRecentFile(); |
| @@ -223,6 +225,8 @@ private: | |||
| 223 | GameList* game_list; | 225 | GameList* game_list; |
| 224 | LoadingScreen* loading_screen; | 226 | LoadingScreen* loading_screen; |
| 225 | 227 | ||
| 228 | GameListPlaceholder* game_list_placeholder; | ||
| 229 | |||
| 226 | // Status bar elements | 230 | // Status bar elements |
| 227 | QLabel* message_label = nullptr; | 231 | QLabel* message_label = nullptr; |
| 228 | QLabel* emu_speed_label = nullptr; | 232 | QLabel* emu_speed_label = nullptr; |
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index ffcabb495..a1ce3c0c3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
| @@ -62,7 +62,6 @@ | |||
| 62 | <addaction name="action_Load_File"/> | 62 | <addaction name="action_Load_File"/> |
| 63 | <addaction name="action_Load_Folder"/> | 63 | <addaction name="action_Load_Folder"/> |
| 64 | <addaction name="separator"/> | 64 | <addaction name="separator"/> |
| 65 | <addaction name="action_Select_Game_List_Root"/> | ||
| 66 | <addaction name="menu_recent_files"/> | 65 | <addaction name="menu_recent_files"/> |
| 67 | <addaction name="separator"/> | 66 | <addaction name="separator"/> |
| 68 | <addaction name="action_Select_NAND_Directory"/> | 67 | <addaction name="action_Select_NAND_Directory"/> |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index a62cd6911..c57290006 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -8,8 +8,10 @@ | |||
| 8 | #include <atomic> | 8 | #include <atomic> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include <QByteArray> | 10 | #include <QByteArray> |
| 11 | #include <QMetaType> | ||
| 11 | #include <QString> | 12 | #include <QString> |
| 12 | #include <QStringList> | 13 | #include <QStringList> |
| 14 | #include <QVector> | ||
| 13 | #include "common/common_types.h" | 15 | #include "common/common_types.h" |
| 14 | 16 | ||
| 15 | namespace UISettings { | 17 | namespace UISettings { |
| @@ -25,6 +27,18 @@ struct Shortcut { | |||
| 25 | using Themes = std::array<std::pair<const char*, const char*>, 2>; | 27 | using Themes = std::array<std::pair<const char*, const char*>, 2>; |
| 26 | extern const Themes themes; | 28 | extern const Themes themes; |
| 27 | 29 | ||
| 30 | struct 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 | |||
| 28 | struct Values { | 42 | struct 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 | ||
| 85 | extern Values values; | 100 | extern Values values; |
| 86 | } // namespace UISettings | 101 | } // namespace UISettings |
| 102 | |||
| 103 | Q_DECLARE_METATYPE(UISettings::GameDir*); | ||