diff options
Diffstat (limited to 'src')
29 files changed, 513 insertions, 359 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c5fed3fd0..95d54dadc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -109,6 +109,8 @@ if (MSVC) | |||
| 109 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) | 109 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) |
| 110 | else() | 110 | else() |
| 111 | add_compile_options( | 111 | add_compile_options( |
| 112 | -fwrapv | ||
| 113 | |||
| 112 | -Werror=all | 114 | -Werror=all |
| 113 | -Werror=extra | 115 | -Werror=extra |
| 114 | -Werror=missing-declarations | 116 | -Werror=missing-declarations |
| @@ -133,7 +135,6 @@ else() | |||
| 133 | 135 | ||
| 134 | if (ARCHITECTURE_x86_64) | 136 | if (ARCHITECTURE_x86_64) |
| 135 | add_compile_options("-mcx16") | 137 | add_compile_options("-mcx16") |
| 136 | add_compile_options("-fwrapv") | ||
| 137 | endif() | 138 | endif() |
| 138 | 139 | ||
| 139 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) | 140 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 2f67e60a9..e95ae80da 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -273,7 +273,8 @@ struct System::Impl { | |||
| 273 | time_manager.Initialize(); | 273 | time_manager.Initialize(); |
| 274 | 274 | ||
| 275 | is_powered_on = true; | 275 | is_powered_on = true; |
| 276 | exit_lock = false; | 276 | exit_locked = false; |
| 277 | exit_requested = false; | ||
| 277 | 278 | ||
| 278 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); | 279 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); |
| 279 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); | 280 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); |
| @@ -398,7 +399,8 @@ struct System::Impl { | |||
| 398 | } | 399 | } |
| 399 | 400 | ||
| 400 | is_powered_on = false; | 401 | is_powered_on = false; |
| 401 | exit_lock = false; | 402 | exit_locked = false; |
| 403 | exit_requested = false; | ||
| 402 | 404 | ||
| 403 | if (gpu_core != nullptr) { | 405 | if (gpu_core != nullptr) { |
| 404 | gpu_core->NotifyShutdown(); | 406 | gpu_core->NotifyShutdown(); |
| @@ -507,7 +509,8 @@ struct System::Impl { | |||
| 507 | 509 | ||
| 508 | CpuManager cpu_manager; | 510 | CpuManager cpu_manager; |
| 509 | std::atomic_bool is_powered_on{}; | 511 | std::atomic_bool is_powered_on{}; |
| 510 | bool exit_lock = false; | 512 | bool exit_locked = false; |
| 513 | bool exit_requested = false; | ||
| 511 | 514 | ||
| 512 | bool nvdec_active{}; | 515 | bool nvdec_active{}; |
| 513 | 516 | ||
| @@ -943,12 +946,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const { | |||
| 943 | return impl->time_manager; | 946 | return impl->time_manager; |
| 944 | } | 947 | } |
| 945 | 948 | ||
| 946 | void System::SetExitLock(bool locked) { | 949 | void System::SetExitLocked(bool locked) { |
| 947 | impl->exit_lock = locked; | 950 | impl->exit_locked = locked; |
| 948 | } | 951 | } |
| 949 | 952 | ||
| 950 | bool System::GetExitLock() const { | 953 | bool System::GetExitLocked() const { |
| 951 | return impl->exit_lock; | 954 | return impl->exit_locked; |
| 955 | } | ||
| 956 | |||
| 957 | void System::SetExitRequested(bool requested) { | ||
| 958 | impl->exit_requested = requested; | ||
| 959 | } | ||
| 960 | |||
| 961 | bool System::GetExitRequested() const { | ||
| 962 | return impl->exit_requested; | ||
| 952 | } | 963 | } |
| 953 | 964 | ||
| 954 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { | 965 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { |
diff --git a/src/core/core.h b/src/core/core.h index c70ea1965..a9ff9315e 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -412,8 +412,11 @@ public: | |||
| 412 | /// Gets an immutable reference to the Room Network. | 412 | /// Gets an immutable reference to the Room Network. |
| 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; | 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; |
| 414 | 414 | ||
| 415 | void SetExitLock(bool locked); | 415 | void SetExitLocked(bool locked); |
| 416 | [[nodiscard]] bool GetExitLock() const; | 416 | bool GetExitLocked() const; |
| 417 | |||
| 418 | void SetExitRequested(bool requested); | ||
| 419 | bool GetExitRequested() const; | ||
| 417 | 420 | ||
| 418 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); | 421 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); |
| 419 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; | 422 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; |
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 4ff2c50e5..e13c5cdc7 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -35,7 +35,6 @@ namespace Core::Crypto { | |||
| 35 | namespace { | 35 | namespace { |
| 36 | 36 | ||
| 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 38 | constexpr u64 FULL_TICKET_SIZE = 0x400; | ||
| 39 | 38 | ||
| 40 | using Common::AsArray; | 39 | using Common::AsArray; |
| 41 | 40 | ||
| @@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { | |||
| 156 | UNREACHABLE(); | 155 | UNREACHABLE(); |
| 157 | } | 156 | } |
| 158 | 157 | ||
| 158 | bool Ticket::IsValid() const { | ||
| 159 | return !std::holds_alternative<std::monostate>(data); | ||
| 160 | } | ||
| 161 | |||
| 159 | SignatureType Ticket::GetSignatureType() const { | 162 | SignatureType Ticket::GetSignatureType() const { |
| 160 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { | 163 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
| 161 | return ticket->sig_type; | 164 | return ticket->sig_type; |
| @@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ | |||
| 210 | return Ticket{out}; | 213 | return Ticket{out}; |
| 211 | } | 214 | } |
| 212 | 215 | ||
| 216 | Ticket Ticket::Read(const FileSys::VirtualFile& file) { | ||
| 217 | // Attempt to read up to the largest ticket size, and make sure we read at least a signature | ||
| 218 | // type. | ||
| 219 | std::array<u8, sizeof(RSA4096Ticket)> raw_data{}; | ||
| 220 | auto read_size = file->Read(raw_data.data(), raw_data.size(), 0); | ||
| 221 | if (read_size < sizeof(SignatureType)) { | ||
| 222 | LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size); | ||
| 223 | return Ticket{std::monostate()}; | ||
| 224 | } | ||
| 225 | return Read(std::span{raw_data}); | ||
| 226 | } | ||
| 227 | |||
| 228 | Ticket Ticket::Read(std::span<const u8> raw_data) { | ||
| 229 | // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so | ||
| 230 | // just make sure we have at least the bare minimum of data to work with. | ||
| 231 | SignatureType sig_type; | ||
| 232 | if (raw_data.size() < sizeof(SignatureType)) { | ||
| 233 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", | ||
| 234 | raw_data.size()); | ||
| 235 | return Ticket{std::monostate()}; | ||
| 236 | } | ||
| 237 | std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type)); | ||
| 238 | |||
| 239 | switch (sig_type) { | ||
| 240 | case SignatureType::RSA_4096_SHA1: | ||
| 241 | case SignatureType::RSA_4096_SHA256: { | ||
| 242 | RSA4096Ticket ticket{}; | ||
| 243 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 244 | return Ticket{ticket}; | ||
| 245 | } | ||
| 246 | case SignatureType::RSA_2048_SHA1: | ||
| 247 | case SignatureType::RSA_2048_SHA256: { | ||
| 248 | RSA2048Ticket ticket{}; | ||
| 249 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 250 | return Ticket{ticket}; | ||
| 251 | } | ||
| 252 | case SignatureType::ECDSA_SHA1: | ||
| 253 | case SignatureType::ECDSA_SHA256: { | ||
| 254 | ECDSATicket ticket{}; | ||
| 255 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 256 | return Ticket{ticket}; | ||
| 257 | } | ||
| 258 | default: | ||
| 259 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type); | ||
| 260 | return Ticket{std::monostate()}; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 213 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 264 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { |
| 214 | Key128 out{}; | 265 | Key128 out{}; |
| 215 | 266 | ||
| @@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { | |||
| 290 | } | 341 | } |
| 291 | } | 342 | } |
| 292 | 343 | ||
| 293 | RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | 344 | void KeyManager::DeriveETicketRSAKey() { |
| 294 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { | 345 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { |
| 295 | return {}; | 346 | return; |
| 296 | } | 347 | } |
| 297 | 348 | ||
| 298 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); | 349 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); |
| @@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | |||
| 304 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | 355 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, |
| 305 | extended_dec.data(), Op::Decrypt); | 356 | extended_dec.data(), Op::Decrypt); |
| 306 | 357 | ||
| 307 | RSAKeyPair<2048> rsa_key{}; | 358 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), |
| 308 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | 359 | eticket_rsa_keypair.decryption_key.size()); |
| 309 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | 360 | std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, |
| 310 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | 361 | eticket_rsa_keypair.modulus.size()); |
| 311 | 362 | std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, | |
| 312 | return rsa_key; | 363 | eticket_rsa_keypair.exponent.size()); |
| 313 | } | 364 | } |
| 314 | 365 | ||
| 315 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { | 366 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { |
| @@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { | |||
| 447 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { | 498 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { |
| 448 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && | 499 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && |
| 449 | buffer[offset + 3] == 0x0) { | 500 | buffer[offset + 3] == 0x0) { |
| 450 | out.emplace_back(); | 501 | // NOTE: Assumes ticket blob will only contain RSA-2048 tickets. |
| 451 | auto& next = out.back(); | 502 | auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)}); |
| 452 | std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); | 503 | offset += sizeof(RSA2048Ticket); |
| 453 | offset += FULL_TICKET_SIZE; | 504 | if (ticket.IsValid()) { |
| 505 | out.push_back(ticket); | ||
| 506 | } | ||
| 454 | } | 507 | } |
| 455 | } | 508 | } |
| 456 | 509 | ||
| @@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { | |||
| 503 | return offset; | 556 | return offset; |
| 504 | } | 557 | } |
| 505 | 558 | ||
| 506 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | 559 | std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) { |
| 507 | const RSAKeyPair<2048>& key) { | 560 | if (!ticket.IsValid()) { |
| 561 | LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket."); | ||
| 562 | return std::nullopt; | ||
| 563 | } | ||
| 564 | |||
| 565 | if (ticket.GetData().rights_id == Key128{}) { | ||
| 566 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID."); | ||
| 567 | return std::nullopt; | ||
| 568 | } | ||
| 569 | |||
| 508 | const auto issuer = ticket.GetData().issuer; | 570 | const auto issuer = ticket.GetData().issuer; |
| 509 | if (IsAllZeroArray(issuer)) { | 571 | if (IsAllZeroArray(issuer)) { |
| 572 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer."); | ||
| 510 | return std::nullopt; | 573 | return std::nullopt; |
| 511 | } | 574 | } |
| 575 | |||
| 512 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { | 576 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { |
| 513 | LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); | 577 | LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority."); |
| 514 | } | 578 | } |
| 515 | 579 | ||
| 516 | Key128 rights_id = ticket.GetData().rights_id; | 580 | if (ticket.GetData().type == TitleKeyType::Common) { |
| 517 | 581 | return ticket.GetData().title_key_common; | |
| 518 | if (rights_id == Key128{}) { | ||
| 519 | return std::nullopt; | ||
| 520 | } | 582 | } |
| 521 | 583 | ||
| 522 | if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), | 584 | if (eticket_rsa_keypair == RSAKeyPair<2048>{}) { |
| 523 | ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { | 585 | LOG_WARNING( |
| 524 | return std::make_pair(rights_id, ticket.GetData().title_key_common); | 586 | Crypto, |
| 587 | "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair."); | ||
| 588 | return std::nullopt; | ||
| 525 | } | 589 | } |
| 526 | 590 | ||
| 527 | mbedtls_mpi D; // RSA Private Exponent | 591 | mbedtls_mpi D; // RSA Private Exponent |
| @@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 534 | mbedtls_mpi_init(&S); | 598 | mbedtls_mpi_init(&S); |
| 535 | mbedtls_mpi_init(&M); | 599 | mbedtls_mpi_init(&M); |
| 536 | 600 | ||
| 537 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); | 601 | const auto& title_key_block = ticket.GetData().title_key_block; |
| 538 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); | 602 | mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(), |
| 539 | mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); | 603 | eticket_rsa_keypair.decryption_key.size()); |
| 604 | mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(), | ||
| 605 | eticket_rsa_keypair.modulus.size()); | ||
| 606 | mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size()); | ||
| 540 | 607 | ||
| 541 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); | 608 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); |
| 542 | 609 | ||
| @@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 564 | 631 | ||
| 565 | Key128 key_temp{}; | 632 | Key128 key_temp{}; |
| 566 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); | 633 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); |
| 567 | 634 | return key_temp; | |
| 568 | return std::make_pair(rights_id, key_temp); | ||
| 569 | } | 635 | } |
| 570 | 636 | ||
| 571 | KeyManager::KeyManager() { | 637 | KeyManager::KeyManager() { |
| @@ -669,6 +735,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 669 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); | 735 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); |
| 670 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { | 736 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { |
| 671 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); | 737 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); |
| 738 | } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { | ||
| 739 | const auto key_data = Common::HexStringToArray<528>(out[1]); | ||
| 740 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), | ||
| 741 | eticket_rsa_keypair.decryption_key.size()); | ||
| 742 | std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, | ||
| 743 | eticket_rsa_keypair.modulus.size()); | ||
| 744 | std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, | ||
| 745 | eticket_rsa_keypair.exponent.size()); | ||
| 672 | } else { | 746 | } else { |
| 673 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { | 747 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { |
| 674 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { | 748 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { |
| @@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data, | |||
| 1110 | 1184 | ||
| 1111 | eticket_extended_kek = data.GetETicketExtendedKek(); | 1185 | eticket_extended_kek = data.GetETicketExtendedKek(); |
| 1112 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); | 1186 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); |
| 1187 | DeriveETicketRSAKey(); | ||
| 1113 | PopulateTickets(); | 1188 | PopulateTickets(); |
| 1114 | } | 1189 | } |
| 1115 | 1190 | ||
| 1116 | void KeyManager::PopulateTickets() { | 1191 | void KeyManager::PopulateTickets() { |
| 1117 | const auto rsa_key = GetETicketRSAKey(); | 1192 | if (ticket_databases_loaded) { |
| 1118 | |||
| 1119 | if (rsa_key == RSAKeyPair<2048>{}) { | ||
| 1120 | return; | 1193 | return; |
| 1121 | } | 1194 | } |
| 1195 | ticket_databases_loaded = true; | ||
| 1122 | 1196 | ||
| 1123 | if (!common_tickets.empty() && !personal_tickets.empty()) { | 1197 | std::vector<Ticket> tickets; |
| 1124 | return; | ||
| 1125 | } | ||
| 1126 | 1198 | ||
| 1127 | const auto system_save_e1_path = | 1199 | const auto system_save_e1_path = |
| 1128 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; | 1200 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; |
| 1129 | 1201 | if (Common::FS::Exists(system_save_e1_path)) { | |
| 1130 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, | 1202 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, |
| 1131 | Common::FS::FileType::BinaryFile}; | 1203 | Common::FS::FileType::BinaryFile}; |
| 1204 | const auto blob1 = GetTicketblob(save_e1); | ||
| 1205 | tickets.insert(tickets.end(), blob1.begin(), blob1.end()); | ||
| 1206 | } | ||
| 1132 | 1207 | ||
| 1133 | const auto system_save_e2_path = | 1208 | const auto system_save_e2_path = |
| 1134 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; | 1209 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; |
| 1210 | if (Common::FS::Exists(system_save_e2_path)) { | ||
| 1211 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | ||
| 1212 | Common::FS::FileType::BinaryFile}; | ||
| 1213 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1214 | tickets.insert(tickets.end(), blob2.begin(), blob2.end()); | ||
| 1215 | } | ||
| 1135 | 1216 | ||
| 1136 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | 1217 | for (const auto& ticket : tickets) { |
| 1137 | Common::FS::FileType::BinaryFile}; | 1218 | AddTicket(ticket); |
| 1138 | |||
| 1139 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1140 | auto res = GetTicketblob(save_e1); | ||
| 1141 | |||
| 1142 | const auto idx = res.size(); | ||
| 1143 | res.insert(res.end(), blob2.begin(), blob2.end()); | ||
| 1144 | |||
| 1145 | for (std::size_t i = 0; i < res.size(); ++i) { | ||
| 1146 | const auto common = i < idx; | ||
| 1147 | const auto pair = ParseTicket(res[i], rsa_key); | ||
| 1148 | if (!pair) { | ||
| 1149 | continue; | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | const auto& [rid, key] = *pair; | ||
| 1153 | u128 rights_id; | ||
| 1154 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1155 | |||
| 1156 | if (common) { | ||
| 1157 | common_tickets[rights_id] = res[i]; | ||
| 1158 | } else { | ||
| 1159 | personal_tickets[rights_id] = res[i]; | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1163 | } | 1219 | } |
| 1164 | } | 1220 | } |
| 1165 | 1221 | ||
| @@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { | |||
| 1291 | return personal_tickets; | 1347 | return personal_tickets; |
| 1292 | } | 1348 | } |
| 1293 | 1349 | ||
| 1294 | bool KeyManager::AddTicketCommon(Ticket raw) { | 1350 | bool KeyManager::AddTicket(const Ticket& ticket) { |
| 1295 | const auto rsa_key = GetETicketRSAKey(); | 1351 | if (!ticket.IsValid()) { |
| 1296 | if (rsa_key == RSAKeyPair<2048>{}) { | 1352 | LOG_WARNING(Crypto, "Attempted to add invalid ticket."); |
| 1297 | return false; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 1301 | if (!pair) { | ||
| 1302 | return false; | 1353 | return false; |
| 1303 | } | 1354 | } |
| 1304 | 1355 | ||
| 1305 | const auto& [rid, key] = *pair; | 1356 | const auto& rid = ticket.GetData().rights_id; |
| 1306 | u128 rights_id; | 1357 | u128 rights_id; |
| 1307 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | 1358 | std::memcpy(rights_id.data(), rid.data(), rid.size()); |
| 1308 | common_tickets[rights_id] = raw; | 1359 | if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) { |
| 1309 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | 1360 | common_tickets[rights_id] = ticket; |
| 1310 | return true; | 1361 | } else { |
| 1311 | } | 1362 | personal_tickets[rights_id] = ticket; |
| 1363 | } | ||
| 1312 | 1364 | ||
| 1313 | bool KeyManager::AddTicketPersonalized(Ticket raw) { | 1365 | if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) { |
| 1314 | const auto rsa_key = GetETicketRSAKey(); | 1366 | LOG_DEBUG(Crypto, |
| 1315 | if (rsa_key == RSAKeyPair<2048>{}) { | 1367 | "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", |
| 1316 | return false; | 1368 | rights_id[1], rights_id[0]); |
| 1369 | return true; | ||
| 1317 | } | 1370 | } |
| 1318 | 1371 | ||
| 1319 | const auto pair = ParseTicket(raw, rsa_key); | 1372 | const auto key = ParseTicketTitleKey(ticket); |
| 1320 | if (!pair) { | 1373 | if (!key) { |
| 1321 | return false; | 1374 | return false; |
| 1322 | } | 1375 | } |
| 1323 | 1376 | SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]); | |
| 1324 | const auto& [rid, key] = *pair; | ||
| 1325 | u128 rights_id; | ||
| 1326 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1327 | common_tickets[rights_id] = raw; | ||
| 1328 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1329 | return true; | 1377 | return true; |
| 1330 | } | 1378 | } |
| 1331 | } // namespace Core::Crypto | 1379 | } // namespace Core::Crypto |
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8c864503b..2250eccec 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <filesystem> | 7 | #include <filesystem> |
| 8 | #include <map> | 8 | #include <map> |
| 9 | #include <optional> | 9 | #include <optional> |
| 10 | #include <span> | ||
| 10 | #include <string> | 11 | #include <string> |
| 11 | 12 | ||
| 12 | #include <variant> | 13 | #include <variant> |
| @@ -29,8 +30,6 @@ enum class ResultStatus : u16; | |||
| 29 | 30 | ||
| 30 | namespace Core::Crypto { | 31 | namespace Core::Crypto { |
| 31 | 32 | ||
| 32 | constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | ||
| 33 | |||
| 34 | using Key128 = std::array<u8, 0x10>; | 33 | using Key128 = std::array<u8, 0x10>; |
| 35 | using Key256 = std::array<u8, 0x20>; | 34 | using Key256 = std::array<u8, 0x20>; |
| 36 | using SHA256Hash = std::array<u8, 0x20>; | 35 | using SHA256Hash = std::array<u8, 0x20>; |
| @@ -82,6 +81,7 @@ struct RSA4096Ticket { | |||
| 82 | INSERT_PADDING_BYTES(0x3C); | 81 | INSERT_PADDING_BYTES(0x3C); |
| 83 | TicketData data; | 82 | TicketData data; |
| 84 | }; | 83 | }; |
| 84 | static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size."); | ||
| 85 | 85 | ||
| 86 | struct RSA2048Ticket { | 86 | struct RSA2048Ticket { |
| 87 | SignatureType sig_type; | 87 | SignatureType sig_type; |
| @@ -89,6 +89,7 @@ struct RSA2048Ticket { | |||
| 89 | INSERT_PADDING_BYTES(0x3C); | 89 | INSERT_PADDING_BYTES(0x3C); |
| 90 | TicketData data; | 90 | TicketData data; |
| 91 | }; | 91 | }; |
| 92 | static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size."); | ||
| 92 | 93 | ||
| 93 | struct ECDSATicket { | 94 | struct ECDSATicket { |
| 94 | SignatureType sig_type; | 95 | SignatureType sig_type; |
| @@ -96,16 +97,41 @@ struct ECDSATicket { | |||
| 96 | INSERT_PADDING_BYTES(0x40); | 97 | INSERT_PADDING_BYTES(0x40); |
| 97 | TicketData data; | 98 | TicketData data; |
| 98 | }; | 99 | }; |
| 100 | static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size."); | ||
| 99 | 101 | ||
| 100 | struct Ticket { | 102 | struct Ticket { |
| 101 | std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; | 103 | std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; |
| 102 | 104 | ||
| 103 | SignatureType GetSignatureType() const; | 105 | [[nodiscard]] bool IsValid() const; |
| 104 | TicketData& GetData(); | 106 | [[nodiscard]] SignatureType GetSignatureType() const; |
| 105 | const TicketData& GetData() const; | 107 | [[nodiscard]] TicketData& GetData(); |
| 106 | u64 GetSize() const; | 108 | [[nodiscard]] const TicketData& GetData() const; |
| 107 | 109 | [[nodiscard]] u64 GetSize() const; | |
| 110 | |||
| 111 | /** | ||
| 112 | * Synthesizes a common ticket given a title key and rights ID. | ||
| 113 | * | ||
| 114 | * @param title_key Title key to store in the ticket. | ||
| 115 | * @param rights_id Rights ID the ticket is for. | ||
| 116 | * @return The synthesized common ticket. | ||
| 117 | */ | ||
| 108 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); | 118 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); |
| 119 | |||
| 120 | /** | ||
| 121 | * Reads a ticket from a file. | ||
| 122 | * | ||
| 123 | * @param file File to read the ticket from. | ||
| 124 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 125 | */ | ||
| 126 | static Ticket Read(const FileSys::VirtualFile& file); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Reads a ticket from a memory buffer. | ||
| 130 | * | ||
| 131 | * @param raw_data Buffer to read the ticket from. | ||
| 132 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 133 | */ | ||
| 134 | static Ticket Read(std::span<const u8> raw_data); | ||
| 109 | }; | 135 | }; |
| 110 | 136 | ||
| 111 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 137 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| @@ -264,8 +290,7 @@ public: | |||
| 264 | const std::map<u128, Ticket>& GetCommonTickets() const; | 290 | const std::map<u128, Ticket>& GetCommonTickets() const; |
| 265 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; | 291 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; |
| 266 | 292 | ||
| 267 | bool AddTicketCommon(Ticket raw); | 293 | bool AddTicket(const Ticket& ticket); |
| 268 | bool AddTicketPersonalized(Ticket raw); | ||
| 269 | 294 | ||
| 270 | void ReloadKeys(); | 295 | void ReloadKeys(); |
| 271 | bool AreKeysLoaded() const; | 296 | bool AreKeysLoaded() const; |
| @@ -279,10 +304,12 @@ private: | |||
| 279 | // Map from rights ID to ticket | 304 | // Map from rights ID to ticket |
| 280 | std::map<u128, Ticket> common_tickets; | 305 | std::map<u128, Ticket> common_tickets; |
| 281 | std::map<u128, Ticket> personal_tickets; | 306 | std::map<u128, Ticket> personal_tickets; |
| 307 | bool ticket_databases_loaded = false; | ||
| 282 | 308 | ||
| 283 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; | 309 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; |
| 284 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; | 310 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; |
| 285 | std::array<u8, 576> eticket_extended_kek{}; | 311 | std::array<u8, 576> eticket_extended_kek{}; |
| 312 | RSAKeyPair<2048> eticket_rsa_keypair{}; | ||
| 286 | 313 | ||
| 287 | bool dev_mode; | 314 | bool dev_mode; |
| 288 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); | 315 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); |
| @@ -293,10 +320,13 @@ private: | |||
| 293 | 320 | ||
| 294 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); | 321 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); |
| 295 | 322 | ||
| 296 | RSAKeyPair<2048> GetETicketRSAKey() const; | 323 | void DeriveETicketRSAKey(); |
| 297 | 324 | ||
| 298 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | 325 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); |
| 299 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | 326 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); |
| 327 | |||
| 328 | /// Parses the title key section of a ticket. | ||
| 329 | std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket); | ||
| 300 | }; | 330 | }; |
| 301 | 331 | ||
| 302 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); | 332 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); |
| @@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke | |||
| 311 | 341 | ||
| 312 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); | 342 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); |
| 313 | 343 | ||
| 314 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority | ||
| 315 | // (offset 0x140-0x144 is zero) | ||
| 316 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | ||
| 317 | const RSAKeyPair<2048>& eticket_extended_key); | ||
| 318 | |||
| 319 | } // namespace Core::Crypto | 344 | } // namespace Core::Crypto |
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 44e6852fe..7d2f0abb8 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -22,6 +22,10 @@ | |||
| 22 | 22 | ||
| 23 | namespace FileSys { | 23 | namespace FileSys { |
| 24 | 24 | ||
| 25 | static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { | ||
| 26 | return std::max<u8>(key_generation, 1) - 1; | ||
| 27 | } | ||
| 28 | |||
| 25 | NCA::NCA(VirtualFile file_, const NCA* base_nca) | 29 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 26 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { | 30 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 27 | if (file == nullptr) { | 31 | if (file == nullptr) { |
| @@ -41,12 +45,17 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) | |||
| 41 | return; | 45 | return; |
| 42 | } | 46 | } |
| 43 | 47 | ||
| 48 | // Ensure we have the proper key area keys to continue. | ||
| 49 | const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); | ||
| 50 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { | ||
| 51 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 44 | RightsId rights_id{}; | 55 | RightsId rights_id{}; |
| 45 | reader->GetRightsId(rights_id.data(), rights_id.size()); | 56 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 46 | if (rights_id != RightsId{}) { | 57 | if (rights_id != RightsId{}) { |
| 47 | // External decryption key required; provide it here. | 58 | // External decryption key required; provide it here. |
| 48 | const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1; | ||
| 49 | |||
| 50 | u128 rights_id_u128; | 59 | u128 rights_id_u128; |
| 51 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); | 60 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 52 | 61 | ||
| @@ -57,12 +66,12 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) | |||
| 57 | return; | 66 | return; |
| 58 | } | 67 | } |
| 59 | 68 | ||
| 60 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { | 69 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
| 61 | status = Loader::ResultStatus::ErrorMissingTitlekek; | 70 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 62 | return; | 71 | return; |
| 63 | } | 72 | } |
| 64 | 73 | ||
| 65 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation); | 74 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id); |
| 66 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); | 75 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 67 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | 76 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), |
| 68 | Core::Crypto::Op::Decrypt); | 77 | Core::Crypto::Op::Decrypt); |
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index e1e89ce2d..68e8ec22f 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp | |||
| @@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl | |||
| 164 | return nullptr; | 164 | return nullptr; |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { | ||
| 168 | if (extracted) | ||
| 169 | LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||
| 170 | std::vector<Core::Crypto::Key128> out; | ||
| 171 | for (const auto& ticket_file : ticket_files) { | ||
| 172 | if (ticket_file == nullptr || | ||
| 173 | ticket_file->GetSize() < | ||
| 174 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | ||
| 175 | continue; | ||
| 176 | } | ||
| 177 | |||
| 178 | out.emplace_back(); | ||
| 179 | ticket_file->Read(out.back().data(), out.back().size(), | ||
| 180 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 181 | } | ||
| 182 | return out; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::vector<VirtualFile> NSP::GetFiles() const { | 167 | std::vector<VirtualFile> NSP::GetFiles() const { |
| 186 | return pfs->GetFiles(); | 168 | return pfs->GetFiles(); |
| 187 | } | 169 | } |
| @@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) { | |||
| 208 | continue; | 190 | continue; |
| 209 | } | 191 | } |
| 210 | 192 | ||
| 211 | if (ticket_file->GetSize() < | 193 | auto ticket = Core::Crypto::Ticket::Read(ticket_file); |
| 212 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | 194 | if (!keys.AddTicket(ticket)) { |
| 195 | LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName()); | ||
| 213 | continue; | 196 | continue; |
| 214 | } | 197 | } |
| 215 | |||
| 216 | Core::Crypto::Key128 key{}; | ||
| 217 | ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 218 | |||
| 219 | // We get the name without the extension in order to create the rights ID. | ||
| 220 | std::string name_only(ticket_file->GetName()); | ||
| 221 | name_only.erase(name_only.size() - 4); | ||
| 222 | |||
| 223 | const auto rights_id_raw = Common::HexStringToArray<16>(name_only); | ||
| 224 | u128 rights_id; | ||
| 225 | std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); | ||
| 226 | keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 227 | } | 198 | } |
| 228 | } | 199 | } |
| 229 | 200 | ||
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 27f97c725..915bffca9 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h | |||
| @@ -53,7 +53,6 @@ public: | |||
| 53 | TitleType title_type = TitleType::Application) const; | 53 | TitleType title_type = TitleType::Application) const; |
| 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, | 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, |
| 55 | TitleType title_type = TitleType::Application) const; | 55 | TitleType title_type = TitleType::Application) const; |
| 56 | std::vector<Core::Crypto::Key128> GetTitlekey() const; | ||
| 57 | 56 | ||
| 58 | std::vector<VirtualFile> GetFiles() const override; | 57 | std::vector<VirtualFile> GetFiles() const override; |
| 59 | 58 | ||
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index da33f0e44..e92f400de 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -341,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { | |||
| 341 | void ISelfController::LockExit(HLERequestContext& ctx) { | 341 | void ISelfController::LockExit(HLERequestContext& ctx) { |
| 342 | LOG_DEBUG(Service_AM, "called"); | 342 | LOG_DEBUG(Service_AM, "called"); |
| 343 | 343 | ||
| 344 | system.SetExitLock(true); | 344 | system.SetExitLocked(true); |
| 345 | 345 | ||
| 346 | IPC::ResponseBuilder rb{ctx, 2}; | 346 | IPC::ResponseBuilder rb{ctx, 2}; |
| 347 | rb.Push(ResultSuccess); | 347 | rb.Push(ResultSuccess); |
| @@ -350,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { | |||
| 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { | 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { |
| 351 | LOG_DEBUG(Service_AM, "called"); | 351 | LOG_DEBUG(Service_AM, "called"); |
| 352 | 352 | ||
| 353 | system.SetExitLock(false); | 353 | system.SetExitLocked(false); |
| 354 | 354 | ||
| 355 | IPC::ResponseBuilder rb{ctx, 2}; | 355 | IPC::ResponseBuilder rb{ctx, 2}; |
| 356 | rb.Push(ResultSuccess); | 356 | rb.Push(ResultSuccess); |
| 357 | |||
| 358 | if (system.GetExitRequested()) { | ||
| 359 | system.Exit(); | ||
| 360 | } | ||
| 357 | } | 361 | } |
| 358 | 362 | ||
| 359 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { | 363 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3c..9eaae4c4b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp | |||
| @@ -122,20 +122,18 @@ private: | |||
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | void ImportTicket(HLERequestContext& ctx) { | 124 | void ImportTicket(HLERequestContext& ctx) { |
| 125 | const auto ticket = ctx.ReadBuffer(); | 125 | const auto raw_ticket = ctx.ReadBuffer(); |
| 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); | 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); |
| 127 | 127 | ||
| 128 | if (ticket.size() < sizeof(Core::Crypto::Ticket)) { | 128 | if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) { |
| 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); | 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); |
| 130 | IPC::ResponseBuilder rb{ctx, 2}; | 130 | IPC::ResponseBuilder rb{ctx, 2}; |
| 131 | rb.Push(ERROR_INVALID_ARGUMENT); | 131 | rb.Push(ERROR_INVALID_ARGUMENT); |
| 132 | return; | 132 | return; |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | Core::Crypto::Ticket raw{}; | 135 | Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket); |
| 136 | std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); | 136 | if (!keys.AddTicket(ticket)) { |
| 137 | |||
| 138 | if (!keys.AddTicketPersonalized(raw)) { | ||
| 139 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); | 137 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); |
| 140 | IPC::ResponseBuilder rb{ctx, 2}; | 138 | IPC::ResponseBuilder rb{ctx, 2}; |
| 141 | rb.Push(ERROR_INVALID_ARGUMENT); | 139 | rb.Push(ERROR_INVALID_ARGUMENT); |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 46125d473..6b966f20d 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -21,7 +21,7 @@ constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | |||
| 21 | constexpr std::size_t BaseMiiCount{2}; | 21 | constexpr std::size_t BaseMiiCount{2}; |
| 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
| 23 | 23 | ||
| 24 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | 24 | constexpr MiiStoreData::Name DefaultMiiName{u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; |
| 25 | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; | 25 | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; |
| 26 | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; | 26 | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; |
| 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; | 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; |
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp index 1442280c8..80369cdb0 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/raw_data.cpp | |||
| @@ -5,109 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | namespace Service::Mii::RawData { | 6 | namespace Service::Mii::RawData { |
| 7 | 7 | ||
| 8 | const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | 8 | const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ |
| 9 | Service::Mii::DefaultMii{ | ||
| 10 | .face_type = 0, | ||
| 11 | .face_color = 0, | ||
| 12 | .face_wrinkle = 0, | ||
| 13 | .face_makeup = 0, | ||
| 14 | .hair_type = 33, | ||
| 15 | .hair_color = 1, | ||
| 16 | .hair_flip = 0, | ||
| 17 | .eye_type = 2, | ||
| 18 | .eye_color = 0, | ||
| 19 | .eye_scale = 4, | ||
| 20 | .eye_aspect = 3, | ||
| 21 | .eye_rotate = 4, | ||
| 22 | .eye_x = 2, | ||
| 23 | .eye_y = 12, | ||
| 24 | .eyebrow_type = 6, | ||
| 25 | .eyebrow_color = 1, | ||
| 26 | .eyebrow_scale = 4, | ||
| 27 | .eyebrow_aspect = 3, | ||
| 28 | .eyebrow_rotate = 6, | ||
| 29 | .eyebrow_x = 2, | ||
| 30 | .eyebrow_y = 10, | ||
| 31 | .nose_type = 1, | ||
| 32 | .nose_scale = 4, | ||
| 33 | .nose_y = 9, | ||
| 34 | .mouth_type = 23, | ||
| 35 | .mouth_color = 0, | ||
| 36 | .mouth_scale = 4, | ||
| 37 | .mouth_aspect = 3, | ||
| 38 | .mouth_y = 13, | ||
| 39 | .mustache_type = 0, | ||
| 40 | .beard_type = 0, | ||
| 41 | .beard_color = 0, | ||
| 42 | .mustache_scale = 4, | ||
| 43 | .mustache_y = 10, | ||
| 44 | .glasses_type = 0, | ||
| 45 | .glasses_color = 0, | ||
| 46 | .glasses_scale = 4, | ||
| 47 | .glasses_y = 10, | ||
| 48 | .mole_type = 0, | ||
| 49 | .mole_scale = 4, | ||
| 50 | .mole_x = 2, | ||
| 51 | .mole_y = 20, | ||
| 52 | .height = 64, | ||
| 53 | .weight = 64, | ||
| 54 | .gender = Gender::Male, | ||
| 55 | .favorite_color = 0, | ||
| 56 | .region = 0, | ||
| 57 | .font_region = FontRegion::Standard, | ||
| 58 | .type = 0, | ||
| 59 | }, | ||
| 60 | Service::Mii::DefaultMii{ | ||
| 61 | .face_type = 0, | ||
| 62 | .face_color = 0, | ||
| 63 | .face_wrinkle = 0, | ||
| 64 | .face_makeup = 0, | ||
| 65 | .hair_type = 12, | ||
| 66 | .hair_color = 1, | ||
| 67 | .hair_flip = 0, | ||
| 68 | .eye_type = 4, | ||
| 69 | .eye_color = 0, | ||
| 70 | .eye_scale = 4, | ||
| 71 | .eye_aspect = 3, | ||
| 72 | .eye_rotate = 3, | ||
| 73 | .eye_x = 2, | ||
| 74 | .eye_y = 12, | ||
| 75 | .eyebrow_type = 0, | ||
| 76 | .eyebrow_color = 1, | ||
| 77 | .eyebrow_scale = 4, | ||
| 78 | .eyebrow_aspect = 3, | ||
| 79 | .eyebrow_rotate = 6, | ||
| 80 | .eyebrow_x = 2, | ||
| 81 | .eyebrow_y = 10, | ||
| 82 | .nose_type = 1, | ||
| 83 | .nose_scale = 4, | ||
| 84 | .nose_y = 9, | ||
| 85 | .mouth_type = 23, | ||
| 86 | .mouth_color = 0, | ||
| 87 | .mouth_scale = 4, | ||
| 88 | .mouth_aspect = 3, | ||
| 89 | .mouth_y = 13, | ||
| 90 | .mustache_type = 0, | ||
| 91 | .beard_type = 0, | ||
| 92 | .beard_color = 0, | ||
| 93 | .mustache_scale = 4, | ||
| 94 | .mustache_y = 10, | ||
| 95 | .glasses_type = 0, | ||
| 96 | .glasses_color = 0, | ||
| 97 | .glasses_scale = 4, | ||
| 98 | .glasses_y = 10, | ||
| 99 | .mole_type = 0, | ||
| 100 | .mole_scale = 4, | ||
| 101 | .mole_x = 2, | ||
| 102 | .mole_y = 20, | ||
| 103 | .height = 64, | ||
| 104 | .weight = 64, | ||
| 105 | .gender = Gender::Female, | ||
| 106 | .favorite_color = 0, | ||
| 107 | .region = 0, | ||
| 108 | .font_region = FontRegion::Standard, | ||
| 109 | .type = 0, | ||
| 110 | }, | ||
| 111 | Service::Mii::DefaultMii{ | 9 | Service::Mii::DefaultMii{ |
| 112 | .face_type = 0, | 10 | .face_type = 0, |
| 113 | .face_color = 4, | 11 | .face_color = 4, |
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h index c2bec68d4..5b81b013b 100644 --- a/src/core/hle/service/mii/raw_data.h +++ b/src/core/hle/service/mii/raw_data.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | namespace Service::Mii::RawData { | 10 | namespace Service::Mii::RawData { |
| 11 | 11 | ||
| 12 | extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; | 12 | extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; |
| 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; | 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; |
| 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; | 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; |
| 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; | 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp index 753c62098..e593132e6 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp | |||
| @@ -161,7 +161,8 @@ enum class SpecialRegister : u64 { | |||
| 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); | 161 | LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); |
| 162 | return ir.Imm32(0); // This is the default value hardware returns. | 162 | return ir.Imm32(0); // This is the default value hardware returns. |
| 163 | default: | 163 | default: |
| 164 | throw NotImplementedException("S2R special register {}", special_register); | 164 | LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register); |
| 165 | return ir.Imm32(0); // This is the default value hardware returns. | ||
| 165 | } | 166 | } |
| 166 | } | 167 | } |
| 167 | } // Anonymous namespace | 168 | } // Anonymous namespace |
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 9f1b340a9..58ce0d8c2 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | namespace Tegra { | 14 | namespace Tegra { |
| 15 | 15 | ||
| 16 | constexpr u32 MacroRegistersStart = 0xE00; | 16 | constexpr u32 MacroRegistersStart = 0xE00; |
| 17 | constexpr u32 ComputeInline = 0x6D; | ||
| 17 | 18 | ||
| 18 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, | 19 | DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, |
| 19 | Control::ChannelState& channel_state_) | 20 | Control::ChannelState& channel_state_) |
| @@ -83,12 +84,35 @@ bool DmaPusher::Step() { | |||
| 83 | dma_state.dma_get, command_list_header.size * sizeof(u32)); | 84 | dma_state.dma_get, command_list_header.size * sizeof(u32)); |
| 84 | } | 85 | } |
| 85 | } | 86 | } |
| 86 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | 87 | const auto safe_process = [&] { |
| 87 | Core::Memory::GuestMemoryFlags::UnsafeRead> | 88 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, |
| 88 | headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); | 89 | Core::Memory::GuestMemoryFlags::SafeRead> |
| 89 | ProcessCommands(headers); | 90 | headers(memory_manager, dma_state.dma_get, command_list_header.size, |
| 91 | &command_headers); | ||
| 92 | ProcessCommands(headers); | ||
| 93 | }; | ||
| 94 | const auto unsafe_process = [&] { | ||
| 95 | Core::Memory::GpuGuestMemory<Tegra::CommandHeader, | ||
| 96 | Core::Memory::GuestMemoryFlags::UnsafeRead> | ||
| 97 | headers(memory_manager, dma_state.dma_get, command_list_header.size, | ||
| 98 | &command_headers); | ||
| 99 | ProcessCommands(headers); | ||
| 100 | }; | ||
| 101 | if (Settings::IsGPULevelHigh()) { | ||
| 102 | if (dma_state.method >= MacroRegistersStart) { | ||
| 103 | unsafe_process(); | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute && | ||
| 107 | dma_state.method == ComputeInline) { | ||
| 108 | unsafe_process(); | ||
| 109 | return true; | ||
| 110 | } | ||
| 111 | safe_process(); | ||
| 112 | return true; | ||
| 113 | } | ||
| 114 | unsafe_process(); | ||
| 90 | } | 115 | } |
| 91 | |||
| 92 | return true; | 116 | return true; |
| 93 | } | 117 | } |
| 94 | 118 | ||
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 8a2784cdc..c9fab2d90 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h | |||
| @@ -130,8 +130,10 @@ public: | |||
| 130 | 130 | ||
| 131 | void DispatchCalls(); | 131 | void DispatchCalls(); |
| 132 | 132 | ||
| 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { | 133 | void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, |
| 134 | Engines::EngineTypes engine_type) { | ||
| 134 | subchannels[subchannel_id] = engine; | 135 | subchannels[subchannel_id] = engine; |
| 136 | subchannel_type[subchannel_id] = engine_type; | ||
| 135 | } | 137 | } |
| 136 | 138 | ||
| 137 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 139 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| @@ -170,6 +172,7 @@ private: | |||
| 170 | const bool ib_enable{true}; ///< IB mode enabled | 172 | const bool ib_enable{true}; ///< IB mode enabled |
| 171 | 173 | ||
| 172 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; | 174 | std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; |
| 175 | std::array<Engines::EngineTypes, max_subchannels> subchannel_type; | ||
| 173 | 176 | ||
| 174 | GPU& gpu; | 177 | GPU& gpu; |
| 175 | Core::System& system; | 178 | Core::System& system; |
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index 392322358..54631ee6c 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h | |||
| @@ -11,6 +11,14 @@ | |||
| 11 | 11 | ||
| 12 | namespace Tegra::Engines { | 12 | namespace Tegra::Engines { |
| 13 | 13 | ||
| 14 | enum class EngineTypes : u32 { | ||
| 15 | KeplerCompute, | ||
| 16 | Maxwell3D, | ||
| 17 | Fermi2D, | ||
| 18 | MaxwellDMA, | ||
| 19 | KeplerMemory, | ||
| 20 | }; | ||
| 21 | |||
| 14 | class EngineInterface { | 22 | class EngineInterface { |
| 15 | public: | 23 | public: |
| 16 | virtual ~EngineInterface() = default; | 24 | virtual ~EngineInterface() = default; |
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index 7242d2529..21bf8aeb4 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h | |||
| @@ -69,6 +69,14 @@ public: | |||
| 69 | /// Binds a rasterizer to this engine. | 69 | /// Binds a rasterizer to this engine. |
| 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); | 70 | void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); |
| 71 | 71 | ||
| 72 | GPUVAddr ExecTargetAddress() const { | ||
| 73 | return regs.dest.Address(); | ||
| 74 | } | ||
| 75 | |||
| 76 | u32 GetUploadSize() const { | ||
| 77 | return copy_size; | ||
| 78 | } | ||
| 79 | |||
| 72 | private: | 80 | private: |
| 73 | void ProcessData(std::span<const u8> read_buffer); | 81 | void ProcessData(std::span<const u8> read_buffer); |
| 74 | 82 | ||
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index a38d9528a..cd61ab222 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp | |||
| @@ -43,16 +43,33 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal | |||
| 43 | 43 | ||
| 44 | switch (method) { | 44 | switch (method) { |
| 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { | 45 | case KEPLER_COMPUTE_REG_INDEX(exec_upload): { |
| 46 | UploadInfo info{.upload_address = upload_address, | ||
| 47 | .exec_address = upload_state.ExecTargetAddress(), | ||
| 48 | .copy_size = upload_state.GetUploadSize()}; | ||
| 49 | uploads.push_back(info); | ||
| 46 | upload_state.ProcessExec(regs.exec_upload.linear != 0); | 50 | upload_state.ProcessExec(regs.exec_upload.linear != 0); |
| 47 | break; | 51 | break; |
| 48 | } | 52 | } |
| 49 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { | 53 | case KEPLER_COMPUTE_REG_INDEX(data_upload): { |
| 54 | upload_address = current_dma_segment; | ||
| 50 | upload_state.ProcessData(method_argument, is_last_call); | 55 | upload_state.ProcessData(method_argument, is_last_call); |
| 51 | break; | 56 | break; |
| 52 | } | 57 | } |
| 53 | case KEPLER_COMPUTE_REG_INDEX(launch): | 58 | case KEPLER_COMPUTE_REG_INDEX(launch): { |
| 59 | const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); | ||
| 60 | |||
| 61 | for (auto& data : uploads) { | ||
| 62 | const GPUVAddr offset = data.exec_address - launch_desc_loc; | ||
| 63 | if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) && | ||
| 64 | memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) { | ||
| 65 | indirect_compute = {data.upload_address}; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | uploads.clear(); | ||
| 54 | ProcessLaunch(); | 69 | ProcessLaunch(); |
| 70 | indirect_compute = std::nullopt; | ||
| 55 | break; | 71 | break; |
| 72 | } | ||
| 56 | default: | 73 | default: |
| 57 | break; | 74 | break; |
| 58 | } | 75 | } |
| @@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun | |||
| 62 | u32 methods_pending) { | 79 | u32 methods_pending) { |
| 63 | switch (method) { | 80 | switch (method) { |
| 64 | case KEPLER_COMPUTE_REG_INDEX(data_upload): | 81 | case KEPLER_COMPUTE_REG_INDEX(data_upload): |
| 82 | upload_address = current_dma_segment; | ||
| 65 | upload_state.ProcessData(base_start, amount); | 83 | upload_state.ProcessData(base_start, amount); |
| 66 | return; | 84 | return; |
| 67 | default: | 85 | default: |
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 2092e685f..735e05fb4 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <optional> | ||
| 8 | #include <vector> | 9 | #include <vector> |
| 9 | #include "common/bit_field.h" | 10 | #include "common/bit_field.h" |
| 10 | #include "common/common_funcs.h" | 11 | #include "common/common_funcs.h" |
| @@ -36,6 +37,9 @@ namespace Tegra::Engines { | |||
| 36 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ | 37 | #define KEPLER_COMPUTE_REG_INDEX(field_name) \ |
| 37 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) | 38 | (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) |
| 38 | 39 | ||
| 40 | #define LAUNCH_REG_INDEX(field_name) \ | ||
| 41 | (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32)) | ||
| 42 | |||
| 39 | class KeplerCompute final : public EngineInterface { | 43 | class KeplerCompute final : public EngineInterface { |
| 40 | public: | 44 | public: |
| 41 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); | 45 | explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); |
| @@ -201,6 +205,10 @@ public: | |||
| 201 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, | 205 | void CallMultiMethod(u32 method, const u32* base_start, u32 amount, |
| 202 | u32 methods_pending) override; | 206 | u32 methods_pending) override; |
| 203 | 207 | ||
| 208 | std::optional<GPUVAddr> GetIndirectComputeAddress() const { | ||
| 209 | return indirect_compute; | ||
| 210 | } | ||
| 211 | |||
| 204 | private: | 212 | private: |
| 205 | void ProcessLaunch(); | 213 | void ProcessLaunch(); |
| 206 | 214 | ||
| @@ -216,6 +224,15 @@ private: | |||
| 216 | MemoryManager& memory_manager; | 224 | MemoryManager& memory_manager; |
| 217 | VideoCore::RasterizerInterface* rasterizer = nullptr; | 225 | VideoCore::RasterizerInterface* rasterizer = nullptr; |
| 218 | Upload::State upload_state; | 226 | Upload::State upload_state; |
| 227 | GPUVAddr upload_address; | ||
| 228 | |||
| 229 | struct UploadInfo { | ||
| 230 | GPUVAddr upload_address; | ||
| 231 | GPUVAddr exec_address; | ||
| 232 | u32 copy_size; | ||
| 233 | }; | ||
| 234 | std::vector<UploadInfo> uploads; | ||
| 235 | std::optional<GPUVAddr> indirect_compute{}; | ||
| 219 | }; | 236 | }; |
| 220 | 237 | ||
| 221 | #define ASSERT_REG_POSITION(field_name, position) \ | 238 | #define ASSERT_REG_POSITION(field_name, position) \ |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index c3696096d..06e349e43 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() { | |||
| 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); | 257 | const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); |
| 258 | num_vertices = std::max( | 258 | num_vertices = std::max( |
| 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); | 259 | num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); |
| 260 | break; | ||
| 260 | } | 261 | } |
| 261 | return num_vertices; | 262 | return num_vertices; |
| 262 | } | 263 | } |
| @@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() { | |||
| 269 | std::numeric_limits<u32>::max()}; | 270 | std::numeric_limits<u32>::max()}; |
| 270 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); | 271 | const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); |
| 271 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); | 272 | const size_t log2_byte_size = Common::Log2Ceil64(byte_size); |
| 273 | const size_t cap{GetMaxCurrentVertices() * 3 * byte_size}; | ||
| 274 | const size_t lower_cap = | ||
| 275 | std::min<size_t>(static_cast<size_t>(end_address - start_address), cap); | ||
| 272 | return std::min<size_t>( | 276 | return std::min<size_t>( |
| 273 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / | 277 | memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / |
| 274 | byte_size, | 278 | byte_size, |
| 275 | static_cast<size_t>(end_address - start_address)); | 279 | lower_cap); |
| 276 | } | 280 | } |
| 277 | 281 | ||
| 278 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { | 282 | u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { |
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 7718a09b3..6de2543b7 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp | |||
| @@ -34,19 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { | |||
| 34 | bound_engines[method_call.subchannel] = engine_id; | 34 | bound_engines[method_call.subchannel] = engine_id; |
| 35 | switch (engine_id) { | 35 | switch (engine_id) { |
| 36 | case EngineID::FERMI_TWOD_A: | 36 | case EngineID::FERMI_TWOD_A: |
| 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel); | 37 | dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, |
| 38 | EngineTypes::Fermi2D); | ||
| 38 | break; | 39 | break; |
| 39 | case EngineID::MAXWELL_B: | 40 | case EngineID::MAXWELL_B: |
| 40 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel); | 41 | dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, |
| 42 | EngineTypes::Maxwell3D); | ||
| 41 | break; | 43 | break; |
| 42 | case EngineID::KEPLER_COMPUTE_B: | 44 | case EngineID::KEPLER_COMPUTE_B: |
| 43 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel); | 45 | dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, |
| 46 | EngineTypes::KeplerCompute); | ||
| 44 | break; | 47 | break; |
| 45 | case EngineID::MAXWELL_DMA_COPY_A: | 48 | case EngineID::MAXWELL_DMA_COPY_A: |
| 46 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel); | 49 | dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, |
| 50 | EngineTypes::MaxwellDMA); | ||
| 47 | break; | 51 | break; |
| 48 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: | 52 | case EngineID::KEPLER_INLINE_TO_MEMORY_B: |
| 49 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel); | 53 | dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, |
| 54 | EngineTypes::KeplerMemory); | ||
| 50 | break; | 55 | break; |
| 51 | default: | 56 | default: |
| 52 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); | 57 | UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 1ba31be88..dd03efecd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -380,6 +380,17 @@ void RasterizerOpenGL::DispatchCompute() { | |||
| 380 | pipeline->SetEngine(kepler_compute, gpu_memory); | 380 | pipeline->SetEngine(kepler_compute, gpu_memory); |
| 381 | pipeline->Configure(); | 381 | pipeline->Configure(); |
| 382 | const auto& qmd{kepler_compute->launch_description}; | 382 | const auto& qmd{kepler_compute->launch_description}; |
| 383 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 384 | if (indirect_address) { | ||
| 385 | // DispatchIndirect | ||
| 386 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 387 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 388 | const auto [buffer, offset] = | ||
| 389 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 390 | glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle()); | ||
| 391 | glDispatchComputeIndirect(static_cast<GLintptr>(offset)); | ||
| 392 | return; | ||
| 393 | } | ||
| 383 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); | 394 | glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); |
| 384 | ++num_queued_commands; | 395 | ++num_queued_commands; |
| 385 | has_written_global_memory |= pipeline->WritesGlobalMemory(); | 396 | has_written_global_memory |= pipeline->WritesGlobalMemory(); |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index fe432dfe1..4f83a88e1 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -665,6 +665,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | |||
| 665 | std::move(modules), infos); | 665 | std::move(modules), infos); |
| 666 | 666 | ||
| 667 | } catch (const Shader::Exception& exception) { | 667 | } catch (const Shader::Exception& exception) { |
| 668 | auto hash = key.Hash(); | ||
| 669 | size_t env_index{0}; | ||
| 670 | for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { | ||
| 671 | if (key.unique_hashes[index] == 0) { | ||
| 672 | continue; | ||
| 673 | } | ||
| 674 | Shader::Environment& env{*envs[env_index]}; | ||
| 675 | ++env_index; | ||
| 676 | |||
| 677 | const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | ||
| 678 | Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | ||
| 679 | env.Dump(hash, key.unique_hashes[index]); | ||
| 680 | } | ||
| 668 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); | 681 | LOG_ERROR(Render_Vulkan, "{}", exception.what()); |
| 669 | return nullptr; | 682 | return nullptr; |
| 670 | } | 683 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 032f694bc..01e76a82c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -463,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() { | |||
| 463 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); | 463 | pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); |
| 464 | 464 | ||
| 465 | const auto& qmd{kepler_compute->launch_description}; | 465 | const auto& qmd{kepler_compute->launch_description}; |
| 466 | auto indirect_address = kepler_compute->GetIndirectComputeAddress(); | ||
| 467 | if (indirect_address) { | ||
| 468 | // DispatchIndirect | ||
| 469 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | ||
| 470 | const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite; | ||
| 471 | const auto [buffer, offset] = | ||
| 472 | buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); | ||
| 473 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 474 | scheduler.Record([indirect_buffer = buffer->Handle(), | ||
| 475 | indirect_offset = offset](vk::CommandBuffer cmdbuf) { | ||
| 476 | cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); | ||
| 477 | }); | ||
| 478 | return; | ||
| 479 | } | ||
| 466 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; | 480 | const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; |
| 467 | scheduler.RequestOutsideRenderPassOperationContext(); | 481 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 468 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); | 482 | scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 78e5a248f..c3f388d89 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp | |||
| @@ -92,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { | |||
| 92 | X(vkCmdCopyImage); | 92 | X(vkCmdCopyImage); |
| 93 | X(vkCmdCopyImageToBuffer); | 93 | X(vkCmdCopyImageToBuffer); |
| 94 | X(vkCmdDispatch); | 94 | X(vkCmdDispatch); |
| 95 | X(vkCmdDispatchIndirect); | ||
| 95 | X(vkCmdDraw); | 96 | X(vkCmdDraw); |
| 96 | X(vkCmdDrawIndexed); | 97 | X(vkCmdDrawIndexed); |
| 97 | X(vkCmdDrawIndirect); | 98 | X(vkCmdDrawIndirect); |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index c226a2a29..049fa8038 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h | |||
| @@ -203,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch { | |||
| 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; | 203 | PFN_vkCmdCopyImage vkCmdCopyImage{}; |
| 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; | 204 | PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; |
| 205 | PFN_vkCmdDispatch vkCmdDispatch{}; | 205 | PFN_vkCmdDispatch vkCmdDispatch{}; |
| 206 | PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{}; | ||
| 206 | PFN_vkCmdDraw vkCmdDraw{}; | 207 | PFN_vkCmdDraw vkCmdDraw{}; |
| 207 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; | 208 | PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; |
| 208 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; | 209 | PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; |
| @@ -1209,6 +1210,10 @@ public: | |||
| 1209 | dld->vkCmdDispatch(handle, x, y, z); | 1210 | dld->vkCmdDispatch(handle, x, y, z); |
| 1210 | } | 1211 | } |
| 1211 | 1212 | ||
| 1213 | void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept { | ||
| 1214 | dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset); | ||
| 1215 | } | ||
| 1216 | |||
| 1212 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, | 1217 | void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, |
| 1213 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, | 1218 | VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, |
| 1214 | Span<VkBufferMemoryBarrier> buffer_barriers, | 1219 | Span<VkBufferMemoryBarrier> buffer_barriers, |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 33c9fd0af..4e435c7e2 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2010,8 +2010,16 @@ bool GMainWindow::OnShutdownBegin() { | |||
| 2010 | 2010 | ||
| 2011 | emit EmulationStopping(); | 2011 | emit EmulationStopping(); |
| 2012 | 2012 | ||
| 2013 | int shutdown_time = 1000; | ||
| 2014 | |||
| 2015 | if (system->DebuggerEnabled()) { | ||
| 2016 | shutdown_time = 0; | ||
| 2017 | } else if (system->GetExitLocked()) { | ||
| 2018 | shutdown_time = 5000; | ||
| 2019 | } | ||
| 2020 | |||
| 2013 | shutdown_timer.setSingleShot(true); | 2021 | shutdown_timer.setSingleShot(true); |
| 2014 | shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); | 2022 | shutdown_timer.start(shutdown_time); |
| 2015 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); | 2023 | connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); |
| 2016 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); | 2024 | connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); |
| 2017 | 2025 | ||
| @@ -2267,40 +2275,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { | |||
| 2267 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); | 2275 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); |
| 2268 | } | 2276 | } |
| 2269 | 2277 | ||
| 2270 | static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { | 2278 | static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog, |
| 2271 | std::size_t out = 0; | 2279 | const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, |
| 2272 | 2280 | bool full) { | |
| 2273 | for (const auto& subdir : dir->GetSubdirectories()) { | ||
| 2274 | out += 1 + CalculateRomFSEntrySize(subdir, full); | ||
| 2275 | } | ||
| 2276 | |||
| 2277 | return out + (full ? dir->GetFiles().size() : 0); | ||
| 2278 | } | ||
| 2279 | |||
| 2280 | static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src, | ||
| 2281 | const FileSys::VirtualDir& dest, std::size_t block_size, bool full) { | ||
| 2282 | if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | 2281 | if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) |
| 2283 | return false; | 2282 | return false; |
| 2284 | if (dialog.wasCanceled()) | 2283 | if (dialog.wasCanceled()) |
| 2285 | return false; | 2284 | return false; |
| 2286 | 2285 | ||
| 2286 | std::vector<u8> buffer(CopyBufferSize); | ||
| 2287 | auto last_timestamp = std::chrono::steady_clock::now(); | ||
| 2288 | |||
| 2289 | const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file, | ||
| 2290 | const FileSys::VirtualFile& dest_file) { | ||
| 2291 | if (src_file == nullptr || dest_file == nullptr) { | ||
| 2292 | return false; | ||
| 2293 | } | ||
| 2294 | if (!dest_file->Resize(src_file->GetSize())) { | ||
| 2295 | return false; | ||
| 2296 | } | ||
| 2297 | |||
| 2298 | for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) { | ||
| 2299 | if (dialog.wasCanceled()) { | ||
| 2300 | dest_file->Resize(0); | ||
| 2301 | return false; | ||
| 2302 | } | ||
| 2303 | |||
| 2304 | using namespace std::literals::chrono_literals; | ||
| 2305 | const auto new_timestamp = std::chrono::steady_clock::now(); | ||
| 2306 | |||
| 2307 | if ((new_timestamp - last_timestamp) > 33ms) { | ||
| 2308 | last_timestamp = new_timestamp; | ||
| 2309 | dialog.setValue( | ||
| 2310 | static_cast<int>(std::min(read_size, total_size) * 100 / total_size)); | ||
| 2311 | QCoreApplication::processEvents(); | ||
| 2312 | } | ||
| 2313 | |||
| 2314 | const auto read = src_file->Read(buffer.data(), buffer.size(), i); | ||
| 2315 | dest_file->Write(buffer.data(), read, i); | ||
| 2316 | |||
| 2317 | read_size += read; | ||
| 2318 | } | ||
| 2319 | |||
| 2320 | return true; | ||
| 2321 | }; | ||
| 2322 | |||
| 2287 | if (full) { | 2323 | if (full) { |
| 2288 | for (const auto& file : src->GetFiles()) { | 2324 | for (const auto& file : src->GetFiles()) { |
| 2289 | const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); | 2325 | const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); |
| 2290 | if (!FileSys::VfsRawCopy(file, out, block_size)) | 2326 | if (!QtRawCopy(file, out)) |
| 2291 | return false; | ||
| 2292 | dialog.setValue(dialog.value() + 1); | ||
| 2293 | if (dialog.wasCanceled()) | ||
| 2294 | return false; | 2327 | return false; |
| 2295 | } | 2328 | } |
| 2296 | } | 2329 | } |
| 2297 | 2330 | ||
| 2298 | for (const auto& dir : src->GetSubdirectories()) { | 2331 | for (const auto& dir : src->GetSubdirectories()) { |
| 2299 | const auto out = dest->CreateSubdirectory(dir->GetName()); | 2332 | const auto out = dest->CreateSubdirectory(dir->GetName()); |
| 2300 | if (!RomFSRawCopy(dialog, dir, out, block_size, full)) | 2333 | if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full)) |
| 2301 | return false; | ||
| 2302 | dialog.setValue(dialog.value() + 1); | ||
| 2303 | if (dialog.wasCanceled()) | ||
| 2304 | return false; | 2334 | return false; |
| 2305 | } | 2335 | } |
| 2306 | 2336 | ||
| @@ -2573,50 +2603,48 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2573 | return; | 2603 | return; |
| 2574 | } | 2604 | } |
| 2575 | 2605 | ||
| 2576 | FileSys::VirtualFile base_romfs; | 2606 | FileSys::VirtualFile packed_update_raw{}; |
| 2577 | if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) { | 2607 | loader->ReadUpdateRaw(packed_update_raw); |
| 2578 | failed(); | ||
| 2579 | return; | ||
| 2580 | } | ||
| 2581 | 2608 | ||
| 2582 | const auto& installed = system->GetContentProvider(); | 2609 | const auto& installed = system->GetContentProvider(); |
| 2583 | const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); | ||
| 2584 | 2610 | ||
| 2585 | if (!romfs_title_id) { | 2611 | u64 title_id{}; |
| 2612 | u8 raw_type{}; | ||
| 2613 | if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) { | ||
| 2586 | failed(); | 2614 | failed(); |
| 2587 | return; | 2615 | return; |
| 2588 | } | 2616 | } |
| 2589 | 2617 | ||
| 2590 | const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program | 2618 | const auto type = static_cast<FileSys::ContentRecordType>(raw_type); |
| 2591 | : FileSys::ContentRecordType::Data; | 2619 | const auto base_nca = installed.GetEntry(title_id, type); |
| 2592 | const auto base_nca = installed.GetEntry(*romfs_title_id, type); | ||
| 2593 | if (!base_nca) { | 2620 | if (!base_nca) { |
| 2594 | failed(); | 2621 | failed(); |
| 2595 | return; | 2622 | return; |
| 2596 | } | 2623 | } |
| 2597 | 2624 | ||
| 2625 | const FileSys::NCA update_nca{packed_update_raw, nullptr}; | ||
| 2626 | if (type != FileSys::ContentRecordType::Program || | ||
| 2627 | update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || | ||
| 2628 | update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) { | ||
| 2629 | packed_update_raw = {}; | ||
| 2630 | } | ||
| 2631 | |||
| 2632 | const auto base_romfs = base_nca->GetRomFS(); | ||
| 2633 | if (!base_romfs) { | ||
| 2634 | failed(); | ||
| 2635 | return; | ||
| 2636 | } | ||
| 2637 | |||
| 2598 | const auto dump_dir = | 2638 | const auto dump_dir = |
| 2599 | target == DumpRomFSTarget::Normal | 2639 | target == DumpRomFSTarget::Normal |
| 2600 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) | 2640 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) |
| 2601 | : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; | 2641 | : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; |
| 2602 | const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); | 2642 | const auto romfs_dir = fmt::format("{:016X}/romfs", title_id); |
| 2603 | 2643 | ||
| 2604 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); | 2644 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); |
| 2605 | 2645 | ||
| 2606 | FileSys::VirtualFile romfs; | 2646 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed}; |
| 2607 | 2647 | auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); | |
| 2608 | if (*romfs_title_id == program_id) { | ||
| 2609 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; | ||
| 2610 | romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false); | ||
| 2611 | } else { | ||
| 2612 | romfs = installed.GetEntry(*romfs_title_id, type)->GetRomFS(); | ||
| 2613 | } | ||
| 2614 | |||
| 2615 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | ||
| 2616 | if (extracted == nullptr) { | ||
| 2617 | failed(); | ||
| 2618 | return; | ||
| 2619 | } | ||
| 2620 | 2648 | ||
| 2621 | const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); | 2649 | const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); |
| 2622 | 2650 | ||
| @@ -2640,11 +2668,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2640 | return; | 2668 | return; |
| 2641 | } | 2669 | } |
| 2642 | 2670 | ||
| 2671 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | ||
| 2672 | if (extracted == nullptr) { | ||
| 2673 | failed(); | ||
| 2674 | return; | ||
| 2675 | } | ||
| 2676 | |||
| 2643 | const auto full = res == selections.constFirst(); | 2677 | const auto full = res == selections.constFirst(); |
| 2644 | const auto entry_size = CalculateRomFSEntrySize(extracted, full); | ||
| 2645 | 2678 | ||
| 2646 | // The minimum required space is the size of the extracted RomFS + 1 GiB | 2679 | // The expected required space is the size of the RomFS + 1 GiB |
| 2647 | const auto minimum_free_space = extracted->GetSize() + 0x40000000; | 2680 | const auto minimum_free_space = romfs->GetSize() + 0x40000000; |
| 2648 | 2681 | ||
| 2649 | if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { | 2682 | if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { |
| 2650 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | 2683 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), |
| @@ -2655,12 +2688,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2655 | return; | 2688 | return; |
| 2656 | } | 2689 | } |
| 2657 | 2690 | ||
| 2658 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, | 2691 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this); |
| 2659 | static_cast<s32>(entry_size), this); | ||
| 2660 | progress.setWindowModality(Qt::WindowModal); | 2692 | progress.setWindowModality(Qt::WindowModal); |
| 2661 | progress.setMinimumDuration(100); | 2693 | progress.setMinimumDuration(100); |
| 2694 | progress.setAutoClose(false); | ||
| 2695 | progress.setAutoReset(false); | ||
| 2696 | |||
| 2697 | size_t read_size = 0; | ||
| 2662 | 2698 | ||
| 2663 | if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) { | 2699 | if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) { |
| 2664 | progress.close(); | 2700 | progress.close(); |
| 2665 | QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), | 2701 | QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), |
| 2666 | tr("The operation completed successfully.")); | 2702 | tr("The operation completed successfully.")); |
| @@ -3261,7 +3297,7 @@ void GMainWindow::OnPauseContinueGame() { | |||
| 3261 | } | 3297 | } |
| 3262 | 3298 | ||
| 3263 | void GMainWindow::OnStopGame() { | 3299 | void GMainWindow::OnStopGame() { |
| 3264 | if (system->GetExitLock() && !ConfirmForceLockedExit()) { | 3300 | if (system->GetExitLocked() && !ConfirmForceLockedExit()) { |
| 3265 | return; | 3301 | return; |
| 3266 | } | 3302 | } |
| 3267 | 3303 | ||
| @@ -4350,28 +4386,41 @@ bool GMainWindow::CheckSystemArchiveDecryption() { | |||
| 4350 | return mii_nca->GetRomFS().get() != nullptr; | 4386 | return mii_nca->GetRomFS().get() != nullptr; |
| 4351 | } | 4387 | } |
| 4352 | 4388 | ||
| 4353 | std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, | 4389 | bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, |
| 4354 | u64 program_id) { | 4390 | u64* selected_title_id, u8* selected_content_record_type) { |
| 4355 | const auto dlc_entries = | 4391 | using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; |
| 4356 | installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | 4392 | boost::container::flat_map<u64, ContentInfo> available_title_ids; |
| 4357 | std::vector<FileSys::ContentProviderEntry> dlc_match; | 4393 | |
| 4358 | dlc_match.reserve(dlc_entries.size()); | 4394 | const auto RetrieveEntries = [&](FileSys::TitleType title_type, |
| 4359 | std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), | 4395 | FileSys::ContentRecordType record_type) { |
| 4360 | [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { | 4396 | const auto entries = installed.ListEntriesFilter(title_type, record_type); |
| 4361 | return FileSys::GetBaseTitleID(entry.title_id) == program_id && | 4397 | for (const auto& entry : entries) { |
| 4362 | installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; | 4398 | if (FileSys::GetBaseTitleID(entry.title_id) == program_id && |
| 4363 | }); | 4399 | installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { |
| 4364 | 4400 | available_title_ids[entry.title_id] = {title_type, record_type}; | |
| 4365 | std::vector<u64> romfs_tids; | 4401 | } |
| 4366 | romfs_tids.push_back(program_id); | 4402 | } |
| 4367 | for (const auto& entry : dlc_match) { | 4403 | }; |
| 4368 | romfs_tids.push_back(entry.title_id); | 4404 | |
| 4369 | } | 4405 | RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); |
| 4370 | 4406 | RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | |
| 4371 | if (romfs_tids.size() > 1) { | 4407 | |
| 4372 | QStringList list{QStringLiteral("Base")}; | 4408 | if (available_title_ids.empty()) { |
| 4373 | for (std::size_t i = 1; i < romfs_tids.size(); ++i) { | 4409 | return false; |
| 4374 | list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); | 4410 | } |
| 4411 | |||
| 4412 | size_t title_index = 0; | ||
| 4413 | |||
| 4414 | if (available_title_ids.size() > 1) { | ||
| 4415 | QStringList list; | ||
| 4416 | for (auto& [title_id, content_info] : available_title_ids) { | ||
| 4417 | const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); | ||
| 4418 | if (content_info.first == FileSys::TitleType::Application) { | ||
| 4419 | list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id)); | ||
| 4420 | } else { | ||
| 4421 | list.push_back( | ||
| 4422 | QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); | ||
| 4423 | } | ||
| 4375 | } | 4424 | } |
| 4376 | 4425 | ||
| 4377 | bool ok; | 4426 | bool ok; |
| @@ -4379,13 +4428,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv | |||
| 4379 | this, tr("Select RomFS Dump Target"), | 4428 | this, tr("Select RomFS Dump Target"), |
| 4380 | tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); | 4429 | tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); |
| 4381 | if (!ok) { | 4430 | if (!ok) { |
| 4382 | return {}; | 4431 | return false; |
| 4383 | } | 4432 | } |
| 4384 | 4433 | ||
| 4385 | return romfs_tids[list.indexOf(res)]; | 4434 | title_index = list.indexOf(res); |
| 4386 | } | 4435 | } |
| 4387 | 4436 | ||
| 4388 | return program_id; | 4437 | const auto selected_info = available_title_ids.nth(title_index); |
| 4438 | *selected_title_id = selected_info->first; | ||
| 4439 | *selected_content_record_type = static_cast<u8>(selected_info->second.second); | ||
| 4440 | return true; | ||
| 4389 | } | 4441 | } |
| 4390 | 4442 | ||
| 4391 | bool GMainWindow::ConfirmClose() { | 4443 | bool GMainWindow::ConfirmClose() { |
| @@ -4515,6 +4567,8 @@ void GMainWindow::RequestGameExit() { | |||
| 4515 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); | 4567 | auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); |
| 4516 | bool has_signalled = false; | 4568 | bool has_signalled = false; |
| 4517 | 4569 | ||
| 4570 | system->SetExitRequested(true); | ||
| 4571 | |||
| 4518 | if (applet_oe != nullptr) { | 4572 | if (applet_oe != nullptr) { |
| 4519 | applet_oe->GetMessageQueue()->RequestExit(); | 4573 | applet_oe->GetMessageQueue()->RequestExit(); |
| 4520 | has_signalled = true; | 4574 | has_signalled = true; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1b7055122..668dbc3b1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -375,7 +375,8 @@ private: | |||
| 375 | void RemoveAllTransferableShaderCaches(u64 program_id); | 375 | void RemoveAllTransferableShaderCaches(u64 program_id); |
| 376 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); | 376 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); |
| 377 | void RemoveCacheStorage(u64 program_id); | 377 | void RemoveCacheStorage(u64 program_id); |
| 378 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); | 378 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 379 | u64* selected_title_id, u8* selected_content_record_type); | ||
| 379 | InstallResult InstallNSPXCI(const QString& filename); | 380 | InstallResult InstallNSPXCI(const QString& filename); |
| 380 | InstallResult InstallNCA(const QString& filename); | 381 | InstallResult InstallNCA(const QString& filename); |
| 381 | void MigrateConfigFiles(); | 382 | void MigrateConfigFiles(); |